Compare commits
8 Commits
fix-reject
...
qc/3-dec-2
| Author | SHA1 | Date | |
|---|---|---|---|
| a93f97ed6a | |||
| 858b441a8c | |||
| 98aaa126a1 | |||
| 69452ff4e7 | |||
| 33ec892ec8 | |||
| 8a900e9469 | |||
| d471682ae7 | |||
| 00eea71248 |
@@ -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: "12",
|
||||
},
|
||||
|
||||
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 })
|
||||
}
|
||||
|
||||
@@ -1,142 +1,12 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AvatarComp,
|
||||
ButtonCustom,
|
||||
CenterCustom,
|
||||
DrawerCustom,
|
||||
FloatingButton,
|
||||
Grid,
|
||||
LoaderCustom,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection";
|
||||
import Forum_MenuDrawerBerandaSection from "@/screens/Forum/MenuDrawerSection.tsx/MenuBeranda";
|
||||
import { apiForumGetAll } from "@/service/api-client/api-forum";
|
||||
import { apiUser } from "@/service/api-client/api-user";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import View_Forumku from "@/screens/Forum/ViewForumku";
|
||||
import View_Forumku2 from "@/screens/Forum/ViewForumku2";
|
||||
|
||||
export default function Forumku() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const { user } = useAuth();
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [status, setStatus] = useState("");
|
||||
const [listData, setListData] = useState<any | null>(null);
|
||||
const [dataUser, setDataUser] = useState<any | null>(null);
|
||||
const [loadingGetList, setLoadingGetList] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
onLoadDataProfile(id as string);
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadDataProfile = async (id: string) => {
|
||||
try {
|
||||
const response = await apiUser(id);
|
||||
|
||||
setDataUser(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
}
|
||||
};
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setLoadingGetList(true);
|
||||
const response = await apiForumGetAll({
|
||||
search: "",
|
||||
authorId: id as string,
|
||||
});
|
||||
|
||||
setListData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadingGetList(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper
|
||||
floatingButton={
|
||||
user?.id === id && (
|
||||
<FloatingButton
|
||||
onPress={() =>
|
||||
router.navigate("/(application)/(user)/forum/create")
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
<StackCustom>
|
||||
<CenterCustom>
|
||||
<AvatarComp
|
||||
fileId={dataUser?.Profile?.imageId}
|
||||
href={`/(application)/(image)/preview-image/${dataUser?.Profile?.imageId}`}
|
||||
size="xl"
|
||||
/>
|
||||
</CenterCustom>
|
||||
|
||||
<Grid>
|
||||
<Grid.Col span={6}>
|
||||
<TextCustom bold truncate>
|
||||
@{dataUser?.username || "-"}
|
||||
</TextCustom>
|
||||
<TextCustom>{listData?.length || "0"} postingan</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6} style={{ alignItems: "flex-end" }}>
|
||||
<ButtonCustom href={`/profile/${dataUser?.Profile?.id}`}>
|
||||
Kunjungi Profile
|
||||
</ButtonCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
{loadingGetList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom> Tidak ada diskusi</TextCustom>
|
||||
) : (
|
||||
<>
|
||||
{listData?.map((item: any, index: number) => (
|
||||
<Forum_BoxDetailSection
|
||||
isRightComponent={false}
|
||||
key={index}
|
||||
data={item}
|
||||
isTruncate={true}
|
||||
href={`/forum/${item.id}`}
|
||||
onSetData={(value) => {
|
||||
setOpenDrawer(value.setOpenDrawer);
|
||||
setStatus(value.setStatus);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
|
||||
{/* Drawer Komponen Eksternal */}
|
||||
<DrawerCustom
|
||||
height={"auto"}
|
||||
isVisible={openDrawer}
|
||||
closeDrawer={() => setOpenDrawer(false)}
|
||||
>
|
||||
<Forum_MenuDrawerBerandaSection
|
||||
id={id as string}
|
||||
status={status}
|
||||
setIsDrawerOpen={() => {
|
||||
setOpenDrawer(false);
|
||||
}}
|
||||
authorId={id as string}
|
||||
/>
|
||||
</DrawerCustom>
|
||||
{/* <View_Forumku /> */}
|
||||
<View_Forumku2 />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -223,6 +223,7 @@ export default function ForumDetail() {
|
||||
>
|
||||
<Forum_MenuDrawerBerandaSection
|
||||
id={dataId}
|
||||
authorUsername={data?.Author?.username as string}
|
||||
status={status}
|
||||
setIsDrawerOpen={() => {
|
||||
setOpenDrawer(false);
|
||||
|
||||
@@ -1,129 +1,12 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AvatarComp,
|
||||
BackButton,
|
||||
DrawerCustom,
|
||||
LoaderCustom,
|
||||
SearchInput,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import FloatingButton from "@/components/Button/FloatingButton";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection";
|
||||
import Forum_MenuDrawerBerandaSection from "@/screens/Forum/MenuDrawerSection.tsx/MenuBeranda";
|
||||
import { apiForumGetAll } from "@/service/api-client/api-forum";
|
||||
import { apiUser } from "@/service/api-client/api-user";
|
||||
import { router, Stack, useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import Forum_ViewBeranda from "@/screens/Forum/ViewBeranda";
|
||||
import Forum_ViewBeranda2 from "@/screens/Forum/ViewBeranda2";
|
||||
|
||||
export default function Forum() {
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [status, setStatus] = useState("");
|
||||
const { user } = useAuth();
|
||||
const [dataUser, setDataUser] = useState<any>();
|
||||
const [listData, setListData] = useState<any[]>();
|
||||
const [loadingGetList, setLoadingGetList] = useState(false);
|
||||
const [search, setSearch] = useState("");
|
||||
const [dataId, setDataId] = useState("");
|
||||
const [authorId, setAuthorId] = useState("");
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
onLoadDataProfile(user?.id as string);
|
||||
}, [user?.id, search])
|
||||
);
|
||||
|
||||
const onLoadDataProfile = async (id: string) => {
|
||||
const response = await apiUser(id);
|
||||
setDataUser(response.data);
|
||||
};
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setLoadingGetList(true);
|
||||
const response = await apiForumGetAll({ search: search });
|
||||
|
||||
setListData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadingGetList(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Forum",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => (
|
||||
<AvatarComp
|
||||
fileId={dataUser?.Profile?.imageId}
|
||||
size="base"
|
||||
href={`/forum/${user?.id}/forumku`}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
<ViewWrapper
|
||||
headerComponent={
|
||||
<SearchInput
|
||||
placeholder="Cari topik diskusi"
|
||||
onChangeText={(e) => setSearch(e)}
|
||||
/>
|
||||
}
|
||||
floatingButton={
|
||||
<FloatingButton
|
||||
onPress={() =>
|
||||
router.navigate("/(application)/(user)/forum/create")
|
||||
}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{loadingGetList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom align="center" color="gray">
|
||||
Tidak ada diskusi
|
||||
</TextCustom>
|
||||
) : (
|
||||
listData?.map((e: any, i: number) => (
|
||||
<Forum_BoxDetailSection
|
||||
key={i}
|
||||
data={e}
|
||||
onSetData={() => {
|
||||
setDataId(e.id);
|
||||
setOpenDrawer(true);
|
||||
setStatus(e.ForumMaster_StatusPosting?.status);
|
||||
setAuthorId(e.Author?.id);
|
||||
}}
|
||||
isTruncate={true}
|
||||
href={`/forum/${e.id}`}
|
||||
isRightComponent={false}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
|
||||
<DrawerCustom
|
||||
height={"auto"}
|
||||
isVisible={openDrawer}
|
||||
closeDrawer={() => setOpenDrawer(false)}
|
||||
>
|
||||
<Forum_MenuDrawerBerandaSection
|
||||
id={dataId}
|
||||
authorId={authorId}
|
||||
status={status}
|
||||
setIsDrawerOpen={() => {
|
||||
setOpenDrawer(false);
|
||||
}}
|
||||
/>
|
||||
</DrawerCustom>
|
||||
{/* <Forum_ViewBeranda /> */}
|
||||
<Forum_ViewBeranda2 />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ import { useEffect, useState } from "react";
|
||||
export default function Application() {
|
||||
const { token, user } = useAuth();
|
||||
const [data, setData] = useState<any>();
|
||||
|
||||
console.log("[User] >>", JSON.stringify(user?.id, null, 2));
|
||||
|
||||
useEffect(() => {
|
||||
onLoadData();
|
||||
|
||||
@@ -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
bun.lock
32
bun.lock
@@ -29,6 +29,7 @@
|
||||
"expo-haptics": "~15.0.7",
|
||||
"expo-image": "~3.0.8",
|
||||
"expo-image-picker": "~17.0.8",
|
||||
"expo-linear-gradient": "~15.0.7",
|
||||
"expo-linking": "~8.0.8",
|
||||
"expo-location": "~19.0.7",
|
||||
"expo-notifications": "^0.32.13",
|
||||
@@ -39,6 +40,7 @@
|
||||
"expo-system-ui": "~6.0.7",
|
||||
"expo-web-browser": "~15.0.9",
|
||||
"lodash": "^4.17.21",
|
||||
"moti": "^0.30.0",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-native": "0.81.4",
|
||||
@@ -340,6 +342,10 @@
|
||||
|
||||
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA=="],
|
||||
|
||||
"@emotion/is-prop-valid": ["@emotion/is-prop-valid@0.8.8", "", { "dependencies": { "@emotion/memoize": "0.7.4" } }, "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA=="],
|
||||
|
||||
"@emotion/memoize": ["@emotion/memoize@0.7.4", "", {}, "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw=="],
|
||||
|
||||
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.7.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw=="],
|
||||
|
||||
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="],
|
||||
@@ -478,6 +484,18 @@
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
|
||||
|
||||
"@motionone/animation": ["@motionone/animation@10.18.0", "", { "dependencies": { "@motionone/easing": "^10.18.0", "@motionone/types": "^10.17.1", "@motionone/utils": "^10.18.0", "tslib": "^2.3.1" } }, "sha512-9z2p5GFGCm0gBsZbi8rVMOAJCtw1WqBTIPw3ozk06gDvZInBPIsQcHgYogEJ4yuHJ+akuW8g1SEIOpTOvYs8hw=="],
|
||||
|
||||
"@motionone/dom": ["@motionone/dom@10.12.0", "", { "dependencies": { "@motionone/animation": "^10.12.0", "@motionone/generators": "^10.12.0", "@motionone/types": "^10.12.0", "@motionone/utils": "^10.12.0", "hey-listen": "^1.0.8", "tslib": "^2.3.1" } }, "sha512-UdPTtLMAktHiqV0atOczNYyDd/d8Cf5fFsd1tua03PqTwwCe/6lwhLSQ8a7TbnQ5SN0gm44N1slBfj+ORIhrqw=="],
|
||||
|
||||
"@motionone/easing": ["@motionone/easing@10.18.0", "", { "dependencies": { "@motionone/utils": "^10.18.0", "tslib": "^2.3.1" } }, "sha512-VcjByo7XpdLS4o9T8t99JtgxkdMcNWD3yHU/n6CLEz3bkmKDRZyYQ/wmSf6daum8ZXqfUAgFeCZSpJZIMxaCzg=="],
|
||||
|
||||
"@motionone/generators": ["@motionone/generators@10.18.0", "", { "dependencies": { "@motionone/types": "^10.17.1", "@motionone/utils": "^10.18.0", "tslib": "^2.3.1" } }, "sha512-+qfkC2DtkDj4tHPu+AFKVfR/C30O1vYdvsGYaR13W/1cczPrrcjdvYCj0VLFuRMN+lP1xvpNZHCRNM4fBzn1jg=="],
|
||||
|
||||
"@motionone/types": ["@motionone/types@10.17.1", "", {}, "sha512-KaC4kgiODDz8hswCrS0btrVrzyU2CSQKO7Ps90ibBVSQmjkrt2teqta6/sOG59v7+dPnKMAg13jyqtMKV2yJ7A=="],
|
||||
|
||||
"@motionone/utils": ["@motionone/utils@10.18.0", "", { "dependencies": { "@motionone/types": "^10.17.1", "hey-listen": "^1.0.8", "tslib": "^2.3.1" } }, "sha512-3XVF7sgyTSI2KWvTf6uLlBJ5iAgRgmvp3bpuOiQJvInd4nZ19ET8lX5unn30SlmRH7hXbBbH+Gxd0m0klJ3Xtw=="],
|
||||
|
||||
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.11", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.9.0" } }, "sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA=="],
|
||||
|
||||
"@nicolo-ribaudo/chokidar-2": ["@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3", "", {}, "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ=="],
|
||||
@@ -1226,6 +1244,8 @@
|
||||
|
||||
"expo-keep-awake": ["expo-keep-awake@15.0.7", "", { "peerDependencies": { "expo": "*", "react": "*" } }, "sha512-CgBNcWVPnrIVII5G54QDqoE125l+zmqR4HR8q+MQaCfHet+dYpS5vX5zii/RMayzGN4jPgA4XYIQ28ePKFjHoA=="],
|
||||
|
||||
"expo-linear-gradient": ["expo-linear-gradient@15.0.7", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-yF+y+9Shpr/OQFfy/wglB/0bykFMbwHBTuMRa5Of/r2P1wbkcacx8rg0JsUWkXH/rn2i2iWdubyqlxSJa3ggZA=="],
|
||||
|
||||
"expo-linking": ["expo-linking@8.0.8", "", { "dependencies": { "expo-constants": "~18.0.8", "invariant": "^2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-MyeMcbFDKhXh4sDD1EHwd0uxFQNAc6VCrwBkNvvvufUsTYFq3glTA9Y8a+x78CPpjNqwNAamu74yIaIz7IEJyg=="],
|
||||
|
||||
"expo-location": ["expo-location@19.0.7", "", { "peerDependencies": { "expo": "*" } }, "sha512-YNkh4r9E6ECbPkBCAMG5A5yHDgS0pw+Rzyd0l2ZQlCtjkhlODB55nMCKr5CZnUI0mXTkaSm8CwfoCO8n2MpYfg=="],
|
||||
@@ -1304,6 +1324,10 @@
|
||||
|
||||
"form-data": ["form-data@4.0.4", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow=="],
|
||||
|
||||
"framer-motion": ["framer-motion@6.5.1", "", { "dependencies": { "@motionone/dom": "10.12.0", "framesync": "6.0.1", "hey-listen": "^1.0.8", "popmotion": "11.0.3", "style-value-types": "5.0.0", "tslib": "^2.1.0" }, "optionalDependencies": { "@emotion/is-prop-valid": "^0.8.2" }, "peerDependencies": { "react": ">=16.8 || ^17.0.0 || ^18.0.0", "react-dom": ">=16.8 || ^17.0.0 || ^18.0.0" } }, "sha512-o1BGqqposwi7cgDrtg0dNONhkmPsUFDaLcKXigzuTFC5x58mE8iyTazxSudFzmT6MEyJKfjjU8ItoMe3W+3fiw=="],
|
||||
|
||||
"framesync": ["framesync@6.0.1", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-fUY88kXvGiIItgNC7wcTOl0SNRCVXMKSWW2Yzfmn7EKNc+MpCzcz9DhdHcdjbrtN3c6R4H5dTY2jiCpPdysEjA=="],
|
||||
|
||||
"freeport-async": ["freeport-async@2.0.0", "", {}, "sha512-K7od3Uw45AJg00XUmy15+Hae2hOcgKcmN3/EF6Y7i01O0gaqiRx8sUSpsb9+BRNL8RPBrhzPsVfy8q9ADlJuWQ=="],
|
||||
|
||||
"fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="],
|
||||
@@ -1380,6 +1404,8 @@
|
||||
|
||||
"hermes-parser": ["hermes-parser@0.29.1", "", { "dependencies": { "hermes-estree": "0.29.1" } }, "sha512-xBHWmUtRC5e/UL0tI7Ivt2riA/YBq9+SiYFU7C1oBa/j2jYGlIF9043oak1F47ihuDIxQ5nbsKueYJDRY02UgA=="],
|
||||
|
||||
"hey-listen": ["hey-listen@1.0.8", "", {}, "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q=="],
|
||||
|
||||
"hoist-non-react-statics": ["hoist-non-react-statics@3.3.2", "", { "dependencies": { "react-is": "^16.7.0" } }, "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw=="],
|
||||
|
||||
"hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="],
|
||||
@@ -1734,6 +1760,8 @@
|
||||
|
||||
"mkdirp": ["mkdirp@3.0.1", "", { "bin": "dist/cjs/src/bin.js" }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="],
|
||||
|
||||
"moti": ["moti@0.30.0", "", { "dependencies": { "framer-motion": "^6.5.1" }, "peerDependencies": { "react-native-reanimated": "*" } }, "sha512-YN78mcefo8kvJaL+TZNyusq6YA2aMFvBPl8WiLPy4eb4wqgOFggJOjP9bUr2YO8PrAt0uusmRG8K4RPL4OhCsA=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],
|
||||
@@ -1850,6 +1878,8 @@
|
||||
|
||||
"pngjs": ["pngjs@5.0.0", "", {}, "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="],
|
||||
|
||||
"popmotion": ["popmotion@11.0.3", "", { "dependencies": { "framesync": "6.0.1", "hey-listen": "^1.0.8", "style-value-types": "5.0.0", "tslib": "^2.1.0" } }, "sha512-Y55FLdj3UxkR7Vl3s7Qr4e9m0onSnP8W7d/xQLsoJM40vs6UKHFdygs6SWryasTZYqugMjm3BepCF4CWXDiHgA=="],
|
||||
|
||||
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
|
||||
|
||||
"postcss": ["postcss@8.4.49", "", { "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA=="],
|
||||
@@ -2162,6 +2192,8 @@
|
||||
|
||||
"structured-headers": ["structured-headers@0.4.1", "", {}, "sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg=="],
|
||||
|
||||
"style-value-types": ["style-value-types@5.0.0", "", { "dependencies": { "hey-listen": "^1.0.8", "tslib": "^2.1.0" } }, "sha512-08yq36Ikn4kx4YU6RD7jWEv27v4V+PUsOGa4n/as8Et3CuODMJQ00ENeAVXAeydX4Z2j1XHZF1K2sX4mGl18fA=="],
|
||||
|
||||
"styleq": ["styleq@0.1.3", "", {}, "sha512-3ZUifmCDCQanjeej1f6kyl/BeP/Vae5EYkQ9iJfUm/QwZvlgnZzyflqAsAWYURdtea8Vkvswu2GrC57h3qffcA=="],
|
||||
|
||||
"sucrase": ["sucrase@3.35.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA=="],
|
||||
|
||||
@@ -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;
|
||||
188
components/_ShareComponent/NewWrapper.tsx
Normal file
188
components/_ShareComponent/NewWrapper.tsx
Normal file
@@ -0,0 +1,188 @@
|
||||
// @/components/NewWrapper.tsx
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { OS_HEIGHT } from "@/constants/constans-value";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import {
|
||||
ImageBackground,
|
||||
Keyboard,
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
ScrollView,
|
||||
FlatList,
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
StyleProp,
|
||||
ViewStyle,
|
||||
} from "react-native";
|
||||
import {
|
||||
NativeSafeAreaViewProps,
|
||||
SafeAreaView,
|
||||
} from "react-native-safe-area-context";
|
||||
import type { ScrollViewProps, FlatListProps } from "react-native";
|
||||
|
||||
// --- ✅ Tambahkan refreshControl ke BaseProps ---
|
||||
interface BaseProps {
|
||||
withBackground?: boolean;
|
||||
headerComponent?: React.ReactNode;
|
||||
footerComponent?: React.ReactNode;
|
||||
floatingButton?: React.ReactNode;
|
||||
hideFooter?: boolean;
|
||||
edgesFooter?: NativeSafeAreaViewProps["edges"];
|
||||
style?: StyleProp<ViewStyle>;
|
||||
refreshControl?: ScrollViewProps["refreshControl"]; // ✅ dipakai di kedua mode
|
||||
}
|
||||
|
||||
interface StaticModeProps extends BaseProps {
|
||||
children: React.ReactNode;
|
||||
listData?: never;
|
||||
renderItem?: never;
|
||||
}
|
||||
|
||||
interface ListModeProps extends BaseProps {
|
||||
children?: never;
|
||||
listData?: any[];
|
||||
renderItem?: FlatListProps<any>["renderItem"];
|
||||
onEndReached?: () => void;
|
||||
// ✅ Gunakan tipe yang kompatibel dengan FlatList
|
||||
ListHeaderComponent?: React.ReactElement | null;
|
||||
ListFooterComponent?: React.ReactElement | null;
|
||||
ListEmptyComponent?: React.ReactElement | null;
|
||||
keyExtractor?: FlatListProps<any>["keyExtractor"];
|
||||
}
|
||||
|
||||
type NewWrapperProps = StaticModeProps | ListModeProps;
|
||||
|
||||
const NewWrapper = (props: NewWrapperProps) => {
|
||||
const {
|
||||
withBackground = false,
|
||||
headerComponent,
|
||||
footerComponent,
|
||||
floatingButton,
|
||||
hideFooter = false,
|
||||
edgesFooter = [],
|
||||
style,
|
||||
refreshControl, // ✅ sekarang ada di BaseProps
|
||||
} = props;
|
||||
|
||||
const assetBackground = require("../../assets/images/main-background.png");
|
||||
|
||||
const renderContainer = (content: React.ReactNode) => {
|
||||
if (withBackground) {
|
||||
return (
|
||||
<ImageBackground
|
||||
source={assetBackground}
|
||||
resizeMode="cover"
|
||||
style={GStyles.imageBackground}
|
||||
>
|
||||
<View style={[GStyles.containerWithBackground, style]}>
|
||||
{content}
|
||||
</View>
|
||||
</ImageBackground>
|
||||
);
|
||||
}
|
||||
return <View style={[GStyles.container, style]}>{content}</View>;
|
||||
};
|
||||
|
||||
// 🔹 Mode Dinamis
|
||||
if ("listData" in props) {
|
||||
const listProps = props as ListModeProps;
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
||||
style={{ flex: 1, backgroundColor: MainColor.darkblue }}
|
||||
>
|
||||
{headerComponent && (
|
||||
<View style={GStyles.stickyHeader}>{headerComponent}</View>
|
||||
)}
|
||||
<View style={[GStyles.container, style]}>
|
||||
<FlatList
|
||||
data={listProps.listData}
|
||||
renderItem={listProps.renderItem}
|
||||
keyExtractor={
|
||||
listProps.keyExtractor ||
|
||||
((item) => {
|
||||
if (item.id == null) {
|
||||
console.warn("Item tanpa 'id':", item);
|
||||
return `fallback-${JSON.stringify(item)}`;
|
||||
}
|
||||
return String(item.id);
|
||||
})
|
||||
}
|
||||
|
||||
refreshControl={refreshControl} // ✅ dari BaseProps
|
||||
onEndReached={listProps.onEndReached}
|
||||
onEndReachedThreshold={0.5}
|
||||
ListHeaderComponent={listProps.ListHeaderComponent}
|
||||
ListFooterComponent={listProps.ListFooterComponent}
|
||||
ListEmptyComponent={listProps.ListEmptyComponent}
|
||||
contentContainerStyle={{ flexGrow: 1 }}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
/>
|
||||
</View>
|
||||
|
||||
{footerComponent ? (
|
||||
<SafeAreaView
|
||||
edges={Platform.OS === "ios" ? edgesFooter : ["bottom"]}
|
||||
style={{ backgroundColor: MainColor.darkblue, height: OS_HEIGHT }}
|
||||
>
|
||||
{footerComponent}
|
||||
</SafeAreaView>
|
||||
) : hideFooter ? null : (
|
||||
<SafeAreaView
|
||||
edges={["bottom"]}
|
||||
style={{ backgroundColor: MainColor.darkblue }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{floatingButton && (
|
||||
<View style={GStyles.floatingContainer}>{floatingButton}</View>
|
||||
)}
|
||||
</KeyboardAvoidingView>
|
||||
);
|
||||
}
|
||||
|
||||
// 🔹 Mode Statis
|
||||
const staticProps = props as StaticModeProps;
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
||||
style={{ flex: 1, backgroundColor: MainColor.darkblue }}
|
||||
>
|
||||
{headerComponent && (
|
||||
<View style={GStyles.stickyHeader}>{headerComponent}</View>
|
||||
)}
|
||||
|
||||
<ScrollView
|
||||
contentContainerStyle={{ flexGrow: 1 }}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
refreshControl={refreshControl} // ✅ sekarang valid
|
||||
>
|
||||
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
||||
{renderContainer(staticProps.children)}
|
||||
</TouchableWithoutFeedback>
|
||||
</ScrollView>
|
||||
|
||||
{footerComponent ? (
|
||||
<SafeAreaView
|
||||
edges={Platform.OS === "ios" ? edgesFooter : ["bottom"]}
|
||||
style={{ backgroundColor: MainColor.darkblue, height: OS_HEIGHT }}
|
||||
>
|
||||
{footerComponent}
|
||||
</SafeAreaView>
|
||||
) : hideFooter ? null : (
|
||||
<SafeAreaView
|
||||
edges={["bottom"]}
|
||||
style={{ backgroundColor: MainColor.darkblue }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{floatingButton && (
|
||||
<View style={GStyles.floatingContainer}>{floatingButton}</View>
|
||||
)}
|
||||
</KeyboardAvoidingView>
|
||||
);
|
||||
};
|
||||
|
||||
export default NewWrapper;
|
||||
59
components/_ShareComponent/SkeletonCustom.tsx
Normal file
59
components/_ShareComponent/SkeletonCustom.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
// components/CustomSkeleton.tsx
|
||||
import React from "react";
|
||||
import { View, StyleProp, ViewStyle, DimensionValue } from "react-native";
|
||||
import { MotiView } from "moti";
|
||||
import { AccentColor, MainColor } from "@/constants/color-palet";
|
||||
|
||||
interface CustomSkeletonProps {
|
||||
isLoading?: boolean;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
width?: DimensionValue;
|
||||
height?: DimensionValue;
|
||||
radius?: number;
|
||||
}
|
||||
|
||||
const CustomSkeleton: React.FC<CustomSkeletonProps> = ({
|
||||
isLoading = true,
|
||||
style,
|
||||
width = "100%",
|
||||
height = 16,
|
||||
radius = 8,
|
||||
}) => {
|
||||
if (!isLoading) return null;
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
{
|
||||
width,
|
||||
height,
|
||||
borderRadius: radius,
|
||||
backgroundColor: AccentColor.darkblue,
|
||||
overflow: "hidden",
|
||||
position: "relative",
|
||||
},
|
||||
style,
|
||||
]}
|
||||
>
|
||||
<MotiView
|
||||
from={{ translateY: -100 }}
|
||||
animate={{ translateY: 100 }}
|
||||
transition={{
|
||||
duration: 1200,
|
||||
repeat: Infinity,
|
||||
type: "timing",
|
||||
}}
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: 100,
|
||||
backgroundColor: MainColor.soft_darkblue,
|
||||
borderRadius: 4,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomSkeleton;
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
View,
|
||||
StyleProp,
|
||||
ViewStyle,
|
||||
ScrollViewProps,
|
||||
} from "react-native";
|
||||
import { NativeSafeAreaViewProps, SafeAreaView } from "react-native-safe-area-context";
|
||||
|
||||
@@ -23,6 +24,7 @@ interface ViewWrapperProps {
|
||||
hideFooter?: boolean;
|
||||
edgesFooter?: NativeSafeAreaViewProps["edges"];
|
||||
style?: StyleProp<ViewStyle>;
|
||||
refreshControl?: ScrollViewProps["refreshControl"];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,6 +42,7 @@ const ViewWrapper = ({
|
||||
hideFooter = false,
|
||||
edgesFooter =[],
|
||||
style,
|
||||
refreshControl,
|
||||
}: ViewWrapperProps) => {
|
||||
const assetBackground = require("../../assets/images/main-background.png");
|
||||
|
||||
@@ -57,6 +60,7 @@ const ViewWrapper = ({
|
||||
<ScrollView
|
||||
contentContainerStyle={{ flexGrow: 1 }}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
refreshControl={refreshControl}
|
||||
>
|
||||
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
||||
<View style={{ flex: 1 }}>
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -72,8 +72,31 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const loginWithNomor = async (nomor: string) => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
console.log("[Masuk provider]", nomor);
|
||||
const response = await apiLogin({ nomor: nomor });
|
||||
await AsyncStorage.setItem("kode_otp", response.kodeId);
|
||||
console.log("[RESPONSE AUTH]", JSON.stringify(response));
|
||||
|
||||
|
||||
if (response.success) {
|
||||
console.log("[Keluar provider]", nomor);
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Sukses",
|
||||
text2: "Kode OTP berhasil dikirim",
|
||||
});
|
||||
|
||||
await AsyncStorage.setItem("kode_otp", response.kodeId);
|
||||
router.push(`/verification?nomor=${nomor}`);
|
||||
return;
|
||||
} else {
|
||||
router.push(`/register?nomor=${nomor}`);
|
||||
Toast.show({
|
||||
type: "info",
|
||||
text1: "Info",
|
||||
text2: "Silahkan mendaftar",
|
||||
});
|
||||
return;
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response?.data?.message || "Gagal kirim OTP");
|
||||
} finally {
|
||||
@@ -81,13 +104,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 +140,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 +171,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 +188,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 +206,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 +218,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>12</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],
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"expo-haptics": "~15.0.7",
|
||||
"expo-image": "~3.0.8",
|
||||
"expo-image-picker": "~17.0.8",
|
||||
"expo-linear-gradient": "~15.0.7",
|
||||
"expo-linking": "~8.0.8",
|
||||
"expo-location": "~19.0.7",
|
||||
"expo-notifications": "^0.32.13",
|
||||
@@ -46,6 +47,7 @@
|
||||
"expo-system-ui": "~6.0.7",
|
||||
"expo-web-browser": "~15.0.9",
|
||||
"lodash": "^4.17.21",
|
||||
"moti": "^0.30.0",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-native": "0.81.4",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { NewWrapper } from "@/components";
|
||||
import ButtonCustom from "@/components/Button/ButtonCustom";
|
||||
import Spacing from "@/components/_ShareComponent/Spacing";
|
||||
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
|
||||
@@ -5,9 +6,11 @@ import { MainColor } from "@/constants/color-palet";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiVersion } from "@/service/api-config";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import { Redirect, router } from "expo-router";
|
||||
import versionBadge from "@/utils/viersionBadge";
|
||||
import VersionBadge from "@/utils/viersionBadge";
|
||||
import { Redirect } from "expo-router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Text, View } from "react-native";
|
||||
import { RefreshControl, Text, View } from "react-native";
|
||||
import PhoneInput, { ICountry } from "react-native-international-phone-number";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
@@ -16,6 +19,7 @@ export default function LoginView() {
|
||||
const [selectedCountry, setSelectedCountry] = useState<null | ICountry>(null);
|
||||
const [inputValue, setInputValue] = useState<string>("");
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [refreshing, setRefreshing] = useState<boolean>(false);
|
||||
|
||||
const { loginWithNomor, token, isAdmin, isUserActive } = useAuth();
|
||||
|
||||
@@ -25,7 +29,18 @@ export default function LoginView() {
|
||||
|
||||
async function onLoadVersion() {
|
||||
const res = await apiVersion();
|
||||
setVersion(res.data);
|
||||
|
||||
if (res.success) {
|
||||
setVersion(versionBadge());
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRefresh() {
|
||||
setRefreshing(true);
|
||||
await onLoadVersion();
|
||||
setInputValue("");
|
||||
setLoading(false);
|
||||
setRefreshing(false);
|
||||
}
|
||||
|
||||
function handleInputValue(phoneNumber: string) {
|
||||
@@ -66,21 +81,13 @@ export default function LoginView() {
|
||||
if (!isValid) return;
|
||||
|
||||
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}`);
|
||||
} catch (error) {
|
||||
console.log("Error login", error);
|
||||
Toast.show({
|
||||
@@ -91,6 +98,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) {
|
||||
@@ -110,7 +141,12 @@ export default function LoginView() {
|
||||
}
|
||||
|
||||
return (
|
||||
<ViewWrapper withBackground>
|
||||
<NewWrapper
|
||||
withBackground
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />
|
||||
}
|
||||
>
|
||||
<View style={GStyles.authContainer}>
|
||||
<View>
|
||||
<View style={GStyles.authContainerTitle}>
|
||||
@@ -154,6 +190,6 @@ export default function LoginView() {
|
||||
Coba
|
||||
</ButtonCustom> */}
|
||||
</View>
|
||||
</ViewWrapper>
|
||||
</NewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ import Toast from "react-native-toast-message";
|
||||
export default function VerificationView() {
|
||||
const { nomor } = useLocalSearchParams<{ nomor: string }>();
|
||||
|
||||
console.log("[NOMOR]", nomor);
|
||||
|
||||
const [inputOtp, setInputOtp] = useState<string>("");
|
||||
const [userNumber, setUserNumber] = useState<string>("");
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
@@ -52,7 +54,7 @@ export default function VerificationView() {
|
||||
try {
|
||||
const response = await apiCheckCodeOtp({ kodeId });
|
||||
console.log(
|
||||
"Response check code otp >>",
|
||||
"[OTP] >>",
|
||||
JSON.stringify(response.otp, null, 2)
|
||||
);
|
||||
// Kita tidak perlu simpan codeOtp di state karena verifikasi dilakukan di backend
|
||||
@@ -89,8 +91,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 +106,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" });
|
||||
|
||||
@@ -22,6 +22,7 @@ const drawerItemsForumBerandaForAuthor = ({
|
||||
),
|
||||
label: "Edit posting",
|
||||
path: `/forum/${id}/edit`,
|
||||
values: "edit"
|
||||
},
|
||||
{
|
||||
icon:
|
||||
@@ -34,6 +35,7 @@ const drawerItemsForumBerandaForAuthor = ({
|
||||
label: status === "Open" ? "Tutup forum" : "Buka forum",
|
||||
path: "",
|
||||
color: status === "Open" ? MainColor.orange : MainColor.green,
|
||||
value: "status"
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
@@ -42,10 +44,11 @@ const drawerItemsForumBerandaForAuthor = ({
|
||||
label: "Hapus",
|
||||
path: "",
|
||||
color: MainColor.red,
|
||||
value: "delete"
|
||||
},
|
||||
];
|
||||
|
||||
const drawerItemsForumBerandaForNonAuthor = ({ id }: { id: string }) => [
|
||||
const drawerItemsForumBerandaForNonAuthor = ({ id, username }: { id: string; username: string }) => [
|
||||
{
|
||||
icon: (
|
||||
<Ionicons name="flag" size={ICON_SIZE_SMALL} color={MainColor.white} />
|
||||
@@ -53,6 +56,16 @@ const drawerItemsForumBerandaForNonAuthor = ({ id }: { id: string }) => [
|
||||
label: "Laporkan diskusi",
|
||||
// color: MainColor.white,
|
||||
path: `/forum/${id}/report-posting`,
|
||||
value: "report"
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<Ionicons name="ban" size={ICON_SIZE_SMALL} color={MainColor.white} />
|
||||
),
|
||||
label: `Blockir @${username}`,
|
||||
color: MainColor.red,
|
||||
path: `/forum/${id}/report-posting`,
|
||||
value: "block"
|
||||
},
|
||||
];
|
||||
|
||||
@@ -64,6 +77,7 @@ const drawerItemsForumComentarForAuthor = ({ id }: { id: string }) => [
|
||||
label: "Hapus",
|
||||
color: MainColor.red,
|
||||
path: "",
|
||||
value: "delete"
|
||||
},
|
||||
];
|
||||
|
||||
@@ -75,5 +89,6 @@ const drawerItemsForumComentarForNonAuthor = ({ id }: { id: string }) => [
|
||||
label: "Laporkan",
|
||||
// color: MainColor.white,
|
||||
path: `/forum/${id}/report-commentar`,
|
||||
value: "report"
|
||||
},
|
||||
];
|
||||
|
||||
@@ -9,15 +9,18 @@ import {
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiForumDelete } from "@/service/api-client/api-forum";
|
||||
import Toast from "react-native-toast-message";
|
||||
import { apiForumBlockUser } from "@/service/api-client/api-user";
|
||||
|
||||
export default function Forum_MenuDrawerBerandaSection({
|
||||
id,
|
||||
authorUsername,
|
||||
status,
|
||||
setIsDrawerOpen,
|
||||
authorId,
|
||||
handlerUpdateStatus,
|
||||
}: {
|
||||
id: string;
|
||||
authorUsername: string;
|
||||
status: string;
|
||||
setIsDrawerOpen: (value: boolean) => void;
|
||||
authorId: string;
|
||||
@@ -25,7 +28,7 @@ export default function Forum_MenuDrawerBerandaSection({
|
||||
}) {
|
||||
const { user } = useAuth();
|
||||
const handlePress = (item: IMenuDrawerItem) => {
|
||||
if (item.label === "Hapus") {
|
||||
if (item.value === "delete") {
|
||||
AlertDefaultSystem({
|
||||
title: "Hapus diskusi",
|
||||
message: "Apakah Anda yakin ingin menghapus diskusi ini?",
|
||||
@@ -34,7 +37,7 @@ export default function Forum_MenuDrawerBerandaSection({
|
||||
onPressRight: async () => {
|
||||
try {
|
||||
const response = await apiForumDelete({ id });
|
||||
|
||||
|
||||
if (response.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
@@ -52,14 +55,46 @@ export default function Forum_MenuDrawerBerandaSection({
|
||||
}
|
||||
},
|
||||
});
|
||||
} else if (item.label === "Buka forum" || item.label === "Tutup forum") {
|
||||
} else if (item.value === "status") {
|
||||
AlertDefaultSystem({
|
||||
title: "Ubah Status",
|
||||
message: "Apakah Anda yakin ingin mengubah status forum ini?",
|
||||
textLeft: "Batal",
|
||||
textRight: "Ubah",
|
||||
onPressRight: () => {
|
||||
handlerUpdateStatus?.(item.label === "Buka forum" ? "Open" : "Closed");
|
||||
handlerUpdateStatus?.(
|
||||
item.label === "Buka forum" ? "Open" : "Closed"
|
||||
);
|
||||
},
|
||||
});
|
||||
} else if (item.value === "block") {
|
||||
AlertDefaultSystem({
|
||||
title: "Blockir user",
|
||||
message: `Apakah anda yakin ingin blockir user @${authorUsername}?`,
|
||||
textLeft: "Batal",
|
||||
textRight: "Blockir",
|
||||
onPressRight: async () => {
|
||||
console.log("Blockir");
|
||||
const response = await apiForumBlockUser({
|
||||
data: {
|
||||
menuFeature: "Forum",
|
||||
blockedId: authorId,
|
||||
blockerId: user?.id || "",
|
||||
},
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Berhasil blokir",
|
||||
});
|
||||
router.back();
|
||||
} else {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: response.message,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
} else {
|
||||
@@ -76,9 +111,12 @@ export default function Forum_MenuDrawerBerandaSection({
|
||||
data={
|
||||
authorId === user?.id
|
||||
? drawerItemsForumBerandaForAuthor({ id, status })
|
||||
: drawerItemsForumBerandaForNonAuthor({ id })
|
||||
: drawerItemsForumBerandaForNonAuthor({
|
||||
id,
|
||||
username: authorUsername,
|
||||
})
|
||||
}
|
||||
columns={4} // Ubah ke 2 jika ingin 2 kolom per baris
|
||||
columns={authorId === user?.id ? 4 : 2} // Ubah ke 2 jika ingin 2 kolom per baris
|
||||
onPressItem={handlePress as any}
|
||||
/>
|
||||
</>
|
||||
|
||||
108
screens/Forum/ViewBeranda.tsx
Normal file
108
screens/Forum/ViewBeranda.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
import {
|
||||
BackButton,
|
||||
AvatarComp,
|
||||
ViewWrapper,
|
||||
SearchInput,
|
||||
FloatingButton,
|
||||
LoaderCustom,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiForumGetAll } from "@/service/api-client/api-forum";
|
||||
import { apiUser } from "@/service/api-client/api-user";
|
||||
import { Stack, router } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useState, useEffect } from "react";
|
||||
import { RefreshControl } from "react-native";
|
||||
import Forum_BoxDetailSection from "./DiscussionBoxSection";
|
||||
|
||||
export default function Forum_ViewBeranda() {
|
||||
const { user } = useAuth();
|
||||
const [dataUser, setDataUser] = useState<any>();
|
||||
const [listData, setListData] = useState<any[]>();
|
||||
const [loadingGetList, setLoadingGetList] = useState(false);
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
onLoadData();
|
||||
onLoadDataProfile(user?.id as string);
|
||||
}, [user?.id, search]);
|
||||
|
||||
const onLoadDataProfile = async (id: string) => {
|
||||
const response = await apiUser(id);
|
||||
setDataUser(response.data);
|
||||
};
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setLoadingGetList(true);
|
||||
const response = await apiForumGetAll({
|
||||
category: "beranda",
|
||||
search: search,
|
||||
userLoginId: user?.id,
|
||||
});
|
||||
|
||||
setListData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadingGetList(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Forum",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => (
|
||||
<AvatarComp
|
||||
fileId={dataUser?.Profile?.imageId}
|
||||
size="base"
|
||||
href={`/forum/${user?.id}/forumku`}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
<ViewWrapper
|
||||
headerComponent={
|
||||
<SearchInput
|
||||
placeholder="Cari topik diskusi"
|
||||
onChangeText={(e) => setSearch(e)}
|
||||
/>
|
||||
}
|
||||
floatingButton={
|
||||
<FloatingButton
|
||||
onPress={() =>
|
||||
router.navigate("/(application)/(user)/forum/create")
|
||||
}
|
||||
/>
|
||||
}
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={loadingGetList} onRefresh={onLoadData} />
|
||||
}
|
||||
>
|
||||
{loadingGetList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom align="center" color="gray">
|
||||
Tidak ada diskusi
|
||||
</TextCustom>
|
||||
) : (
|
||||
listData?.map((e: any, i: number) => (
|
||||
<Forum_BoxDetailSection
|
||||
key={i}
|
||||
data={e}
|
||||
onSetData={() => {}}
|
||||
isTruncate={true}
|
||||
href={`/forum/${e.id}`}
|
||||
isRightComponent={false}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
214
screens/Forum/ViewBeranda2.tsx
Normal file
214
screens/Forum/ViewBeranda2.tsx
Normal file
@@ -0,0 +1,214 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AvatarComp,
|
||||
BackButton,
|
||||
FloatingButton,
|
||||
LoaderCustom,
|
||||
SearchInput,
|
||||
StackCustom,
|
||||
TextCustom, // ← gunakan NewWrapper yang sudah diperbaiki
|
||||
} from "@/components";
|
||||
import SkeletonCustom from "@/components/_ShareComponent/SkeletonCustom";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection";
|
||||
import { apiForumGetAll } from "@/service/api-client/api-forum";
|
||||
import { apiUser } from "@/service/api-client/api-user";
|
||||
import { router, Stack } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { RefreshControl, View } from "react-native";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
|
||||
// Sesuai dengan `takeData = 5` di API-mu
|
||||
const PAGE_SIZE = 5;
|
||||
|
||||
export default function Forum_ViewBeranda2() {
|
||||
const { user } = useAuth();
|
||||
const [dataUser, setDataUser] = useState<any>(null);
|
||||
const [listData, setListData] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [hasMore, setHasMore] = useState(true);
|
||||
const [page, setPage] = useState(1);
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
// 🔹 Load data profil user sekali
|
||||
useEffect(() => {
|
||||
if (user?.id) {
|
||||
apiUser(user.id).then((res) => setDataUser(res.data));
|
||||
}
|
||||
}, [user?.id]);
|
||||
|
||||
// 🔹 Reset dan muat ulang saat search atau user berubah
|
||||
useEffect(() => {
|
||||
setPage(1);
|
||||
setListData([]);
|
||||
setHasMore(true);
|
||||
fetchData(1, true);
|
||||
}, [search, user?.id]);
|
||||
|
||||
// 🔹 Fungsi fetch data
|
||||
const fetchData = async (pageNumber: number, clear: boolean) => {
|
||||
if (!user?.id) return;
|
||||
|
||||
// Cegah multiple call
|
||||
if (!clear && (loading || refreshing)) return;
|
||||
|
||||
const isRefresh = clear;
|
||||
if (isRefresh) setRefreshing(true);
|
||||
if (!isRefresh) setLoading(true);
|
||||
|
||||
try {
|
||||
const response = await apiForumGetAll({
|
||||
category: "beranda",
|
||||
search: search || "",
|
||||
userLoginId: user.id,
|
||||
page: String(pageNumber), // API terima string
|
||||
});
|
||||
|
||||
const newData = response.data || [];
|
||||
setListData((prev) => {
|
||||
const current = Array.isArray(prev) ? prev : [];
|
||||
return clear ? newData : [...current, ...newData];
|
||||
});
|
||||
setHasMore(newData.length === PAGE_SIZE);
|
||||
setPage(pageNumber);
|
||||
} catch (error) {
|
||||
console.error("[ERROR] Fetch forum:", error);
|
||||
setHasMore(false);
|
||||
} finally {
|
||||
setRefreshing(false);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 🔹 Pull-to-refresh
|
||||
const onRefresh = useCallback(() => {
|
||||
fetchData(1, true);
|
||||
}, [search, user?.id]);
|
||||
|
||||
// 🔹 Infinite scroll
|
||||
const loadMore = useCallback(() => {
|
||||
if (hasMore && !loading && !refreshing) {
|
||||
fetchData(page + 1, false);
|
||||
}
|
||||
}, [hasMore, loading, refreshing, page, search, user?.id]);
|
||||
|
||||
// 🔹 Render item forum
|
||||
const renderForumItem = ({ item }: { item: any }) => (
|
||||
<Forum_BoxDetailSection
|
||||
key={item.id}
|
||||
data={item}
|
||||
onSetData={() => {}}
|
||||
isTruncate={true}
|
||||
href={`/forum/${item.id}`}
|
||||
isRightComponent={false}
|
||||
/>
|
||||
);
|
||||
|
||||
// 🔹 Komponen Header List (di dalam FlatList)
|
||||
const ListHeaderComponent = (
|
||||
<View style={{ paddingVertical: 8, alignItems: "center" }}>
|
||||
<TextCustom>Diskusi Terbaru</TextCustom>
|
||||
</View>
|
||||
);
|
||||
|
||||
// 🔹 Komponen Footer List (loading indicator)
|
||||
const ListFooterComponent =
|
||||
loading && !refreshing && listData.length > 0 ? (
|
||||
<View style={{ paddingVertical: 16, alignItems: "center" }}>
|
||||
{/* <Text style={{ color: "#aaa", fontSize: 14 }}>Memuat diskusi...</Text> */}
|
||||
<LoaderCustom />
|
||||
</View>
|
||||
) : null;
|
||||
|
||||
// Skeleton List (untuk initial load)
|
||||
const SkeletonListComponent = () => (
|
||||
<View style={{ flex: 1 }}>
|
||||
<StackCustom>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<SkeletonCustom height={200} key={i} />
|
||||
))}
|
||||
</StackCustom>
|
||||
</View>
|
||||
);
|
||||
|
||||
// Komponen Empty
|
||||
const EmptyComponent = () => (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: 20,
|
||||
}}
|
||||
>
|
||||
<TextCustom align="center" color="gray">
|
||||
{search ? "Tidak ada hasil pencarian" : "Tidak ada diskusi"}
|
||||
</TextCustom>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 🔹 Header Navigation */}
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Forum",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => (
|
||||
<AvatarComp
|
||||
fileId={dataUser?.Profile?.imageId}
|
||||
size="base"
|
||||
href={`/forum/${user?.id}/forumku`}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 🔹 NewWrapper dalam mode list */}
|
||||
<NewWrapper
|
||||
// Header global (di atas FlatList, sticky)
|
||||
headerComponent={
|
||||
<View style={{ paddingHorizontal: 16, paddingTop: 8 }}>
|
||||
<SearchInput
|
||||
placeholder="Cari topik diskusi"
|
||||
onChangeText={_.debounce((text) => setSearch(text), 500)}
|
||||
// value={search}
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
// Floating action button
|
||||
floatingButton={
|
||||
<FloatingButton
|
||||
onPress={() =>
|
||||
router.navigate("/(application)/(user)/forum/create")
|
||||
}
|
||||
/>
|
||||
}
|
||||
// --- Mode List Props ---
|
||||
listData={listData}
|
||||
renderItem={renderForumItem}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
// IOS
|
||||
tintColor={MainColor.yellow}
|
||||
// Android
|
||||
colors={[MainColor.yellow]}
|
||||
progressBackgroundColor={MainColor.yellow}
|
||||
|
||||
refreshing={refreshing}
|
||||
onRefresh={onRefresh}
|
||||
/>
|
||||
}
|
||||
onEndReached={loadMore}
|
||||
ListFooterComponent={ListFooterComponent}
|
||||
ListEmptyComponent={
|
||||
loading && _.isEmpty(listData) ? <SkeletonListComponent /> : <EmptyComponent />
|
||||
}
|
||||
// ------------------------
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
144
screens/Forum/ViewForumku.tsx
Normal file
144
screens/Forum/ViewForumku.tsx
Normal file
@@ -0,0 +1,144 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AvatarComp,
|
||||
ButtonCustom,
|
||||
CenterCustom,
|
||||
DrawerCustom,
|
||||
FloatingButton,
|
||||
Grid,
|
||||
LoaderCustom,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection";
|
||||
import Forum_MenuDrawerBerandaSection from "@/screens/Forum/MenuDrawerSection.tsx/MenuBeranda";
|
||||
import { apiForumGetAll } from "@/service/api-client/api-forum";
|
||||
import { apiUser } from "@/service/api-client/api-user";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function View_Forumku() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const { user } = useAuth();
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [status, setStatus] = useState("");
|
||||
const [listData, setListData] = useState<any | null>(null);
|
||||
const [dataUser, setDataUser] = useState<any | null>(null);
|
||||
const [loadingGetList, setLoadingGetList] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
onLoadDataProfile(id as string);
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadDataProfile = async (id: string) => {
|
||||
try {
|
||||
const response = await apiUser(id);
|
||||
|
||||
setDataUser(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
}
|
||||
};
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setLoadingGetList(true);
|
||||
const response = await apiForumGetAll({
|
||||
search: "",
|
||||
authorId: id as string,
|
||||
category: "forumku",
|
||||
});
|
||||
|
||||
setListData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadingGetList(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper
|
||||
floatingButton={
|
||||
user?.id === id && (
|
||||
<FloatingButton
|
||||
onPress={() =>
|
||||
router.navigate("/(application)/(user)/forum/create")
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
<StackCustom>
|
||||
<CenterCustom>
|
||||
<AvatarComp
|
||||
fileId={dataUser?.Profile?.imageId}
|
||||
href={`/(application)/(image)/preview-image/${dataUser?.Profile?.imageId}`}
|
||||
size="xl"
|
||||
/>
|
||||
</CenterCustom>
|
||||
|
||||
<Grid>
|
||||
<Grid.Col span={6}>
|
||||
<TextCustom bold truncate>
|
||||
@{dataUser?.username || "-"}
|
||||
</TextCustom>
|
||||
<TextCustom>{listData?.length || "0"} postingan</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6} style={{ alignItems: "flex-end" }}>
|
||||
<ButtonCustom href={`/profile/${dataUser?.Profile?.id}`}>
|
||||
Kunjungi Profile
|
||||
</ButtonCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
{loadingGetList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom> Tidak ada diskusi</TextCustom>
|
||||
) : (
|
||||
<>
|
||||
{listData?.map((item: any, index: number) => (
|
||||
<Forum_BoxDetailSection
|
||||
isRightComponent={false}
|
||||
key={index}
|
||||
data={item}
|
||||
isTruncate={true}
|
||||
href={`/forum/${item.id}`}
|
||||
onSetData={(value) => {
|
||||
setOpenDrawer(value.setOpenDrawer);
|
||||
setStatus(value.setStatus);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
|
||||
{/* Drawer Komponen Eksternal */}
|
||||
<DrawerCustom
|
||||
height={"auto"}
|
||||
isVisible={openDrawer}
|
||||
closeDrawer={() => setOpenDrawer(false)}
|
||||
>
|
||||
<Forum_MenuDrawerBerandaSection
|
||||
id={id as string}
|
||||
status={status}
|
||||
setIsDrawerOpen={() => {
|
||||
setOpenDrawer(false);
|
||||
}}
|
||||
authorId={id as string}
|
||||
authorUsername={dataUser?.username}
|
||||
/>
|
||||
</DrawerCustom>
|
||||
</>
|
||||
);
|
||||
}
|
||||
215
screens/Forum/ViewForumku2.tsx
Normal file
215
screens/Forum/ViewForumku2.tsx
Normal file
@@ -0,0 +1,215 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AvatarComp,
|
||||
ButtonCustom,
|
||||
CenterCustom,
|
||||
FloatingButton,
|
||||
Grid,
|
||||
LoaderCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import NoDataText from "@/components/_ShareComponent/NoDataText";
|
||||
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection";
|
||||
import { apiForumGetAll } from "@/service/api-client/api-forum";
|
||||
import { apiUser } from "@/service/api-client/api-user";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { RefreshControl, View } from "react-native";
|
||||
|
||||
const PAGE_SIZE = 5;
|
||||
|
||||
export default function View_Forumku2() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const { user } = useAuth();
|
||||
const [listData, setListData] = useState<any[]>([]);
|
||||
const [dataUser, setDataUser] = useState<any | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [hasMore, setHasMore] = useState(true);
|
||||
const [page, setPage] = useState(1);
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
onLoadDataProfile(id as string);
|
||||
}, [id]);
|
||||
|
||||
useEffect(() => {
|
||||
setPage(1);
|
||||
setListData([]);
|
||||
setHasMore(true);
|
||||
fetchData(1, true);
|
||||
}, [user?.id]);
|
||||
|
||||
const onLoadDataProfile = async (id: string) => {
|
||||
try {
|
||||
const response = await apiUser(id);
|
||||
|
||||
setDataUser(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
}
|
||||
};
|
||||
|
||||
// 🔹 Fungsi fetch data
|
||||
const fetchData = async (pageNumber: number, clear: boolean) => {
|
||||
if (!user?.id) return;
|
||||
|
||||
// Cegah multiple call
|
||||
if (!clear && (loading || refreshing)) return;
|
||||
|
||||
const isRefresh = clear;
|
||||
if (isRefresh) setRefreshing(true);
|
||||
if (!isRefresh) setLoading(true);
|
||||
|
||||
try {
|
||||
const response = await apiForumGetAll({
|
||||
category: "forumku",
|
||||
authorId: id as string,
|
||||
userLoginId: user.id,
|
||||
page: String(pageNumber), // API terima string
|
||||
});
|
||||
|
||||
const newData = response.data.data || [];
|
||||
setListData((prev) => {
|
||||
const current = Array.isArray(prev) ? prev : [];
|
||||
return clear ? newData : [...current, ...newData];
|
||||
});
|
||||
setHasMore(newData.length === PAGE_SIZE);
|
||||
setPage(pageNumber);
|
||||
setCount(response.data.count);
|
||||
} catch (error) {
|
||||
console.error("[ERROR] Fetch forum:", error);
|
||||
setHasMore(false);
|
||||
} finally {
|
||||
setRefreshing(false);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 🔹 Pull-to-refresh
|
||||
const onRefresh = useCallback(() => {
|
||||
fetchData(1, true);
|
||||
}, [user?.id]);
|
||||
|
||||
// 🔹 Infinite scroll
|
||||
const loadMore = useCallback(() => {
|
||||
if (hasMore && !loading && !refreshing) {
|
||||
fetchData(page + 1, false);
|
||||
}
|
||||
}, [hasMore, loading, refreshing, page, user?.id]);
|
||||
|
||||
const randerHeaderComponent = () => (
|
||||
<>
|
||||
<CenterCustom>
|
||||
<AvatarComp
|
||||
fileId={dataUser?.Profile?.imageId}
|
||||
href={`/(application)/(image)/preview-image/${dataUser?.Profile?.imageId}`}
|
||||
size="xl"
|
||||
/>
|
||||
</CenterCustom>
|
||||
|
||||
<Grid>
|
||||
<Grid.Col style={{ paddingLeft: 8 }} span={6}>
|
||||
<TextCustom bold truncate>
|
||||
@{dataUser?.username || "-"}
|
||||
</TextCustom>
|
||||
<TextCustom>{count || "0"} postingan</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6} style={{ alignItems: "flex-end", paddingRight: 8 }}>
|
||||
<ButtonCustom href={`/profile/${dataUser?.Profile?.id}`}>
|
||||
Kunjungi Profile
|
||||
</ButtonCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
<Spacing />
|
||||
</>
|
||||
);
|
||||
|
||||
const renderList = ({ item }: { item: any }) => (
|
||||
<Forum_BoxDetailSection
|
||||
key={item.id}
|
||||
data={item}
|
||||
onSetData={() => {}}
|
||||
isTruncate={true}
|
||||
href={`/forum/${item.id}`}
|
||||
isRightComponent={false}
|
||||
/>
|
||||
);
|
||||
|
||||
// Skeleton List (untuk initial load)
|
||||
const SkeletonListComponent = () => (
|
||||
<View style={{ flex: 1 }}>
|
||||
<StackCustom>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<CustomSkeleton height={200} key={i} />
|
||||
))}
|
||||
</StackCustom>
|
||||
</View>
|
||||
);
|
||||
|
||||
// Komponen Empty
|
||||
const EmptyComponent = () => (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: 20,
|
||||
}}
|
||||
>
|
||||
<NoDataText />
|
||||
</View>
|
||||
);
|
||||
|
||||
// 🔹 Komponen Footer List (loading indicator)
|
||||
const ListFooterComponent =
|
||||
loading && !refreshing && listData.length > 0 ? (
|
||||
<View style={{ paddingVertical: 16, alignItems: "center" }}>
|
||||
{/* <Text style={{ color: "#aaa", fontSize: 14 }}>Memuat diskusi...</Text> */}
|
||||
<LoaderCustom />
|
||||
</View>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<NewWrapper
|
||||
floatingButton={
|
||||
user?.id === id && (
|
||||
<FloatingButton
|
||||
onPress={() =>
|
||||
router.navigate("/(application)/(user)/forum/create")
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
listData={listData}
|
||||
renderItem={renderList}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
// IOS
|
||||
tintColor={MainColor.yellow}
|
||||
// Android
|
||||
colors={[MainColor.yellow]}
|
||||
progressBackgroundColor={MainColor.yellow}
|
||||
refreshing={refreshing}
|
||||
onRefresh={onRefresh}
|
||||
/>
|
||||
}
|
||||
onEndReached={loadMore}
|
||||
ListHeaderComponent={randerHeaderComponent()}
|
||||
ListFooterComponent={ListFooterComponent}
|
||||
ListEmptyComponent={
|
||||
loading && _.isEmpty(listData) ? <SkeletonListComponent /> : <EmptyComponent />
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -59,11 +59,30 @@ export const stylesHome = StyleSheet.create({
|
||||
borderWidth: 2,
|
||||
borderColor: AccentColor.blue,
|
||||
},
|
||||
gridItemInactive: {
|
||||
width: "46%",
|
||||
height: "100%",
|
||||
aspectRatio: 1,
|
||||
backgroundColor: MainColor.darkblue,
|
||||
borderRadius: 8,
|
||||
padding: 16,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
marginVertical: 8,
|
||||
borderWidth: 2,
|
||||
borderColor: AccentColor.blue,
|
||||
opacity: 0.7,
|
||||
},
|
||||
gridLabel: {
|
||||
marginTop: 8,
|
||||
color: "white",
|
||||
fontWeight: "bold",
|
||||
},
|
||||
gridLabelInactive: {
|
||||
marginTop: 8,
|
||||
color: "gray",
|
||||
fontWeight: "bold",
|
||||
},
|
||||
jobVacancyContainer: {
|
||||
backgroundColor: MainColor.darkblue,
|
||||
borderRadius: 8,
|
||||
|
||||
@@ -9,21 +9,25 @@ export default function Home_FeatureSection() {
|
||||
name: "Event",
|
||||
icon: <Ionicons name="analytics" size={48} color="white" />,
|
||||
onPress: () => router.push("/(application)/(user)/event/(tabs)"),
|
||||
status: "active",
|
||||
},
|
||||
{
|
||||
name: "Collaboration",
|
||||
icon: <Ionicons name="share" size={48} color="white" />,
|
||||
icon: <Ionicons name="share" size={48} color="gray" />,
|
||||
onPress: () => router.push("/(application)/(user)/collaboration/(tabs)"),
|
||||
status: "inactive",
|
||||
},
|
||||
{
|
||||
name: "Voting",
|
||||
icon: <Ionicons name="cube" size={48} color="white" />,
|
||||
onPress: () => router.push("/(application)/(user)/voting/(tabs)"),
|
||||
status: "active",
|
||||
},
|
||||
{
|
||||
name: "Crowdfunding",
|
||||
icon: <Ionicons name="heart" size={48} color="white" />,
|
||||
onPress: () => router.push("/(application)/(user)/crowdfunding"),
|
||||
status: "active",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -33,11 +37,12 @@ export default function Home_FeatureSection() {
|
||||
{listFeature.map((item, index) => (
|
||||
<TouchableOpacity
|
||||
key={index}
|
||||
style={stylesHome.gridItem}
|
||||
style={item.status === "inactive" ? stylesHome.gridItemInactive : stylesHome.gridItem}
|
||||
onPress={item.onPress}
|
||||
disabled={item.status === "inactive"}
|
||||
>
|
||||
{item.icon}
|
||||
<Text style={stylesHome.gridLabel}>{item.name}</Text>
|
||||
<Text style={item.status === "inactive" ? stylesHome.gridLabelInactive : stylesHome.gridLabel}>{item.name}</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -14,13 +14,22 @@ export async function apiForumCreate({ data }: { data: any }) {
|
||||
export async function apiForumGetAll({
|
||||
search,
|
||||
authorId,
|
||||
userLoginId,
|
||||
category,
|
||||
page,
|
||||
}: {
|
||||
search: string;
|
||||
search?: string;
|
||||
authorId?: string;
|
||||
userLoginId?: string;
|
||||
category: "beranda" | "forumku";
|
||||
page?: string;
|
||||
}) {
|
||||
const authorQuery = authorId ? `?authorId=${authorId}` : "";
|
||||
const searchQuery = search ? `?search=${search}` : "";
|
||||
const query = search ? searchQuery : authorQuery;
|
||||
const categoryQuery = `?category=${category}`;
|
||||
const authorQuery = authorId ? `&authorId=${authorId}` : "";
|
||||
const userLoginQuery = userLoginId ? `&userLoginId=${userLoginId}` : "";
|
||||
const searchQuery = search ? `&search=${search}` : "";
|
||||
const pageQuery = page ? `&page=${page}` : "";
|
||||
const query = `${categoryQuery}${authorQuery}${userLoginQuery}${searchQuery}${pageQuery}`;
|
||||
|
||||
try {
|
||||
const response = await apiConfig.get(`/mobile/forum${query}`);
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
import { apiConfig } from "../api-config";
|
||||
|
||||
// ================== START MASTER ================== //
|
||||
export async function apiMasterAppCategory() {
|
||||
try {
|
||||
const response = await apiConfig.get(`/mobile/master/app-category`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// ================== END MASTER ================== //
|
||||
|
||||
// ================== START MASTER PORTFOLIO ================== //
|
||||
export async function apiMasterBidangBisnis() {
|
||||
try {
|
||||
@@ -167,4 +179,4 @@ export async function apiMasterTransaction() {
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,3 +14,25 @@ export async function apiDeleteUser({id}:{id: string}) {
|
||||
const response = await apiConfig.delete(`/mobile/user/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function apiForumBlockUser({
|
||||
data,
|
||||
}: {
|
||||
data: {
|
||||
// Id yang di blokir
|
||||
blockedId: string;
|
||||
// Id yang melakukan blokir
|
||||
blockerId: string;
|
||||
menuFeature: "Event" | "Forum";
|
||||
};
|
||||
}) {
|
||||
console.log("[FETCH API]", data);
|
||||
try {
|
||||
const response = await apiConfig.post(`/mobile/block-user`, {
|
||||
data: data,
|
||||
});
|
||||
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;
|
||||
|
||||
@@ -14,13 +14,13 @@ apiConfig.interceptors.request.use(
|
||||
async (config) => {
|
||||
console.log("API_BASE_URL >>", API_BASE_URL);
|
||||
const token = await AsyncStorage.getItem("authToken");
|
||||
// console.log("[TOKEN] >>", token);
|
||||
if (token) {
|
||||
// config.timeout = 10000;
|
||||
config.headers["Content-Type"] = "application/json";
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
// console.log("config", JSON.stringify(config, null, 2));
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
@@ -29,16 +29,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("/auth/mobile-login", {
|
||||
nomor: nomor,
|
||||
});
|
||||
return response.data;
|
||||
return response.data;;
|
||||
}
|
||||
|
||||
export async function apiCheckCodeOtp({ kodeId }: { kodeId: string }) {
|
||||
@@ -47,7 +46,7 @@ export async function apiCheckCodeOtp({ kodeId }: { kodeId: string }) {
|
||||
}
|
||||
|
||||
export async function apiValidationCode({ nomor }: { nomor: string }) {
|
||||
const response = await apiConfig.post(`/auth/validasi`, {
|
||||
const response = await apiConfig.post(`/auth/mobile-validasi`, {
|
||||
nomor: nomor,
|
||||
});
|
||||
return response.data;
|
||||
@@ -58,7 +57,7 @@ export async function apiRegister({
|
||||
}: {
|
||||
data: { nomor: string; username: string; termsOfServiceAccepted: boolean };
|
||||
}) {
|
||||
const response = await apiConfig.post(`/auth/register`, {
|
||||
const response = await apiConfig.post(`/auth/mobile-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,
|
||||
},
|
||||
|
||||
18
utils/viersionBadge.ts
Normal file
18
utils/viersionBadge.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
// VersionBadge.tsx
|
||||
import Constants from "expo-constants";
|
||||
import { Platform } from "react-native";
|
||||
|
||||
export default function versionBadge() {
|
||||
const expoConfig = Constants.expoConfig;
|
||||
|
||||
const version = expoConfig?.version; // "1.0.1"
|
||||
const iosBuild = expoConfig?.ios?.buildNumber; // "10"
|
||||
const androidBuild = expoConfig?.android?.versionCode; // 2
|
||||
|
||||
const build =
|
||||
Platform.OS === "ios" ? iosBuild : androidBuild;
|
||||
|
||||
const result = `${version} ( ${build} )`;
|
||||
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user