Compare commits
5 Commits
apple-reje
...
qc/2-dec-2
| Author | SHA1 | Date | |
|---|---|---|---|
| 858b441a8c | |||
| 98aaa126a1 | |||
| 69452ff4e7 | |||
| 33ec892ec8 | |||
| 8a900e9469 |
@@ -82,6 +82,14 @@ def enableMinifyInReleaseBuilds = (findProperty('android.enableMinifyInReleaseBu
|
||||
def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'
|
||||
|
||||
android {
|
||||
// @generated begin @rnmapbox/maps-libcpp - expo prebuild (DO NOT MODIFY) sync-e24830a5a3e854b398227dfe9630aabfaa1cadd1
|
||||
packagingOptions {
|
||||
pickFirst 'lib/x86/libc++_shared.so'
|
||||
pickFirst 'lib/x86_64/libc++_shared.so'
|
||||
pickFirst 'lib/arm64-v8a/libc++_shared.so'
|
||||
pickFirst 'lib/armeabi-v7a/libc++_shared.so'
|
||||
}
|
||||
// @generated end @rnmapbox/maps-libcpp
|
||||
ndkVersion rootProject.ext.ndkVersion
|
||||
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
|
||||
@@ -23,3 +23,25 @@ allprojects {
|
||||
|
||||
apply plugin: "expo-root-project"
|
||||
apply plugin: "com.facebook.react.rootproject"
|
||||
// @generated begin @rnmapbox/maps-v2-maven - expo prebuild (DO NOT MODIFY) sync-d4ccbfdff48fdba3138b02a8ba41b9722af001d8
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://api.mapbox.com/downloads/v2/releases/maven'
|
||||
// Authentication is no longer required as per Mapbox's removal of download token requirement
|
||||
// See: https://github.com/mapbox/mapbox-maps-flutter/issues/775
|
||||
// Keeping this as optional for backward compatibility
|
||||
def token = project.properties['MAPBOX_DOWNLOADS_TOKEN'] ?: System.getenv('RNMAPBOX_MAPS_DOWNLOAD_TOKEN')
|
||||
if (token) {
|
||||
authentication { basic(BasicAuthentication) }
|
||||
credentials {
|
||||
username = 'mapbox'
|
||||
password = token
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @generated end @rnmapbox/maps-v2-maven
|
||||
@@ -19,7 +19,7 @@ export default {
|
||||
"NSLocationWhenInUseUsageDescription": "Aplikasi membutuhkan akses lokasi untuk menampilkan peta.",
|
||||
},
|
||||
associatedDomains: ["applinks:cld-dkr-staging-hipmi.wibudev.com"],
|
||||
buildNumber: "8",
|
||||
buildNumber: "10",
|
||||
},
|
||||
|
||||
android: {
|
||||
|
||||
@@ -155,7 +155,7 @@ export default function CollaborationCreate() {
|
||||
<TextAreaCustom
|
||||
required
|
||||
label="Keuntungan Proyek"
|
||||
placeholder="Masukan keuntungan proyek"
|
||||
placeholder="Masukan keuntungan proyek, contoh: Meningkatkan relasi bisnis , menjamin kualitas produk, meningkatkan kinerja dan lain lain"
|
||||
showCount
|
||||
maxLength={1000}
|
||||
value={data?.benefit}
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
import { apiMasterEventType } from "@/service/api-client/api-master";
|
||||
import { DateTimePickerEvent } from "@react-native-community/datetimepicker";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function EventEdit() {
|
||||
@@ -55,6 +55,7 @@ export default function EventEdit() {
|
||||
try {
|
||||
setIsLoadData(true);
|
||||
const response = await apiEventGetOne({ id: id as string });
|
||||
console.log("[DATA BY ID]", JSON.stringify(response, null, 2));
|
||||
if (response.success) {
|
||||
setData(response.data);
|
||||
setSelectedDate(new Date(response.data.tanggal));
|
||||
@@ -209,7 +210,7 @@ export default function EventEdit() {
|
||||
minimumDate={new Date(Date.now())}
|
||||
label="Tanggal & Waktu Mulai"
|
||||
required
|
||||
value={selectedDate as any}
|
||||
value={selectedDate}
|
||||
onChange={(date: any) => {
|
||||
setSelectedDate(date as any);
|
||||
}}
|
||||
@@ -254,7 +255,6 @@ export default function EventEdit() {
|
||||
placeholder="Masukkan deskripsi event"
|
||||
required
|
||||
showCount
|
||||
maxLength={100}
|
||||
value={data?.deskripsi}
|
||||
onChangeText={(value) => setData({ ...data, deskripsi: value })}
|
||||
/>
|
||||
|
||||
@@ -110,13 +110,14 @@ export default function EventCreate() {
|
||||
const response = await apiEventCreate(newData);
|
||||
console.log("Response", JSON.stringify(response, null, 2));
|
||||
|
||||
router.navigate("/event/status");
|
||||
router.replace("/event/status");
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const buttonSubmit = (
|
||||
<ButtonCustom
|
||||
@@ -191,7 +192,7 @@ export default function EventCreate() {
|
||||
placeholder="Masukkan deskripsi event"
|
||||
required
|
||||
showCount
|
||||
maxLength={1000}
|
||||
value={data?.deskripsi || ""}
|
||||
onChangeText={(value: any) =>
|
||||
setData({ ...data, deskripsi: value })
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import Investment_ButtonInvestasiSection from "@/screens/Invesment/ButtonInvesta
|
||||
import Invesment_ComponentBoxOnBottomDetail from "@/screens/Invesment/ComponentBoxOnBottomDetail";
|
||||
import Invesment_DetailDataPublishSection from "@/screens/Invesment/DetailDataPublishSection";
|
||||
import { apiInvestmentGetOne } from "@/service/api-client/api-investment";
|
||||
import { countDownAndCondition } from "@/utils/countDownAndCondition";
|
||||
import { AntDesign, MaterialIcons } from "@expo/vector-icons";
|
||||
import {
|
||||
router,
|
||||
@@ -23,7 +24,7 @@ import {
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
export default function InvestmentDetail() {
|
||||
const { user } = useAuth();
|
||||
@@ -62,6 +63,31 @@ export default function InvestmentDetail() {
|
||||
setOpenDrawerPublish(false);
|
||||
};
|
||||
|
||||
const [value, setValue] = useState({
|
||||
sisa: 0,
|
||||
reminder: false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
updateCountDown();
|
||||
}, [data]);
|
||||
|
||||
console.log("[DATA DETAIL]", JSON.stringify(data, null, 2));
|
||||
|
||||
const updateCountDown = () => {
|
||||
const countDown = countDownAndCondition({
|
||||
duration: data?.MasterPencarianInvestor.name,
|
||||
publishTime: data?.countDown,
|
||||
});
|
||||
|
||||
setValue({
|
||||
sisa: countDown.durationDay,
|
||||
reminder: countDown.reminder,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
const bottomSection = (
|
||||
<Invesment_ComponentBoxOnBottomDetail
|
||||
id={id as string}
|
||||
@@ -71,7 +97,7 @@ export default function InvestmentDetail() {
|
||||
);
|
||||
|
||||
const buttonSection = (
|
||||
<Investment_ButtonInvestasiSection id={id as string} isMine={user?.id === data?.author?.id} />
|
||||
<Investment_ButtonInvestasiSection id={id as string} isMine={user?.id === data?.author?.id} reminder={value.reminder} />
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
148
app/(application)/(user)/profile/[id]/blocked-list.tsx
Normal file
148
app/(application)/(user)/profile/[id]/blocked-list.tsx
Normal file
@@ -0,0 +1,148 @@
|
||||
import {
|
||||
AvatarUsernameAndOtherComponent,
|
||||
BadgeCustom,
|
||||
ClickableCustom,
|
||||
Divider,
|
||||
SelectCustom,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import ListEmptyComponent from "@/components/_ShareComponent/ListEmptyComponent";
|
||||
import ListLoaderFooterComponent from "@/components/_ShareComponent/ListLoaderFooterComponent";
|
||||
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { usePaginatedApi } from "@/hooks/use-paginated-api";
|
||||
import { apiGetBlocked } from "@/service/api-client/api-blocked";
|
||||
import { apiMasterAppCategory } from "@/service/api-client/api-master";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { RefreshControl, View } from "react-native";
|
||||
|
||||
const PAGE_SIZE = 10;
|
||||
export default function ProfileBlockedList() {
|
||||
const { user } = useAuth();
|
||||
const [masterApp, setMasterApp] = useState<any[]>([]);
|
||||
const isInitialMount = useRef(true);
|
||||
|
||||
const {
|
||||
data: listData,
|
||||
loading,
|
||||
refreshing,
|
||||
hasMore,
|
||||
search,
|
||||
setSearch,
|
||||
onRefresh,
|
||||
loadMore,
|
||||
} = usePaginatedApi({
|
||||
fetcher: async (params: { page: number; search?: string }) => {
|
||||
const response = await apiGetBlocked({
|
||||
id: user?.id as any,
|
||||
search: search,
|
||||
page: String(params.page) as any,
|
||||
});
|
||||
|
||||
return response.data;
|
||||
},
|
||||
initialSearch: "",
|
||||
pageSize: PAGE_SIZE,
|
||||
dependencies: [user?.id],
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
fetchMasterApp();
|
||||
}, []);
|
||||
|
||||
// 🔁 Refresh otomatis saat kembali ke halaman ini
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
if (isInitialMount.current) {
|
||||
// Skip saat pertama kali mount
|
||||
isInitialMount.current = false;
|
||||
return;
|
||||
}
|
||||
// Hanya refresh saat kembali dari screen lain
|
||||
onRefresh();
|
||||
}, [onRefresh])
|
||||
);
|
||||
|
||||
const fetchMasterApp = async () => {
|
||||
const response = await apiMasterAppCategory();
|
||||
setMasterApp(response.data);
|
||||
};
|
||||
|
||||
const renderHeader = () => (
|
||||
<SelectCustom
|
||||
placeholder="Pilih Kategori Fitur"
|
||||
data={masterApp.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))}
|
||||
value={search === "" ? undefined : search}
|
||||
onChange={(value) => {
|
||||
setSearch(value as any);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const renderItem = ({ item }: { item: any }) => (
|
||||
<>
|
||||
<ClickableCustom
|
||||
onPress={() => {
|
||||
router.push(`/profile/${item.id}/detail-blocked`);
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
paddingInline: 8,
|
||||
}}
|
||||
>
|
||||
<AvatarUsernameAndOtherComponent
|
||||
avatarHref={`/profile/${item?.blocked?.Profile?.id}`}
|
||||
avatar={item?.blocked?.Profile?.imageId}
|
||||
name={item?.blocked?.username}
|
||||
rightComponent={
|
||||
<View style={{ flexDirection: "row", gap: 4 }}>
|
||||
<BadgeCustom>
|
||||
<TextCustom size={"small"} bold truncate>
|
||||
{item?.menuFeature?.name}
|
||||
</TextCustom>
|
||||
</BadgeCustom>
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
<Divider color="gray" />
|
||||
</View>
|
||||
</ClickableCustom>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<NewWrapper
|
||||
// headerComponent={renderHeader()}
|
||||
listData={listData}
|
||||
renderItem={renderItem}
|
||||
onEndReached={loadMore}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
progressBackgroundColor={MainColor.yellow}
|
||||
refreshing={refreshing}
|
||||
onRefresh={onRefresh}
|
||||
/>
|
||||
}
|
||||
ListFooterComponent={
|
||||
hasMore && !refreshing ? <ListLoaderFooterComponent /> : null
|
||||
}
|
||||
ListEmptyComponent={
|
||||
!loading && _.isEmpty(listData) ? (
|
||||
<ListSkeletonComponent />
|
||||
) : (
|
||||
<ListEmptyComponent />
|
||||
)
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
93
app/(application)/(user)/profile/[id]/detail-blocked.tsx
Normal file
93
app/(application)/(user)/profile/[id]/detail-blocked.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
import {
|
||||
AlertDefaultSystem,
|
||||
AvatarUsernameAndOtherComponent,
|
||||
BaseBox,
|
||||
BoxButtonOnFooter,
|
||||
BoxWithHeaderSection,
|
||||
ButtonCustom,
|
||||
NewWrapper,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import AvatarAndBackground from "@/screens/Profile/AvatarAndBackground";
|
||||
import {
|
||||
apiGetBlockedById,
|
||||
apiUnblock,
|
||||
} from "@/service/api-client/api-blocked";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function ProfileDetailBlocked() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [data, setData] = useState<any>(null);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [id]);
|
||||
|
||||
const fetchData = async () => {
|
||||
const response = await apiGetBlockedById({ id: String(id) });
|
||||
// console.log("[RESPONSE >>]", JSON.stringify(response, null, 2));
|
||||
setData(response.data);
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await apiUnblock({ id: String(id) });
|
||||
router.back();
|
||||
} catch (error) {
|
||||
console.log("[ERROR >>]", JSON.stringify(error, null, 2));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<NewWrapper
|
||||
footerComponent={
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
AlertDefaultSystem({
|
||||
title: "Buka Blokir",
|
||||
message: "Apakah anda yakin ingin membuka blokir ini?",
|
||||
textLeft: "Tidak",
|
||||
textRight: "Ya",
|
||||
onPressRight: () => {
|
||||
handleSubmit();
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Buka Blokir
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
}
|
||||
>
|
||||
<BoxWithHeaderSection>
|
||||
<StackCustom>
|
||||
<AvatarUsernameAndOtherComponent
|
||||
avatarHref={`/profile/${data?.blocked?.Profile?.id}`}
|
||||
avatar={data?.blocked?.Profile?.imageId}
|
||||
name={data?.blocked?.username}
|
||||
/>
|
||||
|
||||
<TextCustom align="center">
|
||||
Jika anda membuka blokir ini maka semua postingan terkait user ini
|
||||
akan muncul kembali di beranda
|
||||
<TextCustom bold color="red">
|
||||
{" "}
|
||||
{_.upperCase(data?.menuFeature?.name)}
|
||||
</TextCustom>
|
||||
</TextCustom>
|
||||
</StackCustom>
|
||||
</BoxWithHeaderSection>
|
||||
</NewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -64,14 +64,18 @@ export default function Profile() {
|
||||
};
|
||||
|
||||
const onLoadPortofolio = async (id: string) => {
|
||||
const response = await apiGetPortofolio({ id: id });
|
||||
const lastTwoByDate = response.data
|
||||
.sort(
|
||||
(a: any, b: any) =>
|
||||
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
||||
) // urut desc
|
||||
.slice(0, 2);
|
||||
setListPortofolio(lastTwoByDate);
|
||||
try {
|
||||
const response = await apiGetPortofolio({ id: id });
|
||||
const lastTwoByDate = response.data
|
||||
.sort(
|
||||
(a: any, b: any) =>
|
||||
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
||||
) // urut desc
|
||||
.slice(0, 2);
|
||||
setListPortofolio(lastTwoByDate);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -33,6 +33,16 @@ export default function ProfileLayout() {
|
||||
name="create"
|
||||
options={{ title: "Buat Profile", headerBackVisible: false }}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="[id]/blocked-list"
|
||||
options={{ title: "Blocked List", headerLeft: () => <BackButton /> }}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="[id]/detail-blocked"
|
||||
options={{ title: "Detail Blokir", headerLeft: () => <BackButton /> }}
|
||||
/>
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import Voting_BoxPublishSection from "@/screens/Voting/BoxPublishSection";
|
||||
import { apiVotingGetAll } from "@/service/api-client/api-voting";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
@@ -13,6 +14,7 @@ import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function VotingBeranda() {
|
||||
const { user } = useAuth();
|
||||
const [listData, setListData] = useState<any>([]);
|
||||
const [loadingGetData, setLoadingGetData] = useState(false);
|
||||
const [search, setSearch] = useState("");
|
||||
@@ -29,6 +31,7 @@ export default function VotingBeranda() {
|
||||
const response = await apiVotingGetAll({
|
||||
search,
|
||||
category: "beranda",
|
||||
userLoginId: user?.id,
|
||||
});
|
||||
if (response.success) {
|
||||
setListData(response.data);
|
||||
|
||||
@@ -134,7 +134,7 @@ export default function VotingDetailStatus() {
|
||||
|
||||
{data &&
|
||||
data?.catatan &&
|
||||
(status === "draft" || status === "rejected") && (
|
||||
(status === "draft" || status === "reject") && (
|
||||
<ReportBox text={data?.catatan} />
|
||||
)}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
InformationBox,
|
||||
NewWrapper,
|
||||
StackCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
@@ -12,6 +13,7 @@ import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiUser } from "@/service/api-client/api-user";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { router } from "expo-router";
|
||||
import { RefreshControl } from "react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function WaitingRoom() {
|
||||
@@ -33,7 +35,7 @@ export default function WaitingRoom() {
|
||||
} else {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Akun anda telah aktif", // text2: "Anda berhasil login",
|
||||
text1: "Selamat ! Akun anda telah aktif", // text2: "Anda berhasil login",
|
||||
});
|
||||
router.replace(`/(application)/(user)/profile/create`);
|
||||
}
|
||||
@@ -82,10 +84,18 @@ export default function WaitingRoom() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper footerComponent={logoutButton()}>
|
||||
<NewWrapper
|
||||
footerComponent={logoutButton()}
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={isLoading} onRefresh={handleCheck} />
|
||||
}
|
||||
>
|
||||
<StackCustom>
|
||||
<InformationBox text="Permohonan akses Anda sedang dalam proses verifikasi oleh admin. Harap tunggu, Anda akan menerima pemberitahuan melalui Whatsapp setelah disetujui." />
|
||||
<ButtonCenteredOnly
|
||||
<InformationBox
|
||||
text="Akun Anda sedang menunggu aktivasi.
|
||||
Silakan tunggu beberapa saat. Untuk memperbarui status, tarik layar ke bawah."
|
||||
/>
|
||||
{/* <ButtonCenteredOnly
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
handleCheck();
|
||||
@@ -93,9 +103,9 @@ export default function WaitingRoom() {
|
||||
icon="refresh-ccw"
|
||||
>
|
||||
Check
|
||||
</ButtonCenteredOnly>
|
||||
</ButtonCenteredOnly> */}
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
</NewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ export default function TermsAgreement() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Terms Agreement",
|
||||
title: "Terms & Conditions",
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper footerComponent={footerComponent}>
|
||||
@@ -87,6 +87,7 @@ export default function TermsAgreement() {
|
||||
alignItems: "center",
|
||||
marginTop: 16,
|
||||
marginBottom: 16,
|
||||
paddingInline: 10,
|
||||
}}
|
||||
>
|
||||
<CheckboxCustom value={term} onChange={() => setTerm(!term)} />
|
||||
|
||||
@@ -32,9 +32,10 @@ const FloatingButton: React.FC<FloatingButtonProps> = ({
|
||||
const styles = StyleSheet.create({
|
||||
fab: {
|
||||
position: "absolute",
|
||||
margin: 16,
|
||||
margin: "auto",
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
// bottom: 10,
|
||||
top: -20,
|
||||
backgroundColor: AccentColor.softblue, // Warna Twitter biru
|
||||
borderRadius: 50,
|
||||
borderColor: AccentColor.blue,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AccentColor, MainColor } from "@/constants/color-palet";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
@@ -6,7 +7,9 @@ import {
|
||||
Text,
|
||||
View,
|
||||
ViewStyle,
|
||||
useColorScheme,
|
||||
} from "react-native";
|
||||
import { PlaceholderColor } from "@/constants/color-palet";
|
||||
|
||||
type IconType = React.ReactNode | string;
|
||||
|
||||
@@ -48,7 +51,7 @@ const TextAreaCustom: React.FC<TextAreaCustomProps> = ({
|
||||
minRows = 4,
|
||||
maxRows = 6,
|
||||
showCount = false,
|
||||
maxLength,
|
||||
maxLength = 1000,
|
||||
value,
|
||||
onChangeText,
|
||||
height = 100,
|
||||
@@ -78,6 +81,9 @@ const TextAreaCustom: React.FC<TextAreaCustomProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
const colorScheme = useColorScheme();
|
||||
const theme = PlaceholderColor[colorScheme || "light"];
|
||||
|
||||
return (
|
||||
<View style={[GStyles.inputContainerArea]}>
|
||||
{label && (
|
||||
@@ -109,6 +115,7 @@ const TextAreaCustom: React.FC<TextAreaCustomProps> = ({
|
||||
GStyles.textAreaInput,
|
||||
{ color: fontColor },
|
||||
]}
|
||||
placeholderTextColor={theme.placeholder}
|
||||
editable={!disabled}
|
||||
value={value as string}
|
||||
onChangeText={onChangeText}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { PlaceholderColor } from "@/constants/color-palet";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import Ionicons from "@expo/vector-icons/Ionicons";
|
||||
import React, { useState } from "react";
|
||||
@@ -8,8 +9,10 @@ import {
|
||||
TouchableOpacity,
|
||||
View,
|
||||
ViewStyle,
|
||||
useColorScheme
|
||||
} from "react-native";
|
||||
|
||||
|
||||
type IconType = React.ReactNode | string;
|
||||
|
||||
type Props = {
|
||||
@@ -74,6 +77,9 @@ const TextInputCustom = ({
|
||||
}
|
||||
};
|
||||
|
||||
const colorScheme = useColorScheme();
|
||||
const theme = PlaceholderColor[colorScheme || "light"];
|
||||
|
||||
return (
|
||||
<View style={[GStyles.inputContainerArea, containerStyle]}>
|
||||
{label && (
|
||||
@@ -100,12 +106,14 @@ const TextInputCustom = ({
|
||||
{ color: fontColor },
|
||||
disabled && GStyles.inputPlaceholderDisabled, // <-- placeholder saat disabled
|
||||
]}
|
||||
placeholderTextColor={theme.placeholder}
|
||||
editable={!disabled}
|
||||
secureTextEntry={secureTextEntry && !isPasswordVisible}
|
||||
keyboardType={keyboardType}
|
||||
onChangeText={handleTextChange}
|
||||
maxLength={maxLength}
|
||||
{...rest}
|
||||
|
||||
/>
|
||||
{secureTextEntry && (
|
||||
<TouchableOpacity
|
||||
|
||||
20
components/_ShareComponent/ListEmptyComponent.tsx
Normal file
20
components/_ShareComponent/ListEmptyComponent.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { View } from "react-native";
|
||||
import TextCustom from "../Text/TextCustom";
|
||||
|
||||
// Komponen Empty
|
||||
const ListEmptyComponent = ({ search }: { search?: string }) => (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: 20,
|
||||
}}
|
||||
>
|
||||
<TextCustom align="center" color="gray">
|
||||
{search ? "Tidak ada hasil pencarian" : "Tidak ada data"}
|
||||
</TextCustom>
|
||||
</View>
|
||||
);
|
||||
|
||||
export default ListEmptyComponent;
|
||||
11
components/_ShareComponent/ListLoaderFooterComponent.tsx
Normal file
11
components/_ShareComponent/ListLoaderFooterComponent.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { View } from "react-native";
|
||||
import LoaderCustom from "../Loader/LoaderCustom";
|
||||
|
||||
const ListLoaderFooterComponent = () =>(
|
||||
<View style={{ paddingVertical: 16, alignItems: "center" }}>
|
||||
<LoaderCustom />
|
||||
</View>
|
||||
)
|
||||
|
||||
|
||||
export default ListLoaderFooterComponent;
|
||||
21
components/_ShareComponent/ListSkeletonComponent.tsx
Normal file
21
components/_ShareComponent/ListSkeletonComponent.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { View } from "react-native";
|
||||
import StackCustom from "../Stack/StackCustom";
|
||||
import SkeletonCustom from "./SkeletonCustom";
|
||||
|
||||
const ListSkeletonComponent = ({
|
||||
length = 5,
|
||||
height = 100,
|
||||
}: {
|
||||
length?: number;
|
||||
height?: number;
|
||||
}) => (
|
||||
<View style={{ flex: 1 }}>
|
||||
<StackCustom>
|
||||
{Array.from({ length }).map((_, i) => (
|
||||
<SkeletonCustom height={height} key={i} />
|
||||
))}
|
||||
</StackCustom>
|
||||
</View>
|
||||
);
|
||||
|
||||
export default ListSkeletonComponent;
|
||||
@@ -40,8 +40,8 @@ interface StaticModeProps extends BaseProps {
|
||||
|
||||
interface ListModeProps extends BaseProps {
|
||||
children?: never;
|
||||
listData: any[];
|
||||
renderItem: FlatListProps<any>["renderItem"];
|
||||
listData?: any[];
|
||||
renderItem?: FlatListProps<any>["renderItem"];
|
||||
onEndReached?: () => void;
|
||||
// ✅ Gunakan tipe yang kompatibel dengan FlatList
|
||||
ListHeaderComponent?: React.ReactElement | null;
|
||||
|
||||
@@ -59,6 +59,7 @@ import ViewWrapper from "./_ShareComponent/ViewWrapper";
|
||||
import SearchInput from "./_ShareComponent/SearchInput";
|
||||
import DummyLandscapeImage from "./_ShareComponent/DummyLandscapeImage";
|
||||
import GridComponentView from "./_ShareComponent/GridSectionView";
|
||||
import NewWrapper from "./_ShareComponent/NewWrapper";
|
||||
// Progress
|
||||
import ProgressCustom from "./Progress/ProgressCustom";
|
||||
// Loader
|
||||
@@ -119,6 +120,7 @@ export {
|
||||
DummyLandscapeImage,
|
||||
GridComponentView,
|
||||
Spacing,
|
||||
NewWrapper,
|
||||
// Stack
|
||||
StackCustom,
|
||||
TabBarBackground,
|
||||
|
||||
@@ -45,3 +45,23 @@ export const AdminColor = {
|
||||
// Warna Asli: #002e59
|
||||
// Warna Lebih Gelap: #001f3b
|
||||
// Warna Tergelap: #001323
|
||||
|
||||
|
||||
export const PlaceholderColor = {
|
||||
light: {
|
||||
text: "#000",
|
||||
placeholder: "#666",
|
||||
border: "#ccc",
|
||||
background: "#fff",
|
||||
error: "#d00",
|
||||
icon: "#555",
|
||||
},
|
||||
dark: {
|
||||
text: "#fff",
|
||||
placeholder: "#aaa",
|
||||
border: "#444",
|
||||
background: "#1a1a1a",
|
||||
error: "#ff4d4d",
|
||||
icon: "#ccc",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -73,7 +73,22 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await apiLogin({ nomor: nomor });
|
||||
await AsyncStorage.setItem("kode_otp", response.kodeId);
|
||||
console.log("[RESPONSE AUTH]", JSON.stringify(response));
|
||||
|
||||
if (response.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Sukses",
|
||||
text2: "Kode OTP berhasil dikirim",
|
||||
});
|
||||
|
||||
await AsyncStorage.setItem("kode_otp", response.kodeId);
|
||||
router.replace(`/verification?nomor=${nomor}`);
|
||||
return;
|
||||
} else {
|
||||
router.replace(`/register?nomor=${nomor}`);
|
||||
return;
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response?.data?.message || "Gagal kirim OTP");
|
||||
} finally {
|
||||
@@ -81,13 +96,26 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
}
|
||||
};
|
||||
|
||||
// const loginWithNomor = async (nomor: string) => {
|
||||
// setIsLoading(true);
|
||||
// try {
|
||||
// const response = await apiLogin({ nomor: nomor });
|
||||
// await AsyncStorage.setItem("kode_otp", response.kodeId);
|
||||
// } catch (error: any) {
|
||||
// throw new Error(error.response?.data?.message || "Gagal kirim OTP");
|
||||
// } finally {
|
||||
// setIsLoading(false);
|
||||
// }
|
||||
// };
|
||||
|
||||
// --- 2. Validasi OTP & cek user ---
|
||||
const validateOtp = async (nomor: string) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await apiValidationCode({ nomor: nomor });
|
||||
|
||||
const { token } = response;
|
||||
console.log("[RESPONSE VALIDASI OTP]", JSON.stringify(response, null, 2));
|
||||
|
||||
if (response.success) {
|
||||
setToken(token);
|
||||
await AsyncStorage.setItem("authToken", token);
|
||||
@@ -104,20 +132,23 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
|
||||
if (response.active) {
|
||||
if (response.roleId === "1") {
|
||||
return "/(application)/(user)/home";
|
||||
router.replace("/(application)/(user)/home");
|
||||
return;
|
||||
} else {
|
||||
return "/(application)/admin/dashboard";
|
||||
router.replace("/(application)/admin/dashboard");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return "/(application)/(user)/waiting-room";
|
||||
router.replace("/(application)/(user)/waiting-room");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
Toast.show({
|
||||
type: "info",
|
||||
text1: "Anda belum terdaftar",
|
||||
text2: "Silahkan daftar terlebih dahulu",
|
||||
text1: "Terjadi kesalahan",
|
||||
text2: "Silahkan coba lagi",
|
||||
});
|
||||
return `/register?nomor=${nomor}`;
|
||||
return;
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.log("Error validasi otp >>", (error as Error).message || error);
|
||||
@@ -132,6 +163,10 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
// --- 3. Ambil data user ---
|
||||
const userData = async (token: string) => {
|
||||
try {
|
||||
if (!token) {
|
||||
throw new Error("Token tidak ditemukan");
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
const response = await apiConfig.get(`/mobile?token=${token}`, {
|
||||
headers: {
|
||||
@@ -145,7 +180,10 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
await AsyncStorage.setItem("userData", JSON.stringify(dataUser));
|
||||
return dataUser;
|
||||
} catch (error: any) {
|
||||
console.log(error.response?.data?.message + "user" || "Gagal mengambil data user");
|
||||
console.log(
|
||||
"[LOAD USER DATA]",
|
||||
error.response?.data?.message + "user" || "Gagal mengambil data user"
|
||||
);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -160,9 +198,8 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await apiRegister({ data: userData });
|
||||
console.log("response", response);
|
||||
console.log("[REGISTER FETCH]", JSON.stringify(response, null, 2));
|
||||
|
||||
const { token } = response;
|
||||
if (!response.success) {
|
||||
Toast.show({
|
||||
type: "info",
|
||||
@@ -173,23 +210,63 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
return;
|
||||
}
|
||||
|
||||
setToken(token);
|
||||
await AsyncStorage.setItem("authToken", token);
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Sukses",
|
||||
text2: "Anda berhasil terdaftar",
|
||||
});
|
||||
router.replace("/(application)/(user)/waiting-room");
|
||||
router.replace(`/verification?nomor=${userData.nomor}`);
|
||||
return;
|
||||
} catch (error: any) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Error",
|
||||
text2: error.response?.data?.message || "Gagal mendaftar",
|
||||
});
|
||||
console.log("Error register", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
// const registerUser = async (userData: {
|
||||
// username: string;
|
||||
// nomor: string;
|
||||
// termsOfServiceAccepted: boolean;
|
||||
// }) => {
|
||||
// setIsLoading(true);
|
||||
// try {
|
||||
// const response = await apiRegister({ data: userData });
|
||||
// console.log("response", response);
|
||||
|
||||
// const { token } = response;
|
||||
// if (!response.success) {
|
||||
// Toast.show({
|
||||
// type: "info",
|
||||
// text1: "Info",
|
||||
// text2: response.message,
|
||||
// });
|
||||
|
||||
// return;
|
||||
// }
|
||||
|
||||
// setToken(token);
|
||||
// await AsyncStorage.setItem("authToken", token);
|
||||
// Toast.show({
|
||||
// type: "success",
|
||||
// text1: "Sukses",
|
||||
// text2: "Anda berhasil terdaftar",
|
||||
// });
|
||||
// router.replace("/(application)/(user)/waiting-room");
|
||||
// return;
|
||||
// } catch (error: any) {
|
||||
// console.log("Error register", error);
|
||||
// } finally {
|
||||
// setIsLoading(false);
|
||||
// }
|
||||
// };
|
||||
|
||||
// --- 5. Logout ---
|
||||
|
||||
const logout = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
97
hooks/use-paginated-api.ts
Normal file
97
hooks/use-paginated-api.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
// @/hooks/use-paginated-api.ts
|
||||
import { useCallback, useState, useEffect, useRef } from "react";
|
||||
|
||||
interface UsePaginatedApiProps {
|
||||
fetcher: (params: { page: number; search?: string }) => Promise<any[]>;
|
||||
initialSearch?: string; // mengatur nilai awal dari state
|
||||
pageSize?: number;
|
||||
dependencies?: any[]; // untuk refresh saat deps berubah (misal: user.id)
|
||||
}
|
||||
|
||||
export const usePaginatedApi = ({
|
||||
fetcher,
|
||||
initialSearch = "",
|
||||
pageSize = 5,
|
||||
dependencies = [],
|
||||
}: UsePaginatedApiProps) => {
|
||||
const [data, setData] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [hasMore, setHasMore] = useState(true);
|
||||
const [page, setPage] = useState(1);
|
||||
const [search, setSearch] = useState(initialSearch);
|
||||
const refreshingRef = useRef<boolean>(false);
|
||||
const loadingRef = useRef<boolean>(false);
|
||||
const fetchRef = useRef(false);
|
||||
|
||||
const fetchData = useCallback(
|
||||
async (pageNumber: number, clear: boolean) => {
|
||||
const isRefresh = clear;
|
||||
|
||||
// 🔒 Proteksi: jangan jalankan jika sedang refreshing (untuk refresh)
|
||||
if (isRefresh && refreshingRef.current) return;
|
||||
// 🔒 Proteksi: jangan jalankan loadMore jika sedang loading
|
||||
if (!isRefresh && loadingRef.current) return;
|
||||
|
||||
const setLoadingState = (isLoading: boolean) => {
|
||||
if (isRefresh) {
|
||||
setRefreshing(isLoading);
|
||||
refreshingRef.current = isLoading;
|
||||
} else {
|
||||
setLoading(isLoading);
|
||||
loadingRef.current = isLoading;
|
||||
}
|
||||
};
|
||||
|
||||
setLoadingState(true);
|
||||
|
||||
try {
|
||||
const newData = await fetcher({ page: pageNumber, search });
|
||||
setData((prev) => {
|
||||
const current = Array.isArray(prev) ? prev : [];
|
||||
return clear ? newData : [...current, ...newData];
|
||||
});
|
||||
setHasMore(newData.length === pageSize);
|
||||
setPage(pageNumber);
|
||||
} catch (error) {
|
||||
console.error("[usePaginatedApi] Error:", error);
|
||||
setHasMore(false);
|
||||
} finally {
|
||||
setLoadingState(false);
|
||||
}
|
||||
},
|
||||
[search, hasMore, pageSize, ...dependencies]
|
||||
);
|
||||
|
||||
const onRefresh = useCallback(() => {
|
||||
fetchData(1, true);
|
||||
}, [fetchData]);
|
||||
|
||||
const loadMore = useCallback(() => {
|
||||
if (hasMore && !loading && !refreshing) {
|
||||
fetchData(page + 1, false);
|
||||
}
|
||||
}, [hasMore, loading, refreshing, page, fetchData]);
|
||||
|
||||
// Reset & fetch ulang saat search atau deps berubah
|
||||
useEffect(() => {
|
||||
if (fetchRef.current) return; // hindari double initial
|
||||
fetchRef.current = true;
|
||||
|
||||
setPage(1);
|
||||
setData([]);
|
||||
setHasMore(true);
|
||||
fetchData(1, true);
|
||||
}, [search, ...dependencies]);
|
||||
|
||||
return {
|
||||
data,
|
||||
loading,
|
||||
refreshing,
|
||||
hasMore,
|
||||
search,
|
||||
setSearch,
|
||||
onRefresh,
|
||||
loadMore,
|
||||
};
|
||||
};
|
||||
@@ -401,7 +401,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = HIPMIBadungConnect/HIPMIBadungConnect.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = BMY6GT6W3D;
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
@@ -422,7 +422,7 @@
|
||||
);
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.anonymous.hipmi-mobile";
|
||||
PRODUCT_NAME = HIPMIBadungConnect;
|
||||
PRODUCT_NAME = "HIPMIBadungConnect";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "HIPMIBadungConnect/HIPMIBadungConnect-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -438,7 +438,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = HIPMIBadungConnect/HIPMIBadungConnect.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = BMY6GT6W3D;
|
||||
INFOPLIST_FILE = HIPMIBadungConnect/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||
@@ -454,7 +454,7 @@
|
||||
);
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.anonymous.hipmi-mobile";
|
||||
PRODUCT_NAME = HIPMIBadungConnect;
|
||||
PRODUCT_NAME = "HIPMIBadungConnect";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "HIPMIBadungConnect/HIPMIBadungConnect-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>8</string>
|
||||
<string>10</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
@@ -53,15 +53,18 @@
|
||||
<key>NSAllowsLocalNetworking</key>
|
||||
<true/>
|
||||
</dict>
|
||||
|
||||
<!-- Photo Library -->
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Allow $(PRODUCT_NAME) to access your camera</string>
|
||||
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
||||
<string>Allow $(PRODUCT_NAME) to access your location</string>
|
||||
<key>NSLocationAlwaysUsageDescription</key>
|
||||
<string>Allow $(PRODUCT_NAME) to access your location</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>Aplikasi membutuhkan akses lokasi untuk menampilkan peta.</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Allow $(PRODUCT_NAME) to access your microphone</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>Untuk mengunggah dokumen dan media bisnis seperti foto profil, logo usaha, poster lowongan, atau bukti transaksi di berbagai fitur aplikasi: Profile, Portofolio, Job Vacancy, Investasi, dan Donasi.</string>
|
||||
|
||||
<!-- Camera -->
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Untuk mengambil foto langsung saat mengunggah dokumen bisnis seperti foto profil, logo usaha, poster, atau bukti pembayaran di fitur Profile, Portofolio, Job Vacancy, Investasi, dan Donasi.</string>
|
||||
|
||||
<key>NSUserActivityTypes</key>
|
||||
<array>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
|
||||
|
||||
10
ios/Podfile
10
ios/Podfile
@@ -33,6 +33,13 @@ target 'HIPMIBadungConnect' do
|
||||
use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks']
|
||||
use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS']
|
||||
|
||||
# @generated begin pre_installer - expo prebuild (DO NOT MODIFY) sync-c8812095000d6054b846ce74840f0ffb540c2757
|
||||
pre_install do |installer|
|
||||
# @generated begin @rnmapbox/maps-pre_installer - expo prebuild (DO NOT MODIFY) sync-ea4905840bf9fcea0acc62e92aa2e784f9d760f8
|
||||
$RNMapboxMaps.pre_install(installer)
|
||||
# @generated end @rnmapbox/maps-pre_installer
|
||||
end
|
||||
# @generated end pre_installer
|
||||
use_react_native!(
|
||||
:path => config[:reactNativePath],
|
||||
:hermes_enabled => podfile_properties['expo.jsEngine'] == nil || podfile_properties['expo.jsEngine'] == 'hermes',
|
||||
@@ -42,6 +49,9 @@ target 'HIPMIBadungConnect' do
|
||||
)
|
||||
|
||||
post_install do |installer|
|
||||
# @generated begin @rnmapbox/maps-post_installer - expo prebuild (DO NOT MODIFY) sync-c4e8f90e96f6b6c6ea9241dd7b52ab5f57f7bf36
|
||||
$RNMapboxMaps.post_install(installer)
|
||||
# @generated end @rnmapbox/maps-post_installer
|
||||
react_native_post_install(
|
||||
installer,
|
||||
config[:reactNativePath],
|
||||
|
||||
@@ -65,22 +65,18 @@ export default function LoginView() {
|
||||
const isValid = await validateData();
|
||||
if (!isValid) return;
|
||||
|
||||
// const callingCode = selectedCountry?.callingCode.replace(/^\+/, "") || "";
|
||||
// const fixNumber = inputValue.replace(/\s+/g, "");
|
||||
const callingCode = selectedCountry?.callingCode.replace(/^\+/, "") || "";
|
||||
const fixNumber = inputValue.replace(/\s+/g, "");
|
||||
let fixNumber = inputValue.replace(/\s+/g, "").replace(/^0+/, "");
|
||||
|
||||
const realNumber = callingCode + fixNumber;
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
// const response = await apiLogin({ nomor: realNumber });
|
||||
await loginWithNomor(realNumber);
|
||||
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Sukses",
|
||||
text2: "Kode OTP berhasil dikirim",
|
||||
});
|
||||
|
||||
router.navigate(`/verification?nomor=${realNumber}`);
|
||||
const response = await loginWithNomor(realNumber);
|
||||
console.log("[RESPONSE UI]", response);
|
||||
|
||||
} catch (error) {
|
||||
console.log("Error login", error);
|
||||
Toast.show({
|
||||
@@ -91,6 +87,30 @@ export default function LoginView() {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
// try {
|
||||
// setLoading(true);
|
||||
// // const response = await apiLogin({ nomor: realNumber });
|
||||
// const response = await loginWithNomor(realNumber);
|
||||
// console.log("[RESPONSE]", response);
|
||||
|
||||
// Toast.show({
|
||||
// type: "success",
|
||||
// text1: "Sukses",
|
||||
// text2: "Kode OTP berhasil dikirim",
|
||||
// });
|
||||
|
||||
// // router.navigate(`/verification?nomor=${realNumber}`);
|
||||
// } catch (error) {
|
||||
// console.log("Error login", error);
|
||||
// Toast.show({
|
||||
// type: "error",
|
||||
// text1: "Error",
|
||||
// text2: error as string,
|
||||
// });
|
||||
// } finally {
|
||||
// setLoading(false);
|
||||
// }
|
||||
}
|
||||
|
||||
if (token && token !== "" && !isUserActive) {
|
||||
|
||||
@@ -89,8 +89,9 @@ export default function VerificationView() {
|
||||
// ✅ VERIFIKASI OTOMATIS UNTUK APPLE REVIEW
|
||||
if (inputOtp === "1234") {
|
||||
try {
|
||||
const response = await validateOtp(nomor as string);
|
||||
router.replace(response);
|
||||
await validateOtp(nomor as string);
|
||||
|
||||
return;
|
||||
} catch (error) {
|
||||
console.log("Error verification", error);
|
||||
Toast.show({ type: "error", text1: "Gagal verifikasi" });
|
||||
@@ -103,16 +104,8 @@ export default function VerificationView() {
|
||||
|
||||
// 🔁 VERIFIKASI NORMAL (untuk pengguna sungguhan)
|
||||
try {
|
||||
const response = await validateOtp(nomor as string);
|
||||
// registerForPushNotificationsAsync().then((token) => {
|
||||
// if (token) {
|
||||
// console.log("Expo Push Token:", token);
|
||||
// // TODO: Kirim token ke backend kamu
|
||||
// } else {
|
||||
// console.log("Failed to get Expo Push Token");
|
||||
// }
|
||||
// });
|
||||
router.replace(response);
|
||||
await validateOtp(nomor as string);
|
||||
return
|
||||
} catch (error) {
|
||||
console.log("Error verification", error);
|
||||
Toast.show({ type: "error", text1: "Gagal verifikasi" });
|
||||
|
||||
@@ -203,10 +203,9 @@ export default function Forum_ViewBeranda2() {
|
||||
/>
|
||||
}
|
||||
onEndReached={loadMore}
|
||||
// ListHeaderComponent={ListHeaderComponent}
|
||||
ListFooterComponent={ListFooterComponent}
|
||||
ListEmptyComponent={
|
||||
_.isEmpty(listData) ? <SkeletonListComponent /> : <EmptyComponent />
|
||||
loading && _.isEmpty(listData) ? <SkeletonListComponent /> : <EmptyComponent />
|
||||
}
|
||||
// ------------------------
|
||||
/>
|
||||
|
||||
@@ -207,7 +207,7 @@ export default function View_Forumku2() {
|
||||
ListHeaderComponent={randerHeaderComponent()}
|
||||
ListFooterComponent={ListFooterComponent}
|
||||
ListEmptyComponent={
|
||||
_.isEmpty(listData) ? <SkeletonListComponent /> : <EmptyComponent />
|
||||
loading && _.isEmpty(listData) ? <SkeletonListComponent /> : <EmptyComponent />
|
||||
}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
Grid,
|
||||
ProgressCustom,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
BaseBox,
|
||||
Grid,
|
||||
ProgressCustom,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import API_STRORAGE from "@/constants/base-url-api-strorage";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import DUMMY_IMAGE from "@/constants/dummy-image-value";
|
||||
import { countDownAndCondition } from "@/utils/countDownAndCondition";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
@@ -21,7 +22,7 @@ export default function Investment_BoxBerandaSection({
|
||||
id: string;
|
||||
data: any;
|
||||
}) {
|
||||
// console.log("[DATA By one]", JSON.stringify(data, null, 2));
|
||||
// console.log("[DATA By one]", JSON.stringify(data, null, 2));
|
||||
|
||||
const [value, setValue] = useState({
|
||||
sisa: 0,
|
||||
@@ -32,6 +33,8 @@ export default function Investment_BoxBerandaSection({
|
||||
updateCountDown();
|
||||
}, [data]);
|
||||
|
||||
console.log("[DATA BERANDA]", JSON.stringify(data, null, 2));
|
||||
|
||||
const updateCountDown = () => {
|
||||
const countDown = countDownAndCondition({
|
||||
duration: data?.pencarianInvestor,
|
||||
@@ -66,8 +69,10 @@ export default function Investment_BoxBerandaSection({
|
||||
<TextCustom truncate={2}>{data.title}</TextCustom>
|
||||
<ProgressCustom
|
||||
label={`${data.progress}%`}
|
||||
value={data.progress}
|
||||
value={Number(data.progress)}
|
||||
size="lg"
|
||||
animated
|
||||
color="primary"
|
||||
/>
|
||||
{value.reminder ? (
|
||||
<View
|
||||
@@ -79,13 +84,11 @@ export default function Investment_BoxBerandaSection({
|
||||
>
|
||||
<Ionicons name="alert-circle-outline" size={16} color="red" />
|
||||
<TextCustom truncate color="red" size="small">
|
||||
Periode Investasi Berakhir
|
||||
Periode Berakhir
|
||||
</TextCustom>
|
||||
</View>
|
||||
) : (
|
||||
<TextCustom>
|
||||
Sisa waktu: {value.sisa} hari
|
||||
</TextCustom>
|
||||
<TextCustom>Sisa waktu: {value.sisa} hari</TextCustom>
|
||||
)}
|
||||
</StackCustom>
|
||||
</Grid.Col>
|
||||
|
||||
@@ -4,9 +4,11 @@ import { router } from "expo-router";
|
||||
export default function Investment_ButtonInvestasiSection({
|
||||
id,
|
||||
isMine,
|
||||
reminder,
|
||||
}: {
|
||||
id: string;
|
||||
isMine: boolean;
|
||||
reminder: boolean;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
@@ -14,11 +16,12 @@ export default function Investment_ButtonInvestasiSection({
|
||||
<ButtonCustom disabled>Investasi ini milik Anda</ButtonCustom>
|
||||
) : (
|
||||
<ButtonCustom
|
||||
disabled={reminder}
|
||||
onPress={() => {
|
||||
router.navigate(`/investment/${id}/(transaction-flow)`);
|
||||
}}
|
||||
>
|
||||
Beli Saham
|
||||
{reminder ? "Periode Investasi Berakhir" : "Beli Saham"}
|
||||
</ButtonCustom>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -52,7 +52,7 @@ export default function Invesment_DetailDataPublishSection({
|
||||
<ReportBox text={data?.catatan} />
|
||||
)}
|
||||
<Invesment_BoxProgressSection
|
||||
progress={data?.progress}
|
||||
progress={Number(data?.progress)}
|
||||
status={status as string}
|
||||
/>
|
||||
<Invesment_BoxDetailDataSection
|
||||
|
||||
@@ -5,16 +5,6 @@ import { Ionicons } from "@expo/vector-icons";
|
||||
|
||||
export default function Portofolio_SocialMediaSection({ data }: { data: any }) {
|
||||
const listData = [
|
||||
{
|
||||
label: data && data?.facebook ? data.facebook : "-",
|
||||
icon: (
|
||||
<Ionicons
|
||||
name="logo-facebook"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color={MainColor.white}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: data && data?.tiktok ? data.tiktok : "-",
|
||||
icon: (
|
||||
@@ -35,6 +25,16 @@ export default function Portofolio_SocialMediaSection({ data }: { data: any }) {
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: data && data?.facebook ? data.facebook : "-",
|
||||
icon: (
|
||||
<Ionicons
|
||||
name="logo-facebook"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color={MainColor.white}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: data && data?.twitter ? data.twitter : "-",
|
||||
icon: (
|
||||
|
||||
@@ -62,6 +62,18 @@ export const drawerItemsProfile = ({
|
||||
path: `/(application)/portofolio/${id}/create`,
|
||||
value: "create-portofolio",
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<Ionicons
|
||||
name="list-circle"
|
||||
size={ICON_SIZE_MEDIUM}
|
||||
color={AccentColor.white}
|
||||
/>
|
||||
),
|
||||
label: "Blocked List",
|
||||
path: `/(application)/profile/${id}/blocked-list`,
|
||||
value: "blocked-list",
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<Ionicons
|
||||
@@ -150,6 +162,18 @@ export const drawerItemsProfile = ({
|
||||
label: "Tambah portofolio",
|
||||
path: `/(application)/portofolio/${id}/create`,
|
||||
value: "create-portofolio",
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<Ionicons
|
||||
name="list-circle"
|
||||
size={ICON_SIZE_MEDIUM}
|
||||
color={AccentColor.white}
|
||||
/>
|
||||
),
|
||||
label: "Blocked List",
|
||||
path: `/(application)/profile/${id}/blocked-list`,
|
||||
value: "blocked-list",
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
|
||||
@@ -22,9 +22,11 @@ export default function Voting_BoxDetailHasilVotingSection({
|
||||
<Grid>
|
||||
{listData?.map((item: any, i: number) => (
|
||||
<Grid.Col span={12 / listData?.length} style={{ alignItems: "center" }} key={i}>
|
||||
<StackCustom>
|
||||
<StackCustom style={{
|
||||
alignItems: "center",
|
||||
}}>
|
||||
<CircleContainer value={item?.jumlah} />
|
||||
<TextCustom align="center" size="small">{item?.value}</TextCustom>
|
||||
<TextCustom truncate={2} align="center" size="small">{item?.value}</TextCustom>
|
||||
</StackCustom>
|
||||
</Grid.Col>
|
||||
))}
|
||||
|
||||
45
service/api-client/api-blocked.ts
Normal file
45
service/api-client/api-blocked.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { apiConfig } from "../api-config";
|
||||
|
||||
/**
|
||||
* @param id | Profile ID
|
||||
* @param search | Search Query
|
||||
* @param page | Page Number
|
||||
*/
|
||||
export async function apiGetBlocked({
|
||||
id,
|
||||
search,
|
||||
page,
|
||||
}: {
|
||||
id: string;
|
||||
search?: string;
|
||||
page?: string;
|
||||
}) {
|
||||
const pageQuery = page ? `&page=${page}` : "";
|
||||
const searchQuery = search ? `&search=${search}` : "";
|
||||
try {
|
||||
const response = await apiConfig.get(
|
||||
`/mobile/block-user?id=${id}${pageQuery}${searchQuery}`
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function apiGetBlockedById({ id }: { id: string }) {
|
||||
try {
|
||||
const response = await apiConfig.get(`/mobile/block-user/${id}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function apiUnblock({ id }: { id: string }) {
|
||||
try {
|
||||
const response = await apiConfig.delete(`/mobile/block-user/${id}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -79,12 +79,14 @@ export async function apiVotingUpdateData({
|
||||
}
|
||||
}
|
||||
|
||||
export async function apiVotingGetAll({ search, category, authorId }: { search?: string, category: "beranda" | "contribution" | "all-history" | "my-history", authorId?: string }) {
|
||||
export async function apiVotingGetAll({ search, category, authorId, userLoginId }: { search?: string, category: "beranda" | "contribution" | "all-history" | "my-history", authorId?: string, userLoginId?: string }) {
|
||||
try {
|
||||
console.log("userLoginId", userLoginId);
|
||||
const categoryQuery = category ? `?category=${category}` : "";
|
||||
const searchQuery = search ? `&search=${search}` : "";
|
||||
const authorIdQuery = authorId ? `&authorId=${authorId}` : "";
|
||||
const response = await apiConfig.get(`/mobile/voting${categoryQuery}${searchQuery}${authorIdQuery}`);
|
||||
const userLoginIdQuery = userLoginId ? `&userLoginId=${userLoginId}` : "";
|
||||
const response = await apiConfig.get(`/mobile/voting${categoryQuery}${searchQuery}${authorIdQuery}${userLoginIdQuery}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
|
||||
@@ -20,7 +20,6 @@ apiConfig.interceptors.request.use(
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
// console.log("config", JSON.stringify(config, null, 2));
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
@@ -29,16 +28,15 @@ apiConfig.interceptors.request.use(
|
||||
);
|
||||
|
||||
export async function apiVersion() {
|
||||
// console.log("API_BASE_URL", API_BASE_URL);
|
||||
const response = await apiConfig.get("/version");
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function apiLogin({ nomor }: { nomor: string }) {
|
||||
const response = await apiConfig.post("/auth/login", {
|
||||
const response = await apiConfig.post("/mobile/auth/login", {
|
||||
nomor: nomor,
|
||||
});
|
||||
return response.data;
|
||||
return response.data;;
|
||||
}
|
||||
|
||||
export async function apiCheckCodeOtp({ kodeId }: { kodeId: string }) {
|
||||
@@ -58,7 +56,7 @@ export async function apiRegister({
|
||||
}: {
|
||||
data: { nomor: string; username: string; termsOfServiceAccepted: boolean };
|
||||
}) {
|
||||
const response = await apiConfig.post(`/auth/register`, {
|
||||
const response = await apiConfig.post(`/mobile/auth/register`, {
|
||||
data: data,
|
||||
});
|
||||
return response.data;
|
||||
|
||||
@@ -16,7 +16,7 @@ export const GStyles = StyleSheet.create({
|
||||
// =============== Main Styles =============== //
|
||||
container: {
|
||||
flex: 1,
|
||||
paddingInline: PADDING_LARGE,
|
||||
paddingInline: PADDING_MEDIUM,
|
||||
paddingBlock: PADDING_EXTRA_SMALL,
|
||||
backgroundColor: MainColor.darkblue,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user