Compare commits

..

18 Commits

Author SHA1 Message Date
71e45d06cc Donation – User
- app/(application)/(user)/donation/(tabs)/index.tsx
- app/(application)/(user)/donation/(tabs)/my-donation.tsx
- app/(application)/(user)/donation/[id]/(transaction-flow)/index.tsx

Donation – Admin
- app/(application)/admin/donation/[id]/disbursement-of-funds.tsx

Image Preview
- app/(application)/(image)/preview-image/[id]/index.tsx

### No Issue
2026-01-27 17:42:14 +08:00
07e64c335e Donation – App & Admin
- app/(application)/(user)/donation/(tabs)/status.tsx
- app/(application)/(user)/donation/create-story.tsx
- app/(application)/admin/donation/[id]/[status]/transaction-detail.tsx
- app/(application)/admin/donation/[id]/reject-input.tsx
- screens/Admin/Donation/funDonationUpdateStatus.ts
- service/api-admin/api-admin-donation.ts

Config
- app.config.js
- ios/HIPMIBadungConnect/Info.plist

### No Issue
2026-01-23 17:13:06 +08:00
1aebc9b4e8 Logika EULA
Dipindah ke halaman login dan cek dengan modal

Fix:
Authentication & EULA
- context/AuthContext.tsx
- screens/Authentication/EULAView.tsx
- screens/Authentication/LoginView.tsx

Add:
- components/Modal/ModalReactNative.tsx
- screens/Authentication/EULASection.tsx

### No Issue
2026-01-23 14:45:44 +08:00
5665dc88ba Notification investasi done
### No Isssue
2026-01-22 17:54:33 +08:00
da82a02a45 Investment UI (Admin & User)
- app/(application)/(user)/investment/[id]/(transaction-flow)/invoice.tsx
- app/(application)/admin/investment/[id]/[status]/index.tsx
- app/(application)/admin/investment/[id]/[status]/transaction-detail.tsx
- app/(application)/admin/investment/[status]/status.tsx

API Service (Admin)
- service/api-admin/api-admin-investment.ts

### No issue
2026-01-21 15:39:08 +08:00
14c0f0e499 User – Investment
app/(application)/(user)/investment/(tabs)/_layout.tsx

app/(application)/(user)/investment/(tabs)/portofolio.tsx

app/(application)/(user)/investment/create.tsx

Admin – Investment

app/(application)/admin/investment/[id]/reject-input.tsx

Screens / UI

screens/Invesment/BoxBerandaSection.tsx

API

service/api-admin/api-admin-investment.ts

Utils

utils/pickFile.ts

### No Issue
2026-01-20 17:41:02 +08:00
0262423c50 Fix notification to report comment
Fix:
User – Forum (Reporting & Preview)

app/(application)/(user)/forum/[id]/other-report-commentar.tsx

app/(application)/(user)/forum/[id]/other-report-posting.tsx

app/(application)/(user)/forum/[id]/preview-report-posting.tsx

app/(application)/(user)/forum/[id]/report-commentar.tsx

app/(application)/(user)/forum/[id]/report-posting.tsx

Admin – Forum Moderation

app/(application)/admin/forum/[id]/list-report-comment.tsx

app/(application)/admin/forum/report-posting.tsx

Layout

app/(application)/(user)/_layout.tsx

API Client & Admin

service/api-admin/api-admin-forum.ts

service/api-client/api-forum.ts

service/api-client/api-master.ts

Utils

utils/badWordsIndonesia.ts

### No Issue
2026-01-19 17:46:54 +08:00
c2682246d6 Fix notifikasi forum
FixL
 modified:   app/(application)/(user)/_layout.tsx
        modified:   app/(application)/(user)/forum/[id]/index.tsx
        modified:   app/(application)/(user)/forum/create.tsx
        modified:   app/(application)/admin/forum/[id]/list-report-posting.tsx
        modified:   screens/Home/tabsList.ts
        modified:   service/api-admin/api-admin-forum.ts
        modified:   service/api-client/api-forum.ts

Add:
 app/(application)/(user)/forum/[id]/preview-report-posting.tsx
        types/type-forum.ts

### No Issue
2026-01-17 15:59:00 +08:00
465e01015e Penambahan notifikasi untuk fitur voting
Fix:
- app/(application)/(user)/event/[id]/publish.tsx
- app/(application)/(user)/voting/(tabs)/_layout.tsx
- app/(application)/(user)/voting/(tabs)/status.tsx
- app/(application)/(user)/voting/[id]/index.tsx
- app/(application)/(user)/voting/create.tsx
- app/(application)/admin/voting/[id]/[status]/index.tsx
- app/(application)/admin/voting/[id]/[status]/reject-input.tsx
- screens/Admin/Event/funUpdateStatus.ts
- screens/Admin/Voting/funUpdateStatus.ts
- service/api-admin/api-admin-voting.ts
- types/type-collect-other.ts

### No Issue
2026-01-15 17:39:39 +08:00
3b15871ad4 Notifikasi join event
Fix:
modified:   app/(application)/(user)/event/[id]/history.tsx
        modified:   app/(application)/(user)/event/[id]/publish.tsx
        modified:   app/(application)/(user)/event/create.tsx
        modified:   app/(application)/(user)/notifications/index.tsx

### No Issue
2026-01-15 13:54:14 +08:00
9123e73606 Fix bug pada beberapa halaman
Fix:
modified:   app.config.js
        modified:   app/(application)/(user)/donation/[id]/[status]/detail.tsx
        modified:   app/(application)/(user)/investment/(tabs)/my-holding.tsx
        modified:   app/(application)/(user)/investment/[id]/[status]/detail.tsx
        modified:   app/(application)/(user)/investment/[id]/index.tsx
        modified:   app/(application)/admin/event/[id]/[status]/index.tsx
        modified:   components/DateInput/DateTimePickerCustom.tsx
        modified:   ios/HIPMIBadungConnect/Info.plist
        modified:   screens/Invesment/BoxProgressSection.tsx

### No Issue
2026-01-14 17:41:20 +08:00
6e2046467f Penerapan notifikasi di event
Add:
components/Button/BackButtonFromNotification.tsx
types/type-collect-other.ts

Fix:
- android/app/build.gradle
- app/(application)/(user)/_layout.tsx
- app/(application)/(user)/event/(tabs)/_layout.tsx
- app/(application)/(user)/event/(tabs)/status.tsx
- app/(application)/(user)/event/create.tsx
- app/(application)/(user)/job/(tabs)/_layout.tsx
- app/(application)/(user)/notifications/index.tsx
- app/(application)/admin/event/[id]/[status]/index.tsx
- app/(application)/admin/event/[id]/reject-input.tsx
- app/(application)/admin/notification/index.tsx
- components/Notification/NotificationInitializer.tsx
- hipmi-note.md
- hooks/use-notification-store.tsx
- screens/Admin/Event/funUpdateStatus.ts
- service/api-notifications.ts
- utils/formatChatTime.ts

### No Issue
2026-01-13 17:41:30 +08:00
ca33dd83bb Perbaikan untuk apple store dan push notifikasi ke preview
Fix:
- app.config.js
- app/(application)/(user)/_layout.tsx
- app/(application)/admin/notification/index.tsx
- eas.json
- ios/HIPMIBadungConnect.xcodeproj/project.pbxproj
- ios/HIPMIBadungConnect/Info.plist

### No Issue
2026-01-12 17:40:15 +08:00
ea3fbdc541 Penambahan metode login dengan menerapkan EULA di awal
Add:
app/eula.tsx
screens/Authentication/EULAView.tsx

Fix:
- context/AuthContext.tsx
- screens/Authentication/RegisterView.tsx
- screens/RootLayout/AppRoot.tsx
- service/api-config.ts

### No Issue
2026-01-09 17:44:13 +08:00
33cd47aaed Fix mobile notification:
> - Bug penerima pesan 2 kali

Fix:
modified:   hooks/use-foreground-notifications.ts

### No Issue
2026-01-09 14:46:21 +08:00
57ac1eb45e Notifikasi admin to user
Fix:
- android/app/src/main/AndroidManifest.xml
- app/(application)/(user)/job/(tabs)/_layout.tsx
- app/(application)/(user)/job/[id]/index.tsx
- app/(application)/(user)/notifications/index.tsx
- app/(application)/(user)/profile/[id]/index.tsx
- app/(application)/admin/job/[id]/[status]/index.tsx
- app/(application)/admin/notification/index.tsx
- app/+not-found.tsx
- ios/HIPMIBadungConnect.xcodeproj/project.pbxproj
- screens/Admin/Job/funUpdateStatus.ts
- screens/Home/bottomFeatureSection.tsx

### No Issue
2026-01-08 17:48:53 +08:00
145ad73616 Fix job notifikasi
###  No Issue
2026-01-08 10:12:53 +08:00
7c85e35c61 Note:
- Fitur notifikasi ke admin dari user baru
- Notifikasi ke user bahwa akunnya telah terverifikasi

Fix:
- app/(application)/(user)/notifications/index.tsx
- app/(application)/(user)/waiting-room.tsx
- app/(application)/admin/super-admin/[id]/index.tsx
- app/(application)/admin/user-access/[id]/index.tsx
- context/AuthContext.tsx
- screens/Home/tabsList.ts
- service/api-admin/api-admin-user-access.ts
- service/api-device-token.ts
- service/api-notifications.ts
- types/type-notification-category.ts

Add:
- lib/routeApp.ts

### No Issue
2026-01-06 12:27:30 +08:00
110 changed files with 2335 additions and 771 deletions

View File

@@ -100,7 +100,7 @@ packagingOptions {
applicationId 'com.bip.hipmimobileapp'
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 3
versionCode 4
versionName "1.0.1"
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""

View File

@@ -1,6 +1,4 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.CAMERA"/>
@@ -17,36 +15,13 @@ xmlns:tools="http://schemas.android.com/tools">
<data android:scheme="https"/>
</intent>
</queries>
<application
android:name=".MainApplication"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="true"
android:theme="@style/AppTheme"
android:supportsRtl="true"
android:enableOnBackInvokedCallback="false"
android:fullBackupContent="@xml/secure_store_backup_rules"
android:dataExtractionRules="@xml/secure_store_data_extraction_rules">
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="@color/notification_icon_color"
tools:replace="android:resource"/>
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/notification_icon"/>
<meta-data
android:name="expo.modules.notifications.default_notification_color"
android:resource="@color/notification_icon_color"/>
<meta-data
android:name="expo.modules.notifications.default_notification_icon"
android:resource="@drawable/notification_icon"/>
<meta-data
android:name="expo.modules.updates.ENABLED"
android:value="false"/>
<meta-data
android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH"
android:value="ALWAYS"/>
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme" android:supportsRtl="true" android:enableOnBackInvokedCallback="false" android:fullBackupContent="@xml/secure_store_backup_rules" android:dataExtractionRules="@xml/secure_store_data_extraction_rules">
<meta-data android:name="com.google.firebase.messaging.default_notification_color" android:resource="@color/notification_icon_color" tools:replace="android:resource"/>
<meta-data android:name="com.google.firebase.messaging.default_notification_icon" android:resource="@drawable/notification_icon"/>
<meta-data android:name="expo.modules.notifications.default_notification_color" android:resource="@color/notification_icon_color"/>
<meta-data android:name="expo.modules.notifications.default_notification_icon" android:resource="@drawable/notification_icon"/>
<meta-data android:name="expo.modules.updates.ENABLED" android:value="false"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>
<activity android:name=".MainActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|uiMode" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:exported="true" android:screenOrientation="portrait">
<intent-filter>

View File

@@ -21,7 +21,7 @@ export default {
"Aplikasi membutuhkan akses lokasi untuk menampilkan peta.",
},
associatedDomains: ["applinks:cld-dkr-staging-hipmi.wibudev.com"],
buildNumber: "17",
buildNumber: "20",
},
android: {
@@ -32,7 +32,7 @@ export default {
},
edgeToEdgeEnabled: true,
package: "com.bip.hipmimobileapp",
versionCode: 3,
versionCode: 4,
// softwareKeyboardLayoutMode: 'resize', // option: untuk mengatur keyboard pada room chst collaboration
intentFilters: [
{

View File

@@ -1,8 +1,10 @@
import { CenterCustom, TextCustom, ViewWrapper } from "@/components";
import API_STRORAGE from "@/constants/base-url-api-strorage";
import { MainColor } from "@/constants/color-palet";
import { Image } from "expo-image";
import { useLocalSearchParams } from "expo-router";
import React, { useState } from "react";
import { View } from "react-native";
export default function PreviewImage() {
const { id } = useLocalSearchParams();
@@ -11,18 +13,48 @@ export default function PreviewImage() {
return (
<ViewWrapper>
{id ? (
<Image
onLoad={() => {
setIsLoading(false);
<View
style={{
width: "100%",
height: "100%",
position: "relative",
}}
source={
isLoading
? require("@/assets/images/loading.gif")
: API_STRORAGE.GET({ fileId: id as string })
}
contentFit="contain"
style={{ width: "100%", height: "100%" }}
/>
>
{/* Main Image */}
<Image
onLoad={() => {
setIsLoading(false);
}}
source={API_STRORAGE.GET({ fileId: id as string })}
contentFit="contain"
style={{ width: "100%", height: "100%" }}
// placeholder={require("@/assets/images/loading.gif")}
/>
{/* Custom Loader Overlay */}
{isLoading && (
<View
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
justifyContent: "center",
alignItems: "center",
backgroundColor: MainColor.darkblue,
zIndex: 1,
opacity: 0.5,
}}
>
<Image
source={require("@/assets/images/loading.gif")}
contentFit="contain"
style={{ width: 60, height: 60 }}
/>
</View>
)}
</View>
) : (
<CenterCustom>
<TextCustom>File not found</TextCustom>

View File

@@ -53,30 +53,35 @@ export default function UserLayout() {
/>
{/* ========== Notification Section ========= */}
<Stack.Screen
{/* DIPINDAH DI FILE NOTIFICATION USER */}
{/* <Stack.Screen
name="notifications/index"
options={{
title: "Notifikasi",
headerLeft: () => <BackButton />,
headerRight: () => (
<IconPlus
color={MainColor.yellow}
onPress={() => router.push("/test-notifications")}
/>
),
// headerRight: () => (
// <IconPlus
// color={MainColor.yellow}
// onPress={() => router.push("/test-notifications")}
// />
// ),
}}
/>
/> */}
{/* ========== Event Section ========= */}
<Stack.Screen
name="event/(tabs)"
options={{
title: "Event",
headerLeft: () => (
<LeftButtonCustom path="/(application)/(user)/home" />
),
// NOTE: DIPINDAH DI FILE /Event/(Tabs)/_layout.tsx
// headerLeft: () => (
// <LeftButtonCustom path="/(application)/(user)/home" />
// ),
}}
/>
<Stack.Screen
name="event/create"
options={{
@@ -519,7 +524,8 @@ export default function UserLayout() {
name="job/(tabs)"
options={{
title: "Job Vacancy",
headerLeft: () => <BackButton path="/home" />,
// headerLeft: () => <BackButton path="/home" />,
// NOTE: headerLeft di pindahkan ke Tabs Layout
}}
/>
<Stack.Screen
@@ -610,6 +616,20 @@ export default function UserLayout() {
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="forum/[id]/preview-report-posting"
options={{
title: "Laporan Postingan",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="forum/[id]/preview-report-comment"
options={{
title: "Laporan Komentar",
headerLeft: () => <BackButton />,
}}
/>
{/* ========== Maps Section ========= */}
<Stack.Screen

View File

@@ -26,7 +26,6 @@ export default function DonationBeranda() {
const response = await apiDonationGetAll({
category: "beranda"
});
console.log("[RES GET ALL]", JSON.stringify(response.data, null, 2));
setList(response.data);
} catch (error) {

View File

@@ -16,6 +16,7 @@ import { Href, router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { View } from "react-native";
import Toast from "react-native-toast-message";
export default function DonationMyDonation() {
const { user } = useAuth();
@@ -25,20 +26,25 @@ export default function DonationMyDonation() {
useFocusEffect(
useCallback(() => {
onLoadData();
}, [user?.id])
}, [user?.id]),
);
const onLoadData = async () => {
if (!user?.id) {
Toast.show({
type: "error",
text1: "Load data gagal, user tidak ditemukan",
});
return;
}
try {
setLoadList(true);
const response = await apiDonationGetAll({
category: "my-donation",
authorId: user?.id,
});
console.log(
"[RES GET MY DONATION]",
JSON.stringify(response.data, null, 2)
);
setList(response.data);
} catch (error) {

View File

@@ -9,14 +9,16 @@ import { useAuth } from "@/hooks/use-auth";
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
import Donasi_BoxStatus from "@/screens/Donation/BoxStatus";
import { apiDonationGetByStatus } from "@/service/api-client/api-donation";
import { useFocusEffect } from "expo-router";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function DonationStatus() {
const { user } = useAuth();
const { status } = useLocalSearchParams<{ status?: string }>();
const [activeCategory, setActiveCategory] = useState<string | null>(
"publish"
status || "publish",
);
const [listData, setListData] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState(false);
@@ -24,7 +26,7 @@ export default function DonationStatus() {
useFocusEffect(
useCallback(() => {
onLoadList();
}, [activeCategory])
}, [activeCategory]),
);
const onLoadList = async () => {

View File

@@ -10,21 +10,32 @@ import {
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { LOCAL_STORAGE_KEY } from "@/constants/local-storage-key";
import { useAuth } from "@/hooks/use-auth";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { Ionicons } from "@expo/vector-icons";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import Toast from "react-native-toast-message";
export default function InvestmentInputDonation() {
const { user } = useAuth();
const { id } = useLocalSearchParams();
const [nominal, setNominal] = useState<number>(0);
const handlerSubmit = async () => {
if (!user?.id) {
Toast.show({
type: "error",
text1: "User tidak ditemukan",
});
return;
}
try {
await AsyncStorage.setItem(
LOCAL_STORAGE_KEY.transactionDonation,
JSON.stringify({ nominal: nominal.toString() })
JSON.stringify({ nominal: nominal.toString() }),
);
router.replace(`/donation/${id}/select-bank`);
} catch (error) {

View File

@@ -16,6 +16,7 @@ import Donation_ComponentBoxDetailData from "@/screens/Donation/ComponentBoxDeta
import Donation_ComponentStoryFunrising from "@/screens/Donation/ComponentStoryFunrising";
import Donation_ProgressSection from "@/screens/Donation/ProgressSection";
import { apiDonationGetOne } from "@/service/api-client/api-donation";
import { countDownAndCondition } from "@/utils/countDownAndCondition";
import { FontAwesome6 } from "@expo/vector-icons";
import {
router,
@@ -24,7 +25,7 @@ import {
useLocalSearchParams,
} from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { useCallback, useEffect, useState } from "react";
export default function DonasiDetailStatus() {
const { id, status } = useLocalSearchParams();
@@ -58,6 +59,27 @@ export default function DonasiDetailStatus() {
setOpenDrawer(false);
};
const [value, setValue] = useState({
sisa: 0,
reminder: false,
});
useEffect(() => {
updateCountDown();
}, [data]);
const updateCountDown = () => {
const countDown = countDownAndCondition({
duration: data?.DonasiMaster_Durasi?.name,
publishTime: data?.publishTime,
});
setValue({
sisa: countDown.durationDay,
reminder: countDown.reminder,
});
};
return (
<>
<Stack.Screen
@@ -74,10 +96,15 @@ export default function DonasiDetailStatus() {
/>
<ViewWrapper>
<Donation_ComponentBoxDetailData
sisaHari={value.sisa}
reminder={value.reminder}
data={data}
bottomSection={
status === "publish" && (
<Donation_ProgressSection id={id as string} />
<Donation_ProgressSection
id={id as string}
progres={Number(data?.progres) || 0}
/>
)
}
/>

View File

@@ -103,7 +103,7 @@ export default function DonationCreateStory() {
type: "success",
text1: "Donasi berhasil disimpan",
});
router.replace("/donation/status");
router.replace("/donation/status?status=review");
} catch (error) {
console.log("[ERROR]", error);
} finally {

View File

@@ -4,10 +4,34 @@ import {
IconHome,
IconStatus,
} from "@/components/_Icon";
import BackButtonFromNotification from "@/components/Button/BackButtonFromNotification";
import { TabsStyles } from "@/styles/tabs-styles";
import { Tabs } from "expo-router";
import { router, Tabs, useLocalSearchParams, useNavigation } from "expo-router";
import { useLayoutEffect } from "react";
export default function EventTabsLayout() {
const navigation = useNavigation();
const { from, category } = useLocalSearchParams<{
from?: string;
category?: string;
}>();
console.log("from", from);
console.log("category", category);
// Atur header secara dinamis
useLayoutEffect(() => {
navigation.setOptions({
headerLeft: () => (
<BackButtonFromNotification
from={from as string}
category={category as string}
/>
),
});
}, [from, router, navigation]);
return (
<Tabs screenOptions={TabsStyles}>
<Tabs.Screen

View File

@@ -11,15 +11,17 @@ import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { useAuth } from "@/hooks/use-auth";
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
import { apiEventGetByStatus } from "@/service/api-client/api-event";
import { useFocusEffect } from "expo-router";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function EventStatus() {
const { user } = useAuth();
const { status } = useLocalSearchParams<{ status?: string }>();
const id = user?.id || "";
const [activeCategory, setActiveCategory] = useState<string | null>(
"publish"
status || "publish"
);
const [listData, setListData] = useState([]);
const [loadingGetData, setLoadingGetData] = useState(false);
@@ -73,7 +75,7 @@ export default function EventStatus() {
listData.map((item: any, i) => (
<BoxWithHeaderSection
key={i}
href={`/event/${item.id }/${activeCategory}/detail-event`}
href={`/event/${item.id}/${activeCategory}/detail-event`}
>
<StackCustom gap={"xs"}>
<Grid>

View File

@@ -56,7 +56,7 @@ export default function EventDetailHistory() {
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
height={250}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={menuDrawerPublishEvent({ id: id as string })}

View File

@@ -1,14 +1,15 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AlertDefaultSystem,
BackButton,
ButtonCustom,
DotButton,
DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid,
ViewWrapper,
} from "@/components";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
import LeftButtonCustom from "@/components/Button/BackButton";
import { useAuth } from "@/hooks/use-auth";
import Event_BoxDetailPublishSection from "@/screens/Event/BoxDetailPublishSection";
@@ -18,23 +19,26 @@ import {
apiEventGetOne,
apiEventJoin,
} from "@/service/api-client/api-event";
import dayjs from "dayjs";
import {
Redirect,
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import { useCallback, useState } from "react";
import { useCallback, useEffect, useState } from "react";
import Toast from "react-native-toast-message";
export default function EventDetailPublish() {
const { id } = useLocalSearchParams();
const now = new Date().toISOString();
const { user } = useAuth();
const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false);
const [isLoadingData, setIsLoadingData] = useState(false);
const [isLoadingJoin, setIsLoadingJoin] = useState(false);
const [data, setData] = useState();
const [data, setData] = useState<any>();
const [isParticipant, setIsParticipant] = useState<boolean | null>(null);
useFocusEffect(
@@ -55,8 +59,6 @@ export default function EventDetailPublish() {
userId: user?.id as string,
});
console.log("[RES CHECK PARTICIPANTS]", responseCheckParticipants);
if (
responseCheckParticipants.success &&
responseCheckParticipants.data
@@ -108,7 +110,24 @@ export default function EventDetailPublish() {
}
};
const footerButton = () => {
const isEventFinished =
id && data?.tanggalSelesai && dayjs(data.tanggalSelesai).isBefore(now);
useEffect(() => {
if (isEventFinished) {
router.replace(`/(application)/(user)/event/${id}/history`);
}
}, [isEventFinished, id]);
if (isEventFinished) {
return (
<ViewWrapper>
<CustomSkeleton />
</ViewWrapper>
);
}
const FooterButton = () => {
return (
<>
<ButtonCustom
@@ -138,17 +157,17 @@ export default function EventDetailPublish() {
<Stack.Screen
options={{
title: `Event Publish`,
headerLeft: () => <LeftButtonCustom />,
headerLeft: () => <BackButton onPress={() => router.back()} />,
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
}}
/>
<ViewWrapper>
{isLoadingData ? (
<LoaderCustom />
<CustomSkeleton height={400} />
) : (
<Event_BoxDetailPublishSection
data={data}
footerButton={footerButton()}
footerButton={FooterButton()}
/>
)}
</ViewWrapper>

View File

@@ -14,7 +14,7 @@ import { apiEventCreate } from "@/service/api-client/api-event";
import { apiMasterEventType } from "@/service/api-client/api-master";
import { DateTimePickerEvent } from "@react-native-community/datetimepicker";
import { router } from "expo-router";
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import Toast from "react-native-toast-message";
interface EventCreateProps {
@@ -78,23 +78,6 @@ export default function EventCreate() {
return;
}
// if (selectedDate) {
// console.log("Tanggal yang dipilih:", selectedDate);
// console.log(`ISO Format ${Platform.OS}:`, selectedDate.toString());
// // Kirim ke API atau proses lanjutan
// } else {
// console.log("Tanggal belum dipilih");
// }
// if (selectedEndDate) {
// console.log("Tanggal yang dipilih:", selectedEndDate);
// console.log(`ISO Format ${Platform.OS}:`, selectedEndDate.toString());
// // Kirim ke API atau proses lanjutan
// } else {
// console.log("Tanggal berakhir belum dipilih");
// }
try {
setIsLoading(true);
@@ -110,7 +93,7 @@ export default function EventCreate() {
const response = await apiEventCreate(newData);
console.log("Response", JSON.stringify(response, null, 2));
router.replace("/event/status");
router.replace("/event/status?status=review");
} catch (error) {
console.log(error);
} finally {

View File

@@ -19,34 +19,20 @@ import {
apiForumGetOne,
apiForumUpdateStatus,
} from "@/service/api-client/api-forum";
import { TypeForum_CommentProps } from "@/types/type-forum";
import { isBadContent } from "@/utils/badWordsIndonesia";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useEffect, useState } from "react";
import { Alert } from "react-native";
interface CommentProps {
id: string;
isActive: boolean;
komentar: string;
createdAt: Date;
authorId: string;
Author: {
id: string;
username: string;
Profile: {
id: string;
imageId: string;
};
};
}
export default function ForumDetail() {
const { id } = useLocalSearchParams();
const { user } = useAuth();
const [openDrawer, setOpenDrawer] = useState(false);
const [data, setData] = useState<any | null>(null);
const [listComment, setListComment] = useState<CommentProps[] | null>(null);
const [listComment, setListComment] = useState<TypeForum_CommentProps[] | null>(null);
const [isLoadingComment, setLoadingComment] = useState(false);
// Status
@@ -122,6 +108,7 @@ export default function ForumDetail() {
comment: text,
authorId: user?.id,
};
try {
setLoadingComment(true);
const response = await apiForumCreateComment({

View File

@@ -6,7 +6,7 @@ import {
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import { apiForumCreateReportCommentar } from "@/service/api-client/api-master";
import { apiForumCreateReportCommentar } from "@/service/api-client/api-forum";
import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import Toast from "react-native-toast-message";

View File

@@ -6,7 +6,7 @@ import {
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import { apiForumCreateReportPosting } from "@/service/api-client/api-master";
import { apiForumCreateReportPosting } from "@/service/api-client/api-forum";
import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import Toast from "react-native-toast-message";

View File

@@ -0,0 +1,91 @@
import {
BaseBox,
NewWrapper,
Spacing,
StackCustom,
TextCustom,
} from "@/components";
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
import NoDataText from "@/components/_ShareComponent/NoDataText";
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
import { apiForumGetReportComment } from "@/service/api-client/api-forum";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function ForumPreviewReportComment() {
const { id } = useLocalSearchParams();
const [data, setData] = useState<any | null>(null);
const [listData, setListData] = useState<any | null>(null);
const [loading, setLoading] = useState<boolean>(false);
// Status
useFocusEffect(
useCallback(() => {
onLoadData(id as string);
}, [id])
);
const onLoadData = async (id: string) => {
try {
setLoading(true);
const response = await apiForumGetReportComment({ id });
setData(response.data);
setListData(response?.data?.Forum_ReportKomentar);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoading(false);
}
};
return (
<>
<NewWrapper>
<StackCustom>
<TextCustom color="red" bold>
Komentar anda telah melanggar aturan forum ! Admin mengambil
tindakan untuk menghapus komentar anda!
</TextCustom>
{loading ? (
<CustomSkeleton height={100} />
) : (
<BaseBox>
<TextCustom>"{data?.komentar ? data?.komentar : "-"}"</TextCustom>
</BaseBox>
)}
</StackCustom>
<Spacing height={10} />
<TextCustom bold>Beberapa laporan yang telah diterima</TextCustom>
<Spacing height={10} />
{loading ? (
<ListSkeletonComponent />
) : _.isEmpty(listData) ? (
<NoDataText />
) : (
listData?.map((e: any, index: number) => (
<BaseBox key={index}>
{e?.deskripsi ? (
<StackCustom gap={"sm"}>
<TextCustom bold>Laporan Lainnya</TextCustom>
<TextCustom>{e?.deskripsi}</TextCustom>
</StackCustom>
) : (
<StackCustom gap={"sm"}>
<TextCustom bold>
{e?.ForumMaster_KategoriReport?.title}
</TextCustom>
<TextCustom>
{e?.ForumMaster_KategoriReport?.deskripsi}
</TextCustom>
</StackCustom>
)}
</BaseBox>
))
)}
</NewWrapper>
</>
);
}

View File

@@ -0,0 +1,91 @@
import {
BaseBox,
NewWrapper,
Spacing,
StackCustom,
TextCustom,
} from "@/components";
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
import NoDataText from "@/components/_ShareComponent/NoDataText";
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
import { apiForumGetReportPosting } from "@/service/api-client/api-forum";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function ForumPreviewReportPosting() {
const { id } = useLocalSearchParams();
const [data, setData] = useState<any | null>(null);
const [listData, setListData] = useState<any | null>(null);
const [loading, setLoading] = useState<boolean>(false);
// Status
useFocusEffect(
useCallback(() => {
onLoadData(id as string);
}, [id])
);
const onLoadData = async (id: string) => {
try {
setLoading(true);
const response = await apiForumGetReportPosting({ id });
setData(response.data);
setListData(response?.data?.Forum_ReportPosting);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoading(false);
}
};
return (
<>
<NewWrapper>
<StackCustom>
<TextCustom color="red" bold>
Postingan anda telah melanggar aturan forum ! Admin mengambil
tindakan untuk menghapus komentar anda!
</TextCustom>
{loading ? (
<CustomSkeleton height={100} />
) : (
<BaseBox>
<TextCustom>"{data?.diskusi ? data?.diskusi : "-"}"</TextCustom>
</BaseBox>
)}
</StackCustom>
<Spacing height={10} />
<TextCustom bold>Beberapa laporan yang telah diterima</TextCustom>
<Spacing height={10} />
{loading ? (
<ListSkeletonComponent />
) : _.isEmpty(listData) ? (
<NoDataText />
) : (
listData?.map((e: any) => (
<BaseBox key={e?.id}>
{e?.deskripsi ? (
<StackCustom gap={"sm"}>
<TextCustom bold>Laporan Lainnya</TextCustom>
<TextCustom>{e?.deskripsi}</TextCustom>
</StackCustom>
) : (
<StackCustom gap={"sm"}>
<TextCustom bold>
{e?.ForumMaster_KategoriReport?.title}
</TextCustom>
<TextCustom>
{e?.ForumMaster_KategoriReport?.deskripsi}
</TextCustom>
</StackCustom>
)}
</BaseBox>
))
)}
</NewWrapper>
</>
);
}

View File

@@ -8,7 +8,8 @@ import {
import { AccentColor, MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import Forum_ReportListSection from "@/screens/Forum/ReportListSection";
import { apiForumCreateReportCommentar, apiMasterForumReportList } from "@/service/api-client/api-master";
import { apiForumCreateReportCommentar } from "@/service/api-client/api-forum";
import { apiMasterForumReportList } from "@/service/api-client/api-master";
import { router, useLocalSearchParams } from "expo-router";
import { useState, useEffect } from "react";
import Toast from "react-native-toast-message";

View File

@@ -9,8 +9,8 @@ import {
import { AccentColor, MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import Forum_ReportListSection from "@/screens/Forum/ReportListSection";
import { apiForumCreateReportPosting } from "@/service/api-client/api-forum";
import {
apiForumCreateReportPosting,
apiMasterForumReportList,
} from "@/service/api-client/api-master";
import { router, useLocalSearchParams } from "expo-router";

View File

@@ -2,15 +2,14 @@ import {
BoxButtonOnFooter,
ButtonCustom,
TextAreaCustom,
ViewWrapper
ViewWrapper,
} from "@/components";
import AlertWarning from "@/components/Alert/AlertWarning";
import { useAuth } from "@/hooks/use-auth";
import { apiForumCreate } from "@/service/api-client/api-forum";
import { isBadContent } from "@/utils/badWordsIndonesia";
import { censorText, isBadContent } from "@/utils/badWordsIndonesia";
import { router } from "expo-router";
import { useState } from "react";
import { Alert } from "react-native";
import Toast from "react-native-toast-message";
export default function ForumCreate() {
@@ -19,16 +18,22 @@ export default function ForumCreate() {
const [isLoading, setIsLoading] = useState(false);
const handlerSubmit = async () => {
if (isBadContent(text)) {
AlertWarning({})
if (text.trim() === "") {
AlertWarning({
title: "Lengkapi Data",
description: "Postingan tidak boleh kosong",
});
return;
}
// Bisa di sensor atau return dan tidak bisa di post
const cencorContent = censorText(text)
const newData = {
diskusi: text,
diskusi: cencorContent,
authorId: user?.id,
};
try {
setIsLoading(true);
const response = await apiForumCreate({ data: newData });
@@ -50,6 +55,7 @@ export default function ForumCreate() {
const buttonFooter = (
<BoxButtonOnFooter>
<ButtonCustom
disabled={!text.trim() || isLoading}
isLoading={isLoading}
onPress={() => {
handlerSubmit();

View File

@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable react-hooks/exhaustive-deps */
import { ButtonCustom, StackCustom, ViewWrapper } from "@/components";
import { StackCustom, ViewWrapper } from "@/components";
import { MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import { useNotificationStore } from "@/hooks/use-notification-store";
@@ -23,14 +23,12 @@ export default function Application() {
const [refreshing, setRefreshing] = useState(false);
const { syncUnreadCount } = useNotificationStore();
useFocusEffect(
useCallback(() => {
onLoadData();
checkVersion();
userData(token as string);
syncUnreadCount()
syncUnreadCount();
}, [user?.id, token])
);
@@ -56,10 +54,10 @@ export default function Application() {
setRefreshing(false);
}, []);
if (user && user?.termsOfServiceAccepted === false) {
console.log("User is not accept term service");
return <Redirect href={`/terms-agreement`} />;
}
// if (user && user?.termsOfServiceAccepted === false) {
// console.log("User is not accept term service");
// return <Redirect href={`/terms-agreement`} />;
// }
if (data && data?.active === false) {
console.log("User is not active");

View File

@@ -1,9 +1,33 @@
import BackButtonFromNotification from "@/components/Button/BackButtonFromNotification";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { TabsStyles } from "@/styles/tabs-styles";
import { Feather, FontAwesome6, Ionicons } from "@expo/vector-icons";
import { Tabs } from "expo-router";
import { router, Tabs, useLocalSearchParams, useNavigation } from "expo-router";
import { useLayoutEffect } from "react";
export default function InvestmentTabsLayout() {
// const navigation = useNavigation();
// const { from, category } = useLocalSearchParams<{
// from?: string;
// category?: string;
// }>();
// console.log("from", from);
// console.log("category", category);
// // Atur header secara dinamis
// useLayoutEffect(() => {
// navigation.setOptions({
// headerLeft: () => (
// <BackButtonFromNotification
// from={from as string}
// category={category as string}
// />
// ),
// });
// }, [from, router, navigation]);
return (
<Tabs screenOptions={TabsStyles}>
<Tabs.Screen

View File

@@ -11,9 +11,7 @@ import {
} from "@/components";
import NoDataText from "@/components/_ShareComponent/NoDataText";
import { useAuth } from "@/hooks/use-auth";
import {
apiInvestmentGetAll
} from "@/service/api-client/api-investment";
import { apiInvestmentGetAll } from "@/service/api-client/api-investment";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
@@ -63,37 +61,20 @@ export default function InvestmentMyHolding() {
router.push(`/investment/${item?.id}/(my-holding)/${item?.id}`)
}
>
<Grid>
<Grid.Col span={6}>
<StackCustom gap={"xs"}>
<TextCustom truncate={2}>{item?.title}</TextCustom>
<Spacing height={5} />
<TextCustom size="small">
Rp. {formatCurrencyDisplay(item?.nominal)}
</TextCustom>
<TextCustom size="small">
{item?.lembarTerbeli} Lembar
</TextCustom>
</StackCustom>
</Grid.Col>
<Grid.Col span={1}>
<View />
</Grid.Col>
<Grid.Col
span={5}
style={{
justifyContent: "center",
alignItems: "center",
}}
>
<ProgressCustom
value={item?.progress}
label={`${item?.progress}%`}
size="lg"
/>
</Grid.Col>
</Grid>
<StackCustom>
<TextCustom truncate={2}>{item?.title}</TextCustom>
<TextCustom>
Rp. {formatCurrencyDisplay(item?.nominal)}
</TextCustom>
<TextCustom>{item?.lembarTerbeli} Lembar</TextCustom>
<ProgressCustom
label={`${item.progress}%`}
value={Number(item.progress)}
size="lg"
animated
color="primary"
/>
</StackCustom>
</BaseBox>
))
)}

View File

@@ -9,14 +9,16 @@ import { useAuth } from "@/hooks/use-auth";
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
import Investment_StatusBox from "@/screens/Invesment/StatusBox";
import { apiInvestmentGetByStatus } from "@/service/api-client/api-investment";
import { useFocusEffect } from "expo-router";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function InvestmentPortofolio() {
const { user } = useAuth();
const { status } = useLocalSearchParams<{ status?: string }>();
const [activeCategory, setActiveCategory] = useState<string | null>(
"publish"
status || "publish"
);
const [listData, setListData] = useState<any[]>([]);

View File

@@ -115,7 +115,11 @@ export default function InvestmentAddNews() {
onChangeText={(value) => setData({ ...data, deskripsi: value })}
/>
<ButtonCustom isLoading={isLoading} onPress={handlerSubmit}>
<ButtonCustom
disabled={!data.title || !data.deskripsi || isLoading}
isLoading={isLoading}
onPress={handlerSubmit}
>
Simpan
</ButtonCustom>
</StackCustom>

View File

@@ -27,7 +27,6 @@ import Toast from "react-native-toast-message";
export default function InvestmentInvoice() {
const { id } = useLocalSearchParams();
console.log("[ID]", id);
const [data, setData] = useState<any>({});
const [image, setImage] = useState<IFileData>({
name: "",
@@ -49,7 +48,6 @@ export default function InvestmentInvoice() {
category: "invoice",
});
console.log("[RES INVOICE]", JSON.stringify(response.data, null, 2));
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
@@ -64,8 +62,6 @@ export default function InvestmentInvoice() {
imageUri: image?.uri,
});
console.log("[RESPONSE UPLOAD IMAGE]", responseUploadImage);
if (!responseUploadImage?.data?.id) {
Toast.show({
type: "error",
@@ -83,10 +79,6 @@ export default function InvestmentInvoice() {
});
if (response.success) {
console.log(
"[RESPONSE UPDATE]",
JSON.stringify(response.data, null, 2)
);
Toast.show({
type: "success",
text1: "Berhasil mengunggah bukti transfer",
@@ -210,7 +202,6 @@ export default function InvestmentInvoice() {
pickFile({
allowedType: "image",
setImageUri(file: any) {
console.log("[IMAGE]", file);
setImage(file);
},
});
@@ -224,7 +215,7 @@ export default function InvestmentInvoice() {
<ButtonCustom
isLoading={isLoading}
disabled={!image}
disabled={!image || isLoading}
onPress={() => {
handlerSubmitUpdate();
}}

View File

@@ -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 InvestmentDetailStatus() {
const { user } = useAuth();
@@ -63,6 +64,29 @@ export default function InvestmentDetailStatus() {
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={data?.id}
@@ -72,7 +96,11 @@ export default function InvestmentDetailStatus() {
);
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 (

View File

@@ -18,10 +18,7 @@ import {
apiInvestmentUpdateData,
} from "@/service/api-client/api-investment";
import { apiMasterInvestment } from "@/service/api-client/api-master";
import {
deleteFileService,
uploadFileService,
} from "@/service/upload-service";
import { deleteFileService, uploadFileService } from "@/service/upload-service";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import pickFile from "@/utils/pickFile";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
@@ -70,7 +67,7 @@ export default function InvestmentEdit() {
useCallback(() => {
onLoadMaster();
onLoadData();
}, [id])
}, [id]),
);
const onLoadMaster = async () => {
@@ -178,7 +175,7 @@ export default function InvestmentEdit() {
const responseUpdate = await apiInvestmentUpdateData({
id: id as string,
data: newData,
category: "data"
category: "data",
});
if (responseUpdate.success) {
@@ -256,6 +253,7 @@ export default function InvestmentEdit() {
/>
<TextInputCustom
disabled
required
placeholder="0"
label="Total Lembar"

View File

@@ -63,30 +63,28 @@ export default function InvestmentDetail() {
setOpenDrawerPublish(false);
};
const [value, setValue] = useState({
sisa: 0,
reminder: 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,
});
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,
});
};
setValue({
sisa: countDown.durationDay,
reminder: countDown.reminder,
});
};
const bottomSection = (
<Invesment_ComponentBoxOnBottomDetail
@@ -97,7 +95,11 @@ export default function InvestmentDetail() {
);
const buttonSection = (
<Investment_ButtonInvestasiSection id={id as string} isMine={user?.id === data?.author?.id} reminder={value.reminder} />
<Investment_ButtonInvestasiSection
id={id as string}
isMine={user?.id === data?.author?.id}
reminder={value.reminder}
/>
);
return (

View File

@@ -54,7 +54,7 @@ export default function InvestmentCreate() {
useFocusEffect(
useCallback(() => {
onLoadMaster();
}, [])
}, []),
);
const onLoadMaster = async () => {
@@ -167,7 +167,7 @@ export default function InvestmentCreate() {
text1: "Berhasil",
text2: response.message,
});
router.replace("/investment/portofolio");
router.replace("/investment/portofolio?status=review");
} else {
Toast.show({
type: "error",
@@ -224,7 +224,6 @@ export default function InvestmentCreate() {
onPress={() => {
pickFile({
setPdfUri: ({ uri, name, size }) => {
setPdf({ uri, name, size });
},
allowedType: "pdf",
@@ -265,6 +264,7 @@ export default function InvestmentCreate() {
<StackCustom gap={0}>
<TextInputCustom
disabled
required
placeholder="0"
label="Total Lembar"
@@ -357,7 +357,11 @@ export default function InvestmentCreate() {
)}
<Spacing />
<ButtonCustom isLoading={isLoading} onPress={() => handleSubmit()}>
<ButtonCustom
disabled={isLoading}
isLoading={isLoading}
onPress={() => handleSubmit()}
>
Simpan
</ButtonCustom>
</StackCustom>

View File

@@ -1,34 +1,61 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { BackButton } from "@/components";
import { IconHome, IconStatus } from "@/components/_Icon";
import BackButtonFromNotification from "@/components/Button/BackButtonFromNotification";
import { TabsStyles } from "@/styles/tabs-styles";
import { Ionicons } from "@expo/vector-icons";
import { Tabs } from "expo-router";
import {
router,
Tabs,
useLocalSearchParams,
useNavigation
} from "expo-router";
import { useLayoutEffect } from "react";
export default function JobTabsLayout() {
const navigation = useNavigation();
const { from, category } = useLocalSearchParams<{
from?: string;
category?: string;
}>();
// Atur header secara dinamis
useLayoutEffect(() => {
navigation.setOptions({
headerLeft: () => (
<BackButtonFromNotification from={from as string} category={category as string} />
),
});
}, [from, router, navigation]);
return (
<Tabs screenOptions={TabsStyles}>
<Tabs.Screen
name="index"
options={{
title: "Beranda",
tabBarIcon: ({ color }) => <IconHome color={color} />,
}}
/>
<Tabs.Screen
name="status"
options={{
title: "Status",
tabBarIcon: ({ color }) => <IconStatus color={color} />,
}}
/>
<Tabs.Screen
name="archive"
options={{
title: "Arsip",
tabBarIcon: ({ color }) => (
<Ionicons size={20} name="archive" color={color} />
),
}}
/>
</Tabs>
<>
<Tabs screenOptions={TabsStyles}>
<Tabs.Screen
name="index"
options={{
title: "Beranda",
tabBarIcon: ({ color }) => <IconHome color={color} />,
}}
/>
<Tabs.Screen
name="status"
options={{
title: "Status",
tabBarIcon: ({ color }) => <IconStatus color={color} />,
}}
/>
<Tabs.Screen
name="archive"
options={{
title: "Arsip",
tabBarIcon: ({ color }) => (
<Ionicons size={20} name="archive" color={color} />
),
}}
/>
</Tabs>
</>
);
}

View File

@@ -9,14 +9,17 @@ import {
import { useAuth } from "@/hooks/use-auth";
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
import { apiJobGetByStatus } from "@/service/api-client/api-job";
import { useFocusEffect } from "expo-router";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function JobStatus() {
const { user } = useAuth();
const { status } = useLocalSearchParams<{ status?: string }>();
console.log("STATUS", status);
const [activeCategory, setActiveCategory] = useState<string | null>(
"publish"
status || "publish"
);
const [listData, setListData] = useState<any[]>([]);
const [isLoadList, setIsLoadList] = useState(false);
@@ -60,25 +63,29 @@ export default function JobStatus() {
);
return (
<ViewWrapper headerComponent={scrollComponent} hideFooter>
{isLoadList ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada data {activeCategory}</TextCustom>
) : (
listData.map((e, i) => (
<BaseBox
key={i}
paddingTop={20}
paddingBottom={20}
href={`/job/${e?.id}/${activeCategory}/detail`}
>
<TextCustom align="center" bold truncate size="large">
{e?.title}
</TextCustom>
</BaseBox>
))
)}
</ViewWrapper>
<>
<ViewWrapper headerComponent={scrollComponent} hideFooter>
{isLoadList ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">
Tidak ada data {activeCategory}
</TextCustom>
) : (
listData.map((e, i) => (
<BaseBox
key={i}
paddingTop={20}
paddingBottom={20}
href={`/job/${e?.id}/${activeCategory}/detail`}
>
<TextCustom align="center" bold truncate size="large">
{e?.title}
</TextCustom>
</BaseBox>
))
)}
</ViewWrapper>
</>
);
}

View File

@@ -74,7 +74,7 @@ export default function JobDetailStatus() {
<StackCustom gap={"xs"}>
{data &&
data?.catatan &&
(status === "draft" || status === "rejected") && (
(status === "draft" || status === "reject") && (
<ReportBox text={data?.catatan} />
)}

View File

@@ -25,6 +25,8 @@ export default function JobDetail() {
setIsLoading(true);
const response = await apiJobGetOne({ id: id as string });
console.log("DATA", JSON.stringify(response.data, null,2));
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);

View File

@@ -19,7 +19,7 @@ import { useState } from "react";
import Toast from "react-native-toast-message";
export default function JobCreate() {
const nextUrl = "/(application)/(user)/job/(tabs)/status";
const nextUrl = "/(application)/(user)/job/(tabs)/status?status=review";
const { user } = useAuth();
const [isLoading, setIsLoading] = useState(false);
const [image, setImage] = useState<string | null>(null);

View File

@@ -1,26 +1,60 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AlertDefaultSystem,
BackButton,
BaseBox,
DrawerCustom,
MenuDrawerDynamicGrid,
NewWrapper,
ScrollableCustom,
StackCustom,
TextCustom
TextCustom,
} from "@/components";
import { IconDot } from "@/components/_Icon/IconComponent";
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
import { AccentColor } from "@/constants/color-palet";
import NoDataText from "@/components/_ShareComponent/NoDataText";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { useAuth } from "@/hooks/use-auth";
import { useNotificationStore } from "@/hooks/use-notification-store";
import { apiGetNotificationsById } from "@/service/api-notifications";
import { listOfcategoriesAppNotification } from "@/types/type-notification-category";
import { formatChatTime } from "@/utils/formatChatTime";
import { router, useFocusEffect } from "expo-router";
import { Ionicons } from "@expo/vector-icons";
import { router, Stack, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { RefreshControl, View } from "react-native";
const selectedCategory = (value: string) => {
const category = listOfcategoriesAppNotification.find((c) => c.value === value);
const category = listOfcategoriesAppNotification.find(
(c) => c.value === value
);
return category?.label;
};
const fixPath = ({
deepLink,
categoryApp,
}: {
deepLink: string;
categoryApp: string;
}) => {
if (categoryApp === "OTHER") {
return deepLink;
}
const separator = deepLink.includes("?") ? "&" : "?";
const fixedPath = `${deepLink}${separator}from=notifications&category=${_.lowerCase(
categoryApp
)}`;
console.log("Fix Path", fixedPath);
return fixedPath;
};
const BoxNotification = ({
data,
activeCategory,
@@ -28,18 +62,28 @@ const BoxNotification = ({
data: any;
activeCategory: string | null;
}) => {
// console.log("DATA NOTIFICATION", JSON.stringify(data, null, 2));
const { markAsRead } = useNotificationStore();
return (
<>
<BaseBox
backgroundColor={data.isRead ? AccentColor.darkblue : AccentColor.blue}
onPress={() => {
console.log(
"Notification >",
selectedCategory(activeCategory as string)
);
router.push(data.deepLink);
markAsRead(data.id);
// console.log(
// "Notification >",
// selectedCategory(activeCategory as string)
// );
const newPath = fixPath({
deepLink: data.deepLink,
categoryApp: data.kategoriApp,
});
router.navigate(newPath as any);
selectedCategory(activeCategory as string);
if (!data.isRead) {
markAsRead(data.id);
}
}}
>
<StackCustom>
@@ -60,10 +104,16 @@ const BoxNotification = ({
export default function Notifications() {
const { user } = useAuth();
const [activeCategory, setActiveCategory] = useState<string | null>("event");
const { category } = useLocalSearchParams<{ category?: string }>();
const [activeCategory, setActiveCategory] = useState<string | null>(
category || "event"
);
const [listData, setListData] = useState<any[]>([]);
const [refreshing, setRefreshing] = useState(false);
const [loading, setLoading] = useState(false);
const [openDrawer, setOpenDrawer] = useState(false);
const { markAsReadAll } = useNotificationStore();
const handlePress = (item: any) => {
setActiveCategory(item.value);
@@ -83,7 +133,7 @@ export default function Notifications() {
id: user?.id as any,
category: activeCategory as any,
});
// console.log("Response Notification", JSON.stringify(response, null, 2));
if (response.success) {
setListData(response.data);
} else {
@@ -103,31 +153,96 @@ export default function Notifications() {
};
return (
<NewWrapper
headerComponent={
<ScrollableCustom
data={listOfcategoriesAppNotification.map((e, i) => ({
id: i,
label: e.label,
value: e.value,
}))}
onButtonPress={handlePress}
activeId={activeCategory as string}
<>
<Stack.Screen
options={{
title: "Notifikasi",
headerLeft: () => <BackButton />,
headerRight: () => (
<IconDot
color={MainColor.yellow}
onPress={() => setOpenDrawer(true)}
/>
),
}}
/>
<NewWrapper
headerComponent={
<ScrollableCustom
data={listOfcategoriesAppNotification.map((e, i) => ({
id: i,
label: e.label,
value: e.value,
}))}
onButtonPress={handlePress}
activeId={activeCategory as string}
/>
}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
>
{loading ? (
<ListSkeletonComponent />
) : _.isEmpty(listData) ? (
<NoDataText text="Belum ada notifikasi" />
) : (
listData.map((e, i) => (
<View key={i}>
<BoxNotification
data={e}
activeCategory={activeCategory as any}
/>
</View>
))
)}
</NewWrapper>
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
label: "Tandai Semua Dibaca",
value: "read-all",
icon: (
<Ionicons
name="reader-outline"
size={ICON_SIZE_SMALL}
color={MainColor.white}
/>
),
path: "",
},
]}
onPressItem={(item: any) => {
console.log("Item", item.value);
if (item.value === "read-all") {
AlertDefaultSystem({
title: "Tandai Semua Dibaca",
message:
"Apakah Anda yakin ingin menandai semua notifikasi dibaca?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: () => {
markAsReadAll(user?.id as any);
const data = _.cloneDeep(listData);
data.forEach((e) => {
e.isRead = true;
});
setListData(data);
onRefresh();
setOpenDrawer(false);
},
});
}
}}
/>
}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
>
{loading ? (
<ListSkeletonComponent/>
) : (
listData.map((e, i) => (
<View key={i}>
<BoxNotification data={e} activeCategory={activeCategory as any} />
</View>
))
)}
</NewWrapper>
</DrawerCustom>
</>
);
}

View File

@@ -139,7 +139,9 @@ const ButtonnDot = ({
isUserCheck: boolean;
logout: () => Promise<void>;
}) => {
const isId = id === undefined || id === null;
console.log("[ID] >>", id);
const isId = id === undefined || id === "undefined";
if (isId) {
return (

View File

@@ -36,7 +36,7 @@ export default function ProfileLayout() {
<Stack.Screen
name="[id]/blocked-list"
options={{ title: "Blocked List", headerLeft: () => <BackButton /> }}
options={{ title: "Daftar Blokir", headerLeft: () => <BackButton /> }}
/>
<Stack.Screen

View File

@@ -1,13 +1,37 @@
import {
IconContribution,
IconHistory,
IconHome,
IconStatus,
IconContribution,
IconHistory,
IconHome,
IconStatus,
} from "@/components/_Icon";
import BackButtonFromNotification from "@/components/Button/BackButtonFromNotification";
import { TabsStyles } from "@/styles/tabs-styles";
import { Tabs } from "expo-router";
import { Tabs, useLocalSearchParams, useNavigation, router } from "expo-router";
import { useLayoutEffect } from "react";
export default function VotingTabsLayout() {
const navigation = useNavigation();
const { from, category } = useLocalSearchParams<{
from?: string;
category?: string;
}>();
console.log("from", from);
console.log("category", category);
// Atur header secara dinamis
useLayoutEffect(() => {
navigation.setOptions({
headerLeft: () => (
<BackButtonFromNotification
from={from as string}
category={category as string}
/>
),
});
}, [from, router, navigation]);
return (
<Tabs screenOptions={TabsStyles}>
<Tabs.Screen

View File

@@ -12,15 +12,17 @@ import { useAuth } from "@/hooks/use-auth";
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
import { apiVotingGetByStatus } from "@/service/api-client/api-voting";
import { dateTimeView } from "@/utils/dateTimeView";
import { useFocusEffect } from "expo-router";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function VotingStatus() {
const { user } = useAuth();
const { status } = useLocalSearchParams<{ status?: string }>();
const id = user?.id || "";
const [activeCategory, setActiveCategory] = useState<string | null>(
"publish"
status || "publish"
);
const [listData, setListData] = useState([]);
@@ -86,8 +88,14 @@ export default function VotingStatus() {
style={{ width: "70%", alignSelf: "center" }}
variant="light"
>
{item?.awalVote && dateTimeView({date: item?.awalVote, withoutTime: true})} -{" "}
{item?.akhirVote && dateTimeView({date: item?.akhirVote, withoutTime: true})}
{item?.awalVote &&
dateTimeView({
date: item?.awalVote,
withoutTime: true,
})}{" "}
-{" "}
{item?.akhirVote &&
dateTimeView({ date: item?.akhirVote, withoutTime: true })}
</BadgeCustom>
</StackCustom>
</BaseBox>

View File

@@ -13,6 +13,7 @@ import {
} from "@/components";
import { IconArchive, IconContribution } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
import { useAuth } from "@/hooks/use-auth";
import Voting_BoxDetailHasilVotingSection from "@/screens/Voting/BoxDetailHasilVotingSection";
import { Voting_BoxDetailPublishSection } from "@/screens/Voting/BoxDetailPublishSection";
@@ -22,13 +23,14 @@ import {
apiVotingUpdateData,
} from "@/service/api-client/api-voting";
import { today } from "@/utils/dateTimeView";
import dayjs from "dayjs";
import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import React, { useCallback, useState } from "react";
import React, { useCallback, useEffect, useState } from "react";
import Toast from "react-native-toast-message";
export default function VotingDetail() {
@@ -119,6 +121,23 @@ export default function VotingDetail() {
setOpenDrawerPublish(false);
};
const now = new Date().toISOString();
const isEventFinished = id && data?.akhirVote && dayjs(data.akhirVote).isBefore(now);
useEffect(() => {
if (isEventFinished) {
router.replace(`/(application)/(user)/voting/${id}/history`);
}
}, [isEventFinished, id]);
if (isEventFinished) {
return (
<ViewWrapper>
<CustomSkeleton />
</ViewWrapper>
);
}
return (
<>
<Stack.Screen

View File

@@ -79,7 +79,7 @@ export default function VotingCreate() {
type: "success",
text1: "Data berhasil disimpan",
});
router.replace("/(application)/(user)/voting/(tabs)/status");
router.replace("/(application)/(user)/voting/(tabs)/status?status=review");
} else {
Toast.show({
type: "error",

View File

@@ -1,12 +1,10 @@
import {
AlertDefaultSystem,
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
InformationBox,
NewWrapper,
StackCustom,
ViewWrapper,
StackCustom
} from "@/components";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { useAuth } from "@/hooks/use-auth";

View File

@@ -1,4 +1,5 @@
import {
AlertDefaultSystem,
BadgeCustom,
BaseBox,
BoxButtonOnFooter,
@@ -9,6 +10,7 @@ import {
} from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
import { useAuth } from "@/hooks/use-auth";
import {
apiAdminDonationInvoiceDetailById,
apiAdminDonationInvoiceUpdateById,
@@ -22,6 +24,7 @@ import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function AdminDonasiTransactionDetail() {
const { user } = useAuth();
const { id, status } = useLocalSearchParams();
console.log("[STATUS]", id, status);
@@ -33,7 +36,7 @@ export default function AdminDonasiTransactionDetail() {
onLoadData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id])
}, [id]),
);
const onLoadData = async () => {
@@ -57,6 +60,7 @@ export default function AdminDonasiTransactionDetail() {
const newData = {
donationId: data?.donasiId,
nominal: data?.nominal,
senderId: user?.id,
};
const response = await apiAdminDonationInvoiceUpdateById({
@@ -97,7 +101,15 @@ export default function AdminDonasiTransactionDetail() {
<ButtonCustom
isLoading={isLoading}
onPress={() => {
handlerSubmit();
AlertDefaultSystem({
title: "Konfirmasi transaksi",
message: "Apakah anda yakin ingin menyetujui transaksi ini?",
textLeft: "Tidak",
textRight: "Ya",
onPressRight: () => {
handlerSubmit();
},
});
}}
>
Terima donasi
@@ -109,7 +121,7 @@ export default function AdminDonasiTransactionDetail() {
return (
<BoxButtonOnFooter>
<ButtonCustom disabled>
{data?.DonasiMaster_StatusInvoice?.name}
{data?.DonasiMaster_StatusInvoice?.name}
</ButtonCustom>
</BoxButtonOnFooter>
);
@@ -140,7 +152,7 @@ export default function AdminDonasiTransactionDetail() {
})}
>
{_.startCase(
(data?.DonasiMaster_StatusInvoice?.name as any) || "-"
(data?.DonasiMaster_StatusInvoice?.name as any) || "-",
)}
</BadgeCustom>
)) ||
@@ -157,7 +169,7 @@ export default function AdminDonasiTransactionDetail() {
<ButtonCustom
onPress={() =>
router.push(
`/(application)/(image)/preview-image/${data?.imageId}`
`/(application)/(image)/preview-image/${data?.imageId}`,
)
}
>

View File

@@ -14,7 +14,11 @@ import {
} from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import DIRECTORY_ID from "@/constants/directory-id";
import { apiAdminDonationDetailById, apiAdminDonationDisbursementOfFundsCreated } from "@/service/api-admin/api-admin-donation";
import { useAuth } from "@/hooks/use-auth";
import {
apiAdminDonationDetailById,
apiAdminDonationDisbursementOfFundsCreated,
} from "@/service/api-admin/api-admin-donation";
import { uploadFileService } from "@/service/upload-service";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import pickFile from "@/utils/pickFile";
@@ -25,7 +29,7 @@ import Toast from "react-native-toast-message";
export default function AdminDonationDisbursementOfFunds() {
const { id } = useLocalSearchParams();
const { user } = useAuth();
const [data, setData] = React.useState<any | null>(null);
const [isLoading, setIsLoading] = React.useState(false);
@@ -40,7 +44,7 @@ export default function AdminDonationDisbursementOfFunds() {
useFocusEffect(
React.useCallback(() => {
onLoadData();
}, [id])
}, [id]),
);
const onLoadData = async () => {
@@ -94,6 +98,7 @@ export default function AdminDonationDisbursementOfFunds() {
const newData = {
...value,
authorId: user?.id,
imageId: imageId,
};

View File

@@ -7,15 +7,15 @@ import {
} from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import { useAuth } from "@/hooks/use-auth";
import { funUpdateStatusDonation } from "@/screens/Admin/Donation/funDonationUpdateStatus";
import {
apiAdminDonationDetailById
} from "@/service/api-admin/api-admin-donation";
import { apiAdminDonationDetailById } from "@/service/api-admin/api-admin-donation";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import React from "react";
import Toast from "react-native-toast-message";
export default function AdminDonationRejectInput() {
const { user } = useAuth();
const { id, status } = useLocalSearchParams();
const [data, setData] = React.useState<any | null>(null);
@@ -24,7 +24,7 @@ export default function AdminDonationRejectInput() {
useFocusEffect(
React.useCallback(() => {
onLoadData();
}, [id])
}, [id]),
);
const onLoadData = async () => {
@@ -48,11 +48,23 @@ export default function AdminDonationRejectInput() {
changeStatus: "publish" | "review" | "reject";
}) => {
try {
if (!user?.id) {
Toast.show({
type: "error",
text1: "User tidak ditemukan",
});
return;
}
setIsLoading(true);
const response = await funUpdateStatusDonation({
id: id as string,
changeStatus,
data: data,
data: {
senderId: user?.id as string,
catatan: data,
},
});
if (!response.success) {
@@ -61,7 +73,7 @@ export default function AdminDonationRejectInput() {
text1: "Report gagal",
});
return
return;
}
Toast.show({

View File

@@ -41,8 +41,8 @@ export default function AdminEventDetail() {
const deepLinkURL = `${DEEP_LINK_URL}/event/${id}/confirmation?userId=${user?.id}`;
const deepLinkURLDEV = `${DEEP_LINK_URL}/--/event/${id}/confirmation?userId=${user?.id}`;
const isDevLink = process.env.NODE_ENV === "development" ? deepLinkURLDEV : deepLinkURL;
const isDevLink =
process.env.NODE_ENV === "development" ? deepLinkURLDEV : deepLinkURL;
useFocusEffect(
useCallback(() => {
@@ -126,6 +126,7 @@ export default function AdminEventDetail() {
const response = await funUpdateStatusEvent({
id: id as string,
changeStatus: "publish",
data: { catatan: "", senderId: user?.id as string },
});
if (!response.success) {

View File

@@ -7,6 +7,7 @@ import {
} from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import { useAuth } from "@/hooks/use-auth";
import { funUpdateStatusEvent } from "@/screens/Admin/Event/funUpdateStatus";
import { apiAdminEventById } from "@/service/api-admin/api-admin-event";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
@@ -14,9 +15,13 @@ import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function AdminEventRejectInput() {
const { user } = useAuth();
const { id, status } = useLocalSearchParams();
const [data, setData] = useState<any>("");
const [data, setData] = useState<any>({
catatan: "",
senderId: "",
});
const [isLoading, setIsLoading] = useState(false);
useFocusEffect(
@@ -45,10 +50,16 @@ export default function AdminEventRejectInput() {
}) => {
try {
setIsLoading(true);
const newData = {
catatan: data,
senderId: user?.id as string,
};
const response = await funUpdateStatusEvent({
id: id as string,
changeStatus,
data: data,
data: newData,
});
if (!response.success) {

View File

@@ -15,12 +15,11 @@ import { IconDot, IconView } from "@/components/_Icon/IconComponent";
import { IconTrash } from "@/components/_Icon/IconTrash";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle";
import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue";
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
import { GridSpan_NewComponent } from "@/components/_ShareComponent/GridSpan_NewComponent";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { useAuth } from "@/hooks/use-auth";
import {
apiAdminForumCommentById,
apiAdminForumDeactivateComment,
@@ -35,6 +34,7 @@ import Toast from "react-native-toast-message";
export default function AdminForumReportComment() {
const { id } = useLocalSearchParams();
const { user } = useAuth();
const [data, setData] = useState<any | null>(null);
const [listReport, setListReport] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState(false);
@@ -111,9 +111,13 @@ export default function AdminForumReportComment() {
<AdminComp_BoxTitle title="Daftar Report Komentar" />
<StackCustom gap={"sm"}>
<StackCustom gap={"sm"}>
<GridSpan_NewComponent
text1={<TextCustom bold align="center">Aksi</TextCustom>}
text1={
<TextCustom bold align="center">
Aksi
</TextCustom>
}
text2={<TextCustom bold>Pelapor</TextCustom>}
text3={<TextCustom bold>Kategori Report</TextCustom>}
/>
@@ -129,22 +133,24 @@ export default function AdminForumReportComment() {
<View key={index}>
<GridSpan_NewComponent
text1={
<CenterCustom>
<ActionIcon
icon={<IconView size={ICON_SIZE_BUTTON} color="black" />}
onPress={() => {
setOpenDrawerAction(true);
setSelectedReport({
id: item.id,
username: item.User?.username,
kategori: item.ForumMaster_KategoriReport?.title,
keterangan:
item.ForumMaster_KategoriReport?.deskripsi,
deskripsi: item.deskripsi,
});
}}
/>
</CenterCustom>
<CenterCustom>
<ActionIcon
icon={
<IconView size={ICON_SIZE_BUTTON} color="black" />
}
onPress={() => {
setOpenDrawerAction(true);
setSelectedReport({
id: item.id,
username: item.User?.username,
kategori: item.ForumMaster_KategoriReport?.title,
keterangan:
item.ForumMaster_KategoriReport?.deskripsi,
deskripsi: item.deskripsi,
});
}}
/>
</CenterCustom>
}
text2={
<TextCustom truncate={1}>
@@ -188,15 +194,18 @@ export default function AdminForumReportComment() {
onPressRight: async () => {
const deleteComment = await apiAdminForumDeactivateComment({
id: id as string,
data: {
senderId: user?.id as string,
},
});
if (!deleteComment.success) {
Toast.show({
type: "error",
text1: "Komentar gagal dihapus",
});
return;
}
// if (!deleteComment.success) {
// Toast.show({
// type: "error",
// text1: "Komentar gagal dihapus",
// });
// return;
// }
setOpenDrawer(false);
Toast.show({

View File

@@ -16,12 +16,11 @@ import { IconDot, IconView } from "@/components/_Icon/IconComponent";
import { IconTrash } from "@/components/_Icon/IconTrash";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle";
import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue";
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
import { GridSpan_NewComponent } from "@/components/_ShareComponent/GridSpan_NewComponent";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { useAuth } from "@/hooks/use-auth";
import {
apiAdminForumDeactivatePosting,
apiAdminForumListReportPostingById,
@@ -35,6 +34,7 @@ import { Divider } from "react-native-paper";
import Toast from "react-native-toast-message";
export default function AdminForumReportPosting() {
const { user } = useAuth();
const { id } = useLocalSearchParams();
const [openDrawerPage, setOpenDrawerPage] = useState(false);
const [openDrawerAction, setOpenDrawerAction] = useState(false);
@@ -215,6 +215,9 @@ export default function AdminForumReportPosting() {
onPressRight: async () => {
const response = await apiAdminForumDeactivatePosting({
id: id as string,
data: {
senderId: user?.id as string,
},
});
if (!response.success) {

View File

@@ -73,7 +73,7 @@ export default function AdminForumReportPosting() {
<GridSpan_NewComponent
text1={
<TextCustom bold truncate>
Username
Pelapor
</TextCustom>
}
text2={

View File

@@ -20,6 +20,7 @@ import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButt
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import AdminButtonReview from "@/components/_ShareComponent/Admin/ButtonReview";
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
import ReportBox from "@/components/Box/ReportBox";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
@@ -28,6 +29,7 @@ import {
apiAdminInvestmentDetailById,
} from "@/service/api-admin/api-admin-investment";
import { colorBadgeStatus } from "@/utils/colorBadge";
import { countDownAndCondition } from "@/utils/countDownAndCondition";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
@@ -40,91 +42,41 @@ export default function AdminInvestmentDetail() {
const [data, setData] = React.useState<any | null>(null);
const [isLoading, setLoading] = React.useState(false);
const [remind, setRemind] = React.useState({
sisa: 0,
reminder: false,
});
useFocusEffect(
React.useCallback(() => {
onLoadData();
}, [id])
}, [id]),
);
const onLoadData = async () => {
try {
const response = await apiAdminInvestmentDetailById({ id: id as string });
// console.log("[GETONE INVEST]", JSON.stringify(response, null, 2));
if (response.success) {
setData(response.data);
const duration = response?.data?.MasterPencarianInvestor?.name;
const publishTime = response?.data?.countDown;
const countDown = countDownAndCondition({
duration: duration,
publishTime: publishTime
});
setRemind({
sisa: countDown.durationDay,
reminder: countDown.reminder,
});
}
} catch (error) {
console.log(error);
console.log("Error", error);
}
};
const listData = [
{
label: "Username",
value: (data && data?.author?.username) || "-",
},
{
label: "Judul",
value: (data && data?.title) || "-",
},
{
label: "Status",
value:
data && data?.MasterStatusInvestasi?.name ? (
<BadgeCustom
color={colorBadgeStatus({
status: data?.MasterStatusInvestasi?.name as string,
})}
>
{_.startCase(data?.MasterStatusInvestasi?.name as string)}
</BadgeCustom>
) : (
"-"
),
},
{
label: "Dana Dibutuhkan",
value: `Rp. ${
(data && data?.targetDana && formatCurrencyDisplay(data?.targetDana)) ||
"-"
}`,
},
{
label: "Harga Perlembar",
value: `Rp. ${
(data &&
data?.hargaLembar &&
formatCurrencyDisplay(data?.hargaLembar)) ||
"-"
}`,
},
{
label: "Total Lembar",
value:
(data &&
data?.totalLembar &&
formatCurrencyDisplay(data?.totalLembar)) ||
"-",
},
{
label: "ROI",
value: `${(data && data?.roi && data?.roi) || 0} %`,
},
{
label: "Pembagian Deviden",
value: (data && data?.MasterPembagianDeviden?.name) + " bulan" || "-",
},
{
label: "Jadwal Pembagian",
value: (data && data?.MasterPeriodeDeviden?.name) || "-",
},
{
label: "Pencarian Investor",
value: (data && data?.MasterPencarianInvestor?.name) + " hari" || "-",
},
];
const handlerSubmitPublish = async () => {
try {
setLoading(true);
@@ -134,7 +86,6 @@ export default function AdminInvestmentDetail() {
data: data,
});
// console.log("[GET ON INVEST]", JSON.stringify(response, null, 2));
if (!response.success) {
Toast.show({
type: "error",
@@ -164,6 +115,16 @@ export default function AdminInvestmentDetail() {
/>
);
if (!data) {
return (
<>
<ViewWrapper>
<CustomSkeleton height={200} />
</ViewWrapper>
</>
);
}
return (
<>
<ViewWrapper
@@ -177,8 +138,8 @@ export default function AdminInvestmentDetail() {
{status === "publish" && (
<BaseBox>
<ProgressCustom
label={data && `${data.progress}%` || "0%"}
value={data && data.progress || 0}
label={(data && `${data.progress}%`) || "0%"}
value={(data && data.progress) || 0}
size="lg"
/>
<Spacing />
@@ -187,7 +148,8 @@ export default function AdminInvestmentDetail() {
label={<TextCustom bold>Sisa Saham</TextCustom>}
value={
<TextCustom>
{data && formatCurrencyDisplay(data && data?.sisaLembar)} lembar
{data && formatCurrencyDisplay(data && data?.sisaLembar)}{" "}
lembar
</TextCustom>
}
/>
@@ -206,13 +168,15 @@ export default function AdminInvestmentDetail() {
<BaseBox>
<StackCustom>
<DummyLandscapeImage imageId={data?.imageId} />
{listData.map((item, i) => (
<GridSpan_4_8
key={i}
label={<TextCustom bold>{item.label}</TextCustom>}
value={<TextCustom>{item.value}</TextCustom>}
/>
))}
{listData({ data: data, reminder: remind.reminder })?.map(
(item, i) => (
<GridSpan_4_8
key={i}
label={<TextCustom bold>{item.label}</TextCustom>}
value={<TextCustom>{item.value}</TextCustom>}
/>
),
)}
</StackCustom>
</BaseBox>
@@ -230,7 +194,7 @@ export default function AdminInvestmentDetail() {
}
onPress={() => {
router.push(
`/(application)/(file)/${data?.prospektusFileId}`
`/(application)/(file)/${data?.prospektusFileId}`,
);
}}
>
@@ -259,7 +223,7 @@ export default function AdminInvestmentDetail() {
}
onPress={() => {
router.push(
`/(application)/(file)/${item?.fileId}`
`/(application)/(file)/${item?.fileId}`,
);
}}
>
@@ -299,8 +263,8 @@ export default function AdminInvestmentDetail() {
onReject={() => {
router.push(
`/admin/investment/${id}/reject-input?status=${_.lowerCase(
data?.MasterStatusInvestasi?.name
)}`
data?.MasterStatusInvestasi?.name,
)}`,
);
}}
/>
@@ -343,3 +307,67 @@ export default function AdminInvestmentDetail() {
</>
);
}
const listData = ({ data, reminder }: { data: any; reminder: boolean }) => [
{
label: "Username",
value: (data && data?.author?.username) || "-",
},
{
label: "Judul",
value: (data && data?.title) || "-",
},
{
label: "Status",
value:
data && data?.MasterStatusInvestasi?.name ? (
<BadgeCustom
color={colorBadgeStatus({
status: reminder ? "periode berakhir" : "publish",
})}
>
{reminder
? "Periode Berakhir"
: _.startCase(data?.MasterStatusInvestasi?.name as string)}
</BadgeCustom>
) : (
"-"
),
},
{
label: "Dana Dibutuhkan",
value: `Rp. ${
(data && data?.targetDana && formatCurrencyDisplay(data?.targetDana)) ||
"-"
}`,
},
{
label: "Harga Perlembar",
value: `Rp. ${
(data && data?.hargaLembar && formatCurrencyDisplay(data?.hargaLembar)) ||
"-"
}`,
},
{
label: "Total Lembar",
value:
(data && data?.totalLembar && formatCurrencyDisplay(data?.totalLembar)) ||
"-",
},
{
label: "ROI",
value: `${(data && data?.roi && data?.roi) || 0} %`,
},
{
label: "Pembagian Deviden",
value: (data && data?.MasterPembagianDeviden?.name) + " bulan" || "-",
},
{
label: "Jadwal Pembagian",
value: (data && data?.MasterPeriodeDeviden?.name) || "-",
},
{
label: "Pencarian Investor",
value: (data && data?.MasterPencarianInvestor?.name) + " hari" || "-",
},
];

View File

@@ -13,6 +13,7 @@ import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButt
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
import GridTwoView from "@/components/_ShareComponent/GridTwoView";
import { MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import {
apiAdminInvestmentGetOneInvoiceById,
apiAdminInvestmentUpdateInvoice,
@@ -25,6 +26,7 @@ import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function AdminInvestmentTransactionDetail() {
const { user } = useAuth();
const { id } = useLocalSearchParams();
const [data, setData] = useState<any | null>(null);
const [isLoading, setLoading] = useState<boolean>(false);
@@ -32,7 +34,7 @@ export default function AdminInvestmentTransactionDetail() {
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
}, [id]),
);
const onLoadData = async () => {
@@ -40,7 +42,6 @@ export default function AdminInvestmentTransactionDetail() {
const response = await apiAdminInvestmentGetOneInvoiceById({
id: id as string,
});
// console.log("[RESPONSE]", JSON.stringify(response, null, 2));
if (response.success) {
setData(response.data);
}
@@ -92,7 +93,7 @@ export default function AdminInvestmentTransactionDetail() {
<ButtonCustom
onPress={() =>
router.push(
`/(application)/(image)/preview-image/${data?.imageId}`
`/(application)/(image)/preview-image/${data?.imageId}`,
)
}
>
@@ -109,6 +110,13 @@ export default function AdminInvestmentTransactionDetail() {
}: {
category: "accept" | "deny";
}) => {
if (!user?.id) {
Toast.show({
type: "error",
text1: "Gagal update status transaksi",
});
return;
}
try {
setLoading(true);
const response = await apiAdminInvestmentUpdateInvoice({
@@ -117,11 +125,10 @@ export default function AdminInvestmentTransactionDetail() {
data: {
investasiId: data?.investasiId,
lembarTerbeli: data?.lembarTerbeli,
senderId: user?.id as any,
},
});
// console.log("[RESPONSE SUBMIT]", JSON.stringify(response, null, 2));
if (!response.success) {
Toast.show({
type: "error",
@@ -153,6 +160,7 @@ export default function AdminInvestmentTransactionDetail() {
styleRight={{ paddingLeft: 10 }}
leftIcon={
<ButtonCustom
disabled={isLoading}
isLoading={isLoading}
backgroundColor={MainColor.red}
textColor="white"
@@ -175,6 +183,7 @@ export default function AdminInvestmentTransactionDetail() {
}
rightIcon={
<ButtonCustom
disabled={isLoading}
isLoading={isLoading}
onPress={() => {
AlertDefaultSystem({
@@ -198,8 +207,8 @@ export default function AdminInvestmentTransactionDetail() {
} else if (data?.StatusInvoice?.name === "Gagal") {
return (
<>
<ButtonCustom textColor="red" onPress={() => router.back()}>
Gagal
<ButtonCustom disabled onPress={() => router.back()}>
Transaksi telah gagal
</ButtonCustom>
</>
);

View File

@@ -7,34 +7,39 @@ import {
} from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import { apiAdminInvestasiUpdateByStatus, apiAdminInvestmentDetailById } from "@/service/api-admin/api-admin-investment";
import { useAuth } from "@/hooks/use-auth";
import {
apiAdminInvestasiUpdateByStatus,
apiAdminInvestmentDetailById,
} from "@/service/api-admin/api-admin-investment";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function AdminInvestmentRejectInput() {
const { user } = useAuth();
const { id, status } = useLocalSearchParams();
console.log("[STATUS]", status);
const [value, setValue] = useState<any | null>(null);
const [isLoading , setLoading] = useState(false)
const [isLoading, setLoading] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiAdminInvestmentDetailById({ id: id as string });
console.log("[DATA]", JSON.stringify(response, null, 2));
if (response.success) {
setValue(response.data?.catatan);
}
} catch (error) {
console.log(error);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiAdminInvestmentDetailById({ id: id as string });
console.log("[DATA]", JSON.stringify(response, null, 2));
if (response.success) {
setValue(response.data?.catatan);
}
};
} catch (error) {
console.log(error);
}
};
const handlerSubmit = async () => {
if (!value) {
@@ -45,12 +50,23 @@ export default function AdminInvestmentRejectInput() {
return;
}
if (!user?.id) {
Toast.show({
type: "error",
text1: "User tidak ditemukan",
});
return;
}
try {
setLoading(true)
setLoading(true);
const response = await apiAdminInvestasiUpdateByStatus({
id: id as string,
status: "reject",
data: value,
data: {
catatan: value,
senderId: user?.id as string,
},
});
console.log("[RESPONSE]", JSON.stringify(response, null, 2));
@@ -76,7 +92,7 @@ export default function AdminInvestmentRejectInput() {
} catch (error) {
console.error(["ERROR"], error);
} finally {
setLoading(false)
setLoading(false);
}
};

View File

@@ -22,8 +22,6 @@ import { Divider } from "react-native-paper";
export default function AdminInvestmentStatus() {
const { status } = useLocalSearchParams();
console.log("[STATUS]", status);
const [listData, setListData] = React.useState<any[] | null>(null);
const [loadData, setLoadingData] = React.useState(false);
const [search, setSearch] = React.useState("");
@@ -41,7 +39,7 @@ export default function AdminInvestmentStatus() {
category: status as "publish" | "review" | "reject",
search,
});
console.log("[LIST DATA]", JSON.stringify(response, null, 2));
if (response.success) {
setListData(response.data);
}

View File

@@ -15,6 +15,7 @@ import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import AdminButtonReview from "@/components/_ShareComponent/Admin/ButtonReview";
import ReportBox from "@/components/Box/ReportBox";
import { MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import funUpdateStatusJob from "@/screens/Admin/Job/funUpdateStatus";
import { apiAdminJobGetById } from "@/service/api-admin/api-admin-job";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
@@ -23,8 +24,10 @@ import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function AdminJobDetailStatus() {
const { user } = useAuth();
const { id, status } = useLocalSearchParams();
const [data, setData] = useState<any | null>(null);
const [isLoading, setIsLoading] = useState(false);
useFocusEffect(
useCallback(() => {
@@ -92,6 +95,9 @@ export default function AdminJobDetailStatus() {
const response = await funUpdateStatusJob({
id: id as string,
changeStatus,
data: {
senderId: user?.id as string,
},
});
if (!response.success) {
@@ -142,12 +148,15 @@ export default function AdminJobDetailStatus() {
</StackCustom>
</BaseBox>
{data && data?.catatan && (status === "reject" || status === "review") && (
<ReportBox text={data?.catatan}/>
)}
{data &&
data?.catatan &&
(status === "reject" || status === "review") && (
<ReportBox text={data?.catatan} />
)}
{status === "review" && (
<AdminButtonReview
isLoading={isLoading}
onPublish={() => {
AlertDefaultSystem({
title: "Publish",
@@ -156,6 +165,7 @@ export default function AdminJobDetailStatus() {
textRight: "Ya",
onPressRight: () => {
handleUpdate({ changeStatus: "publish" });
setIsLoading(true);
},
});
}}

View File

@@ -15,7 +15,10 @@ import Toast from "react-native-toast-message";
export default function AdminJobRejectInput() {
const { id, status } = useLocalSearchParams();
const [data, setData] = useState<any | null>(null);
const [data, setData] = useState({
catatan: "",
senderId: ""
});
const [isLoading, setIsLoading] = useState(false);
useFocusEffect(
@@ -48,7 +51,7 @@ export default function AdminJobRejectInput() {
const response = await funUpdateStatusJob({
id: id as string,
changeStatus,
data: data,
data: data ,
});
if (!response.success) {
@@ -102,8 +105,8 @@ export default function AdminJobRejectInput() {
headerComponent={<AdminBackButtonAntTitle title="Penolakan Job" />}
>
<TextAreaCustom
value={data}
onChangeText={setData}
value={data?.catatan}
onChangeText={(text) => setData({ ...data, catatan: text })}
placeholder="Masukan alasan"
required
showCount

View File

@@ -1,19 +1,28 @@
import {
AlertDefaultSystem,
BackButton,
BaseBox,
DrawerCustom,
MenuDrawerDynamicGrid,
NewWrapper,
ScrollableCustom,
StackCustom,
TextCustom,
} from "@/components";
import { IconPlus } from "@/components/_Icon";
import { IconDot } from "@/components/_Icon/IconComponent";
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
import NoDataText from "@/components/_ShareComponent/NoDataText";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { useAuth } from "@/hooks/use-auth";
import { useNotificationStore } from "@/hooks/use-notification-store";
import { apiGetNotificationsById } from "@/service/api-notifications";
import { listOfcategoriesAppNotification } from "@/types/type-notification-category";
import { formatChatTime } from "@/utils/formatChatTime";
import { Ionicons } from "@expo/vector-icons";
import { router, Stack, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { RefreshControl, View } from "react-native";
@@ -66,6 +75,10 @@ export default function AdminNotification() {
const [activeCategory, setActiveCategory] = useState<string | null>("event");
const [listData, setListData] = useState<any[]>([]);
const [refreshing, setRefreshing] = useState(false);
const [loading, setLoading] = useState(false);
const [openDrawer, setOpenDrawer] = useState(false);
const { markAsReadAll } = useNotificationStore();
const handlePress = (item: any) => {
setActiveCategory(item.value);
@@ -80,11 +93,12 @@ export default function AdminNotification() {
const fecthData = async () => {
try {
setLoading(true);
const response = await apiGetNotificationsById({
id: user?.id as any,
category: activeCategory as any,
});
// console.log("Response Notification", JSON.stringify(response, null, 2));
if (response.success) {
setListData(response.data);
} else {
@@ -92,6 +106,8 @@ export default function AdminNotification() {
}
} catch (error) {
console.log("Error Notification", error);
} finally {
setLoading(false);
}
};
@@ -108,9 +124,9 @@ export default function AdminNotification() {
title: "Admin Notifikasi",
headerLeft: () => <BackButton />,
headerRight: () => (
<IconPlus
<IconDot
color={MainColor.yellow}
onPress={() => router.push("/test-notifications")}
onPress={() => setOpenDrawer(true)}
/>
),
}}
@@ -132,12 +148,66 @@ export default function AdminNotification() {
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
>
{listData.map((e, i) => (
<View key={i}>
<BoxNotification data={e} activeCategory={activeCategory as any} />
</View>
))}
{loading ? (
<ListSkeletonComponent />
) : _.isEmpty(listData) ? (
<NoDataText text="Belum ada notifikasi" />
) : (
listData.map((e, i) => (
<View key={i}>
<BoxNotification
data={e}
activeCategory={activeCategory as any}
/>
</View>
))
)}
</NewWrapper>
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
label: "Tandai Semua Dibaca",
value: "read-all",
icon: (
<Ionicons
name="reader-outline"
size={ICON_SIZE_SMALL}
color={MainColor.white}
/>
),
path: "",
},
]}
onPressItem={(item: any) => {
console.log("Item", item.value);
if (item.value === "read-all") {
AlertDefaultSystem({
title: "Tandai Semua Dibaca",
message:
"Apakah Anda yakin ingin menandai semua notifikasi dibaca?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: () => {
markAsReadAll(user?.id as any);
const data = _.cloneDeep(listData);
data.forEach((e) => {
e.isRead = true;
});
setListData(data);
onRefresh();
setOpenDrawer(false);
},
});
}
}}
/>
</DrawerCustom>
</>
);
}

View File

@@ -48,6 +48,7 @@ export default function SuperAdminDetail() {
const response = await apiAdminUserAccessUpdateStatus({
id: id as string,
role: data?.masterUserRoleId === "2" ? "user" : "admin",
category: "role"
});
if (!response.success) {

View File

@@ -9,15 +9,21 @@ import {
} from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import GridTwoView from "@/components/_ShareComponent/GridTwoView";
import { useAuth } from "@/hooks/use-auth";
import { routeUser } from "@/lib/routeApp";
import {
apiAdminUserAccessGetById,
apiAdminUserAccessUpdateStatus,
} from "@/service/api-admin/api-admin-user-access";
import {
apiNotificationsSendById
} from "@/service/api-notifications";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function AdminUserAccessDetail() {
const { user } = useAuth();
const { id } = useLocalSearchParams();
const [data, setData] = useState<any | null>(null);
const [loadData, setLoadData] = useState(false);
@@ -33,6 +39,7 @@ export default function AdminUserAccessDetail() {
try {
setLoadData(true);
const response = await apiAdminUserAccessGetById({ id: id as string });
console.log("[DATA]", JSON.stringify(response.data, null, 2));
setData(response.data);
} catch (error) {
@@ -48,6 +55,7 @@ export default function AdminUserAccessDetail() {
const response = await apiAdminUserAccessUpdateStatus({
id: id as string,
active: !data?.active,
category: "access",
});
if (!response.success) {
@@ -61,6 +69,21 @@ export default function AdminUserAccessDetail() {
type: "success",
text1: "Update aktifasi berhasil ",
});
if (data.active === false) {
await apiNotificationsSendById({
data: {
title: "Akun anda telah diaktifkan",
body: "Selamat menjelajahi HIConnect",
userLoginId: user?.id || "",
kategoriApp: "OTHER",
type: "announcement",
deepLink: routeUser.home,
},
id: id as string,
});
}
router.back();
} catch (error) {
console.log("[ERROR UPDATE STATUS]", error);

View File

@@ -16,6 +16,7 @@ import AdminButtonReview from "@/components/_ShareComponent/Admin/ButtonReview";
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
import ReportBox from "@/components/Box/ReportBox";
import { MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import funUpdateStatusVoting from "@/screens/Admin/Voting/funUpdateStatus";
import { apiAdminVotingById } from "@/service/api-admin/api-admin-voting";
import { colorBadgeStatus } from "@/utils/colorBadge";
@@ -29,6 +30,7 @@ import { List } from "react-native-paper";
import Toast from "react-native-toast-message";
export default function AdminVotingDetail() {
const { user } = useAuth();
const { id, status } = useLocalSearchParams();
const [data, setData] = useState<any | null>(null);
const [isLoading, setIsLoading] = useState(false);
@@ -139,6 +141,10 @@ export default function AdminVotingDetail() {
const response = await funUpdateStatusVoting({
id: id as string,
changeStatus,
data: {
senderId: user?.id as string,
catatan: "",
},
});
if (!response.success) {

View File

@@ -7,6 +7,7 @@ import {
} from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import { useAuth } from "@/hooks/use-auth";
import funUpdateStatusVoting from "@/screens/Admin/Voting/funUpdateStatus";
import { apiAdminVotingById } from "@/service/api-admin/api-admin-voting";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
@@ -14,6 +15,7 @@ import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function AdminVotingRejectInput() {
const { user } = useAuth()
const { id, status } = useLocalSearchParams();
const [data, setData] = useState("");
const [isLoading, setIsLoading] = useState(false);
@@ -48,7 +50,10 @@ export default function AdminVotingRejectInput() {
const response = await funUpdateStatusVoting({
id: id as string,
changeStatus,
data: data,
data: {
catatan: data,
senderId: user?.id as string,
},
});
if (!response.success) {

View File

@@ -1,10 +1,19 @@
import { StackCustom, TextCustom, ViewWrapper } from "@/components";
import { BackButton, StackCustom, TextCustom, ViewWrapper } from "@/components";
import { Stack } from "expo-router";
export default function NotFoundScreen() {
return (
return (
<>
<Stack.Screen
options={{ headerShown: false, headerLeft: () => <BackButton /> }}
/>
<ViewWrapper>
<StackCustom align="center" gap={0} style={{justifyContent: "center", alignItems: "center", flex: 1}}>
<TextCustom size="large" bold style={{fontSize: 100}}>
<StackCustom
align="center"
gap={0}
style={{ justifyContent: "center", alignItems: "center", flex: 1 }}
>
<TextCustom size="large" bold style={{ fontSize: 100 }}>
404
</TextCustom>
<TextCustom size="large" bold>
@@ -12,5 +21,6 @@ export default function NotFoundScreen() {
</TextCustom>
</StackCustom>
</ViewWrapper>
);
}
</>
);
}

9
app/eula.tsx Normal file
View File

@@ -0,0 +1,9 @@
import EULAView from "@/screens/Authentication/EULAView";
export default function EULA() {
return (
<>
<EULAView />
</>
);
}

View File

@@ -12,10 +12,12 @@ const LeftButtonCustom = ({
path,
icon = "arrow-back",
iconCustom,
onPress,
}: {
path?: Href;
icon?: React.ReactNode | any;
iconCustom?: React.ReactNode;
onPress?: () => void;
}) => {
return (
<>
@@ -26,7 +28,7 @@ const LeftButtonCustom = ({
name={icon}
size={20}
color={MainColor.yellow}
onPress={() => (path ? router.replace(path) : router.back())}
onPress={() => (onPress ? onPress() : path ? router.replace(path) : router.back())}
/>
)}
</>

View File

@@ -0,0 +1,29 @@
import { useRouter } from "expo-router";
import { BackButton } from "..";
export default function BackButtonFromNotification({
from,
category,
}: {
from: string;
category?: string;
}) {
const router = useRouter();
return (
<>
<BackButton
onPress={() => {
if (from === "notifications") {
router.replace(`/notifications?category=${category}`);
} else {
if (from) {
router.replace(`/${from}` as any);
} else {
router.navigate("/home");
}
}
}}
/>
</>
);
}

View File

@@ -24,8 +24,6 @@ const DateTimePickerCustom: React.FC<Props> = ({
disabled = false,
}) => {
console.log("Date Android Comp", value)
return (
<>
{Platform.OS === "ios" ? (

View File

@@ -0,0 +1,33 @@
import { Modal, View } from "react-native";
export default function ModalReactNative({
children,
isVisible,
}: {
children: React.ReactNode;
isVisible: boolean;
}) {
return (
<Modal
animationType="slide"
backdropColor={"rgba(0, 0, 0, 0.5)"}
visible={isVisible}
>
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "rgba(0, 0, 0, 0.5)",
// margin: 10,
marginBlock: 30,
padding: 10,
borderRadius: 10,
paddingTop: 30
}}
>
{children}
</View>
</Modal>
);
}

View File

@@ -49,7 +49,8 @@ export default function NotificationInitializer() {
const fcmToken = await getToken(messagingInstance);
if (!fcmToken) {
logout();
console.warn("Tidak bisa mendapatkan FCM token");
// logout();
return;
}

View File

@@ -2,15 +2,16 @@ import {
apiConfig,
apiLogin,
apiRegister,
apiUpdatedTermCondition,
apiValidationCode,
} from "@/service/api-config";
import { apiDeviceTokenDeleted } from "@/service/api-device-token";
import { IUser } from "@/types/User";
import AsyncStorage from "@react-native-async-storage/async-storage";
import * as Device from "expo-device";
import { router } from "expo-router";
import { createContext, useEffect, useState } from "react";
import Toast from "react-native-toast-message";
import * as Device from "expo-device";
// --- Types ---
type AuthContextType = {
@@ -20,7 +21,7 @@ type AuthContextType = {
isAuthenticated: boolean;
isAdmin: boolean;
isUserActive: boolean;
loginWithNomor: (nomor: string) => Promise<void>;
loginWithNomor: (nomor: string) => Promise<boolean>;
validateOtp: (nomor: string) => Promise<any>;
logout: () => Promise<void>;
registerUser: (userData: {
@@ -29,11 +30,12 @@ type AuthContextType = {
termsOfServiceAccepted: boolean;
}) => Promise<void>;
userData: (token: string) => Promise<any>;
acceptedTerms: (nomor: string, onSetModalVisible: (visible: boolean) => void) => Promise<any>;
};
// --- Create Context ---
export const AuthContext = createContext<AuthContextType | undefined>(
undefined
undefined,
);
export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
@@ -74,30 +76,38 @@ 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 });
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",
});
console.log("[RESPONSE AUTH]", JSON.stringify(response, null, 2));
if (response.success && response.isAcceptTerms) {
await AsyncStorage.setItem("kode_otp", response.kodeId);
router.push(`/verification?nomor=${nomor}`);
return;
return true;
} else {
router.push(`/register?nomor=${nomor}`);
Toast.show({
type: "info",
text1: "Info",
text2: "Silahkan mendaftar",
});
return;
// router.push(`/eula?nomor=${nomor}`);
return false;
}
// if (response.success) {
// if (response.isAcceptTerms) {
// Toast.show({
// type: "success",
// text1: "Sukses",
// text2: "Kode OTP berhasil dikirim",
// });
// await AsyncStorage.setItem("kode_otp", response.kodeId);
// router.push(`/verification?nomor=${nomor}`);
// return false
// } else {
// // router.push(`/eula?nomor=${nomor}`);
// return true
// }
// } else {
// router.push(`/eula?nomor=${nomor}`);
// return true;
// }
} catch (error: any) {
throw new Error(error.response?.data?.message || "Gagal kirim OTP");
} finally {
@@ -105,18 +115,6 @@ 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 {
@@ -164,7 +162,7 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
} catch (error: any) {
console.log("Error validasi otp >>", (error as Error).message || error);
throw new Error(
error.response?.data?.message || "OTP salah atau user tidak ditemukan"
error.response?.data?.message || "OTP salah atau user tidak ditemukan",
);
} finally {
setIsLoading(false);
@@ -193,7 +191,7 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
} catch (error: any) {
console.log(
"[LOAD USER DATA]",
error.response?.data?.message + "user" || "Gagal mengambil data user"
error.response?.data?.message + "user" || "Gagal mengambil data user",
);
} finally {
setIsLoading(false);
@@ -209,7 +207,6 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
setIsLoading(true);
try {
const response = await apiRegister({ data: userData });
console.log("[REGISTER FETCH]", JSON.stringify(response, null, 2));
if (!response.success) {
Toast.show({
@@ -239,42 +236,6 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
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 ---
@@ -284,7 +245,8 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
setToken(null);
setUser(null);
const deviceId = Device.osInternalBuildId || Device.modelName || "unknown";
const deviceId =
Device.osInternalBuildId || Device.modelName || "unknown";
await AsyncStorage.removeItem("authToken");
await AsyncStorage.removeItem("userData");
@@ -303,6 +265,33 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
}
};
// --- 6. Accept Terms ---
const acceptedTerms = async (nomor: string, onSetModalVisible: (visible: boolean) => void) => {
try {
setIsLoading(true);
const response = await apiUpdatedTermCondition({ nomor: nomor });
if (response.success) {
router.replace(`/verification?nomor=${nomor}`);
} else {
if (response.status === 404) {
router.replace(`/register?nomor=${nomor}`);
} else {
Toast.show({
type: "error",
text1: "Error",
text2: response.message,
});
}
}
} catch (error) {
console.log("Error accept terms", error);
} finally {
setIsLoading(false);
// onSetModalVisible(false);
}
};
return (
<>
<AuthContext.Provider
@@ -318,6 +307,7 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
logout,
registerUser,
userData,
acceptedTerms,
}}
>
{children}

View File

@@ -11,7 +11,7 @@
"preview": {
"distribution": "internal",
"android": {
"buildType": "app-bundle"
"buildType": "apk"
},
"ios": {
"simulator": false

View File

@@ -13,3 +13,13 @@ Exp: open ios/HIPMIBADUNG.xcworkspace
perubahan versi : npm version patch
ios: bunx expo prebuild --platform ios
android: bunx expo prebuild --platform android
### Android
adb devices : cek device yang terhubung
Note: izinkan perangkat dulu agar statusnya tidak unauthorized
adb install android/app/build/outputs/apk/debug/app-debug.apk : install apk ke device / emulator
Note:
Gunakan flag -s (serial) di perintah adb untuk menentukan target
adb -s <0G52319V261040B2 ini adalah id nya> install android/app/build/outputs/apk/debug/app-debug.apk

View File

@@ -4,6 +4,7 @@ import {
onMessage,
FirebaseMessagingTypes,
} from "@react-native-firebase/messaging";
import { useAuth } from "./use-auth";
// Gunakan tipe resmi dari library
type RemoteMessage = FirebaseMessagingTypes.RemoteMessage;
@@ -11,17 +12,26 @@ type RemoteMessage = FirebaseMessagingTypes.RemoteMessage;
export function useForegroundNotifications(
onMessageReceived: (message: RemoteMessage) => void
) {
const { user } = useAuth();
useEffect(() => {
const messaging = getMessaging();
const unsubscribe = onMessage(messaging, (remoteMessage) => {
const data = remoteMessage.data;
// console.log("DATA NOTIFIKASI DARI SERVER", data)
if (data?.recipientId && data?.recipientId !== user?.id) {
console.log("📵 Notification untuk user lain", data);
return;
}
console.log(
"🔔 Notifikasi diterima saat app aktif:",
JSON.stringify(remoteMessage, null, 2)
JSON.stringify(data, null, 2)
);
onMessageReceived(remoteMessage);
});
return unsubscribe;
}, [onMessageReceived]);
}
}, [user?.id, onMessageReceived]);
}

View File

@@ -40,6 +40,7 @@ type NotificationContextType = {
notif: Omit<AppNotification, "id" | "isRead" | "timestamp">
) => void;
markAsRead: (id: string) => void;
markAsReadAll: (id: string) => void;
syncUnreadCount: () => Promise<void>;
};
@@ -48,6 +49,7 @@ const NotificationContext = createContext<NotificationContextType>({
unreadCount: 0,
addNotification: () => {},
markAsRead: () => {},
markAsReadAll: () => {},
syncUnreadCount: async () => {},
});
@@ -98,7 +100,7 @@ export const NotificationProvider = ({ children }: { children: ReactNode }) => {
const markAsRead = async (id: string) => {
try {
const response = await apiNotificationMarkAsRead({ id });
const response = await apiNotificationMarkAsRead({ id, category: "one" });
console.log("🚀 Response Mark As Read:", response);
if (response.success) {
@@ -114,6 +116,25 @@ export const NotificationProvider = ({ children }: { children: ReactNode }) => {
}
};
const markAsReadAll = async (id: string) => {
try {
const response = await apiNotificationMarkAsRead({ id, category: "all" });
console.log("🚀 Response Mark As Read All:", response);
if (response.success) {
const cloneNotifications = [...notifications];
const index = cloneNotifications.findIndex((n) => n?.data?.id === id);
if (index !== -1) {
cloneNotifications[index].isRead = true;
setNotifications(cloneNotifications);
}
}
} catch (error) {
console.error("Gagal mark as read:", error);
}
};
const syncUnreadCount = async () => {
try {
const count = await apiNotificationUnreadCount({
@@ -133,8 +154,9 @@ export const NotificationProvider = ({ children }: { children: ReactNode }) => {
value={{
notifications,
addNotification,
markAsRead,
unreadCount,
markAsRead,
markAsReadAll,
syncUnreadCount,
}}
>

View File

@@ -39,7 +39,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>17</string>
<string>20</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSMinimumSystemVersion</key>

9
lib/routeApp.ts Normal file
View File

@@ -0,0 +1,9 @@
export { routeAdmin, routeUser };
const routeAdmin = {
userAccess: ({ id }: { id: string }) => `/admin/user-access/${id}`,
};
const routeUser = {
home: `/(user)/home`,
};

View File

@@ -1,4 +1,5 @@
import { apiAdminDonationUpdateStatus } from "@/service/api-admin/api-admin-donation";
import { typeRejectedData } from "@/types/type-collect-other";
export const funUpdateStatusDonation = async ({
id,
@@ -7,7 +8,7 @@ export const funUpdateStatusDonation = async ({
}: {
id: string;
changeStatus: "publish" | "review" | "reject";
data?: string;
data?: typeRejectedData;
}) => {
try {
const response = await apiAdminDonationUpdateStatus({

View File

@@ -1,4 +1,5 @@
import { apiAdminEventUpdateStatus } from "@/service/api-admin/api-admin-event";
import { typeRejectedData } from "@/types/type-collect-other";
export const funUpdateStatusEvent = async ({
id,
@@ -7,17 +8,19 @@ export const funUpdateStatusEvent = async ({
}: {
id: string;
changeStatus: "publish" | "review" | "reject";
data?: string;
data?: typeRejectedData;
}) => {
try {
console.log("[DATA]", data);
const response = await apiAdminEventUpdateStatus({
id: id,
changeStatus: changeStatus as any,
data: data,
data: data as any,
});
return response;
} catch (error) {
console.log("[ERROR]", error);
throw error;
}
};
};

View File

@@ -7,13 +7,17 @@ const funUpdateStatusJob = async ({
}: {
id: string;
changeStatus: "publish" | "review" | "reject";
data?: string;
data: {
catatan?: string;
senderId: string;
};
}) => {
try {
const fixData = data;
const response = await apiAdminJobUpdate({
id: id,
status: changeStatus as any,
data: data,
data: fixData as any,
});
return response;

View File

@@ -1,4 +1,5 @@
import { apiAdminVotingUpdateStatus } from "@/service/api-admin/api-admin-voting";
import { typeRejectedData } from "@/types/type-collect-other";
const funUpdateStatusVoting = async ({
id,
@@ -7,7 +8,7 @@ const funUpdateStatusVoting = async ({
}: {
id: string;
changeStatus: "publish" | "review" | "reject";
data?: string;
data?: typeRejectedData;
}) => {
try {
const response = await apiAdminVotingUpdateStatus({

View File

@@ -0,0 +1,264 @@
// app/syarat-dan-ketentuan.tsx
import {
View,
Text,
ScrollView,
TouchableOpacity,
StyleSheet,
} from "react-native";
import { useState, useRef } from "react";
import { useLocalSearchParams, useRouter } from "expo-router";
import { SafeAreaView } from "react-native-safe-area-context";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
export default function EULASection({ nomor, onSetModalVisible }: { nomor: string, onSetModalVisible: (visible: boolean) => void }) {
const { acceptedTerms } = useAuth();
const [isLoading, setIsLoading] = useState<boolean>(false);
const [isAtBottom, setIsAtBottom] = useState(false);
const scrollViewRef = useRef<ScrollView>(null);
const handleScroll = (event: any) => {
const { layoutMeasurement, contentOffset, contentSize } = event.nativeEvent;
const paddingToBottom = 20;
const isCloseToBottom =
layoutMeasurement.height + contentOffset.y >=
contentSize.height - paddingToBottom;
setIsAtBottom(isCloseToBottom);
};
const handleAccept = async () => {
// console.log("Accept terms", nomor);
// onSetModalVisible(false);
try {
if (!isAtBottom) return;
setIsLoading(true);
await acceptedTerms(nomor as string, onSetModalVisible);
} catch (error) {
console.log("Error accept terms", error);
} finally {
setIsLoading(false);
}
};
return (
<SafeAreaView edges={["bottom"]} style={styles.container}>
<Text style={styles.title}>
Syarat & Ketentuan Penggunaan HIPMI Badung Connect
</Text>
<ScrollView
ref={scrollViewRef}
onScroll={handleScroll}
scrollEventThrottle={16}
style={styles.scrollView}
contentContainerStyle={styles.scrollContent}
>
<Text style={styles.paragraph}>
Dengan menggunakan aplikasi{" "}
<Text style={styles.bold}>HIPMI Badung Connect</Text> (Aplikasi),
Anda setuju untuk mematuhi dan terikat oleh syarat dan ketentuan
berikut. Jika Anda tidak setuju dengan ketentuan ini, harap jangan
gunakan Aplikasi.
</Text>
<Text style={styles.heading}>1. Definisi</Text>
<Text style={styles.paragraph}>
<Text style={styles.bold}>HIPMI Badung Connect</Text> adalah platform
digital resmi untuk anggota Himpunan Pengusaha Muda Indonesia (HIPMI)
Kabupaten Badung, yang bertujuan memfasilitasi jaringan, kolaborasi,
dan pertumbuhan bisnis para pengusaha muda.
</Text>
<Text style={styles.heading}>2. Larangan Konten Tidak Pantas</Text>
<Text style={styles.paragraph}>
Anda <Text style={styles.bold}>dilarang keras</Text> memposting,
mengirim, membagikan, atau mengunggah konten apa pun yang mengandung:
</Text>
<View style={styles.list}>
<Text style={styles.listItem}>
Ujaran kebencian, diskriminasi, atau konten SARA (Suku, Agama,
Ras, Antar-golongan)
</Text>
<Text style={styles.listItem}>
Pornografi, konten seksual eksplisit, atau gambar tidak senonoh
</Text>
<Text style={styles.listItem}>
Ancaman, pelecehan, bullying, atau perilaku melecehkan
</Text>
<Text style={styles.listItem}>
Informasi palsu, hoaks, spam, atau konten menyesatkan
</Text>
<Text style={styles.listItem}>
Konten ilegal, melanggar hukum, atau melanggar hak kekayaan
intelektual pihak lain
</Text>
<Text style={styles.listItem}>
Promosi narkoba, perjudian, atau aktivitas ilegal lainnya
</Text>
</View>
<Text style={styles.heading}>3. Tanggung Jawab Pengguna</Text>
<Text style={styles.paragraph}>
Anda bertanggung jawab penuh atas setiap konten yang Anda unggah atau
bagikan melalui fitur-fitur berikut:
</Text>
<View style={styles.list}>
<Text style={styles.listItem}> Profil (bio, foto, portofolio)</Text>
<Text style={styles.listItem}> Forum diskusi</Text>
<Text style={styles.listItem}> Chat pribadi atau grup</Text>
<Text style={styles.listItem}>
Lowongan kerja, investasi, dan donasi
</Text>
</View>
<Text style={styles.paragraph}>
Konten yang melanggar ketentuan ini dapat dihapus kapan saja tanpa
pemberitahuan.
</Text>
<Text style={styles.heading}>4. Tindakan terhadap Pelanggaran</Text>
<Text style={styles.paragraph}>
Jika kami menerima laporan atau menemukan konten yang melanggar
ketentuan ini, kami akan:
</Text>
<View style={styles.list}>
<Text style={styles.listItem}>
Segera menghapus konten tersebut
</Text>
<Text style={styles.listItem}>
Memberikan peringatan atau memblokir akun pengguna
</Text>
<Text style={styles.listItem}>
Dalam kasus berat, melaporkan ke pihak berwajib sesuai hukum yang
berlaku
</Text>
</View>
<Text style={styles.paragraph}>
Tim kami berkomitmen untuk menanggapi laporan konten tidak pantas{" "}
<Text style={styles.bold}>dalam waktu 24 jam</Text>.
</Text>
<Text style={styles.heading}>5. Mekanisme Pelaporan</Text>
<Text style={styles.paragraph}>
Anda dapat melaporkan konten atau pengguna yang mencurigakan melalui:
</Text>
<View style={styles.list}>
<Text style={styles.listItem}>
Tombol <Text style={styles.bold}>Laporkan</Text> di setiap
posting forum atau pesan chat
</Text>
<Text style={styles.listItem}>
Tombol <Text style={styles.bold}>Blokir Pengguna</Text> di
profil pengguna
</Text>
</View>
<Text style={styles.paragraph}>
Setiap laporan akan ditangani secara rahasia dan segera.
</Text>
<Text style={styles.heading}>6. Perubahan Ketentuan</Text>
<Text style={styles.paragraph}>
Kami berhak memperbarui Syarat & Ketentuan ini sewaktu-waktu. Versi
terbaru akan dipublikasikan di halaman ini dengan tanggal revisi yang
diperbarui.
</Text>
<Text style={styles.heading}>7. Kontak</Text>
<Text style={styles.paragraph}>
Jika Anda memiliki pertanyaan tentang ketentuan ini, silakan hubungi
kami di:{"\n"}
<Text style={[styles.bold, { color: "#1E90FF" }]}>
bip.baliinteraktifperkasa@gmail.com
</Text>
</Text>
<Text style={styles.footer}>
© 2026 Bali Interaktif Perkasa. All rights reserved.
</Text>
</ScrollView>
<TouchableOpacity
onPress={handleAccept}
disabled={!isAtBottom || isLoading}
style={[styles.button, { opacity: !isAtBottom || isLoading ? 0.6 : 1 }]}
>
<Text style={styles.buttonText}>
{isLoading ? "Menyimpan..." : "Saya Setuju"}
</Text>
</TouchableOpacity>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: MainColor.darkblue,
padding: 16,
},
title: {
fontSize: 20,
fontWeight: "bold",
textAlign: "center",
marginBottom: 16,
color: MainColor.white,
},
scrollView: {
flex: 1,
marginBottom: 20,
},
scrollContent: {
paddingBottom: 30,
},
heading: {
fontSize: 16,
fontWeight: "600",
marginTop: 16,
marginBottom: 8,
color: MainColor.white,
},
paragraph: {
fontSize: 14,
lineHeight: 22,
color: MainColor.white,
marginBottom: 12,
},
bold: {
fontWeight: "600",
},
list: {
marginLeft: 8,
marginBottom: 12,
},
listItem: {
fontSize: 14,
lineHeight: 22,
color: MainColor.white,
marginBottom: 6,
},
footer: {
fontSize: 12,
color: MainColor.white,
textAlign: "center",
marginTop: 20,
paddingTop: 10,
borderTopWidth: 2,
borderTopColor: AccentColor.blue,
},
button: {
backgroundColor: MainColor.yellow,
paddingVertical: 14,
borderRadius: 8,
alignItems: "center",
width: 200,
alignSelf: "center",
},
buttonText: {
color: "#fff",
fontSize: 16,
fontWeight: "600",
},
});

View File

@@ -0,0 +1,259 @@
// app/syarat-dan-ketentuan.tsx
import {
View,
Text,
ScrollView,
TouchableOpacity,
StyleSheet,
} from "react-native";
import { useState, useRef } from "react";
import { useLocalSearchParams, useRouter } from "expo-router";
import { SafeAreaView } from "react-native-safe-area-context";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
export default function EULAView() {
const { acceptedTerms } = useAuth();
const { nomor } = useLocalSearchParams();
const [isLoading, setIsLoading] = useState<boolean>(false);
const [isAtBottom, setIsAtBottom] = useState(false);
const scrollViewRef = useRef<ScrollView>(null);
const handleScroll = (event: any) => {
const { layoutMeasurement, contentOffset, contentSize } = event.nativeEvent;
const paddingToBottom = 20;
const isCloseToBottom =
layoutMeasurement.height + contentOffset.y >=
contentSize.height - paddingToBottom;
setIsAtBottom(isCloseToBottom);
};
const handleAccept = async () => {
try {
if (!isAtBottom) return;
setIsLoading(true);
// await acceptedTerms(nomor as string);
} catch (error) {
console.log("Error accept terms", error);
} finally {
setIsLoading(false);
}
};
return (
<SafeAreaView edges={["bottom"]} style={styles.container}>
<Text style={styles.title}>
Syarat & Ketentuan Penggunaan HIPMI Badung Connect
</Text>
<ScrollView
ref={scrollViewRef}
onScroll={handleScroll}
scrollEventThrottle={16}
style={styles.scrollView}
contentContainerStyle={styles.scrollContent}
>
<Text style={styles.paragraph}>
Dengan menggunakan aplikasi{" "}
<Text style={styles.bold}>HIPMI Badung Connect</Text> (Aplikasi),
Anda setuju untuk mematuhi dan terikat oleh syarat dan ketentuan
berikut. Jika Anda tidak setuju dengan ketentuan ini, harap jangan
gunakan Aplikasi.
</Text>
<Text style={styles.heading}>1. Definisi</Text>
<Text style={styles.paragraph}>
<Text style={styles.bold}>HIPMI Badung Connect</Text> adalah platform
digital resmi untuk anggota Himpunan Pengusaha Muda Indonesia (HIPMI)
Kabupaten Badung, yang bertujuan memfasilitasi jaringan, kolaborasi,
dan pertumbuhan bisnis para pengusaha muda.
</Text>
<Text style={styles.heading}>2. Larangan Konten Tidak Pantas</Text>
<Text style={styles.paragraph}>
Anda <Text style={styles.bold}>dilarang keras</Text> memposting,
mengirim, membagikan, atau mengunggah konten apa pun yang mengandung:
</Text>
<View style={styles.list}>
<Text style={styles.listItem}>
Ujaran kebencian, diskriminasi, atau konten SARA (Suku, Agama,
Ras, Antar-golongan)
</Text>
<Text style={styles.listItem}>
Pornografi, konten seksual eksplisit, atau gambar tidak senonoh
</Text>
<Text style={styles.listItem}>
Ancaman, pelecehan, bullying, atau perilaku melecehkan
</Text>
<Text style={styles.listItem}>
Informasi palsu, hoaks, spam, atau konten menyesatkan
</Text>
<Text style={styles.listItem}>
Konten ilegal, melanggar hukum, atau melanggar hak kekayaan
intelektual pihak lain
</Text>
<Text style={styles.listItem}>
Promosi narkoba, perjudian, atau aktivitas ilegal lainnya
</Text>
</View>
<Text style={styles.heading}>3. Tanggung Jawab Pengguna</Text>
<Text style={styles.paragraph}>
Anda bertanggung jawab penuh atas setiap konten yang Anda unggah atau
bagikan melalui fitur-fitur berikut:
</Text>
<View style={styles.list}>
<Text style={styles.listItem}> Profil (bio, foto, portofolio)</Text>
<Text style={styles.listItem}> Forum diskusi</Text>
<Text style={styles.listItem}> Chat pribadi atau grup</Text>
<Text style={styles.listItem}>
Lowongan kerja, investasi, dan donasi
</Text>
</View>
<Text style={styles.paragraph}>
Konten yang melanggar ketentuan ini dapat dihapus kapan saja tanpa
pemberitahuan.
</Text>
<Text style={styles.heading}>4. Tindakan terhadap Pelanggaran</Text>
<Text style={styles.paragraph}>
Jika kami menerima laporan atau menemukan konten yang melanggar
ketentuan ini, kami akan:
</Text>
<View style={styles.list}>
<Text style={styles.listItem}>
Segera menghapus konten tersebut
</Text>
<Text style={styles.listItem}>
Memberikan peringatan atau memblokir akun pengguna
</Text>
<Text style={styles.listItem}>
Dalam kasus berat, melaporkan ke pihak berwajib sesuai hukum yang
berlaku
</Text>
</View>
<Text style={styles.paragraph}>
Tim kami berkomitmen untuk menanggapi laporan konten tidak pantas{" "}
<Text style={styles.bold}>dalam waktu 24 jam</Text>.
</Text>
<Text style={styles.heading}>5. Mekanisme Pelaporan</Text>
<Text style={styles.paragraph}>
Anda dapat melaporkan konten atau pengguna yang mencurigakan melalui:
</Text>
<View style={styles.list}>
<Text style={styles.listItem}>
Tombol <Text style={styles.bold}>Laporkan</Text> di setiap
posting forum atau pesan chat
</Text>
<Text style={styles.listItem}>
Tombol <Text style={styles.bold}>Blokir Pengguna</Text> di
profil pengguna
</Text>
</View>
<Text style={styles.paragraph}>
Setiap laporan akan ditangani secara rahasia dan segera.
</Text>
<Text style={styles.heading}>6. Perubahan Ketentuan</Text>
<Text style={styles.paragraph}>
Kami berhak memperbarui Syarat & Ketentuan ini sewaktu-waktu. Versi
terbaru akan dipublikasikan di halaman ini dengan tanggal revisi yang
diperbarui.
</Text>
<Text style={styles.heading}>7. Kontak</Text>
<Text style={styles.paragraph}>
Jika Anda memiliki pertanyaan tentang ketentuan ini, silakan hubungi
kami di:{"\n"}
<Text style={[styles.bold, { color: "#1E90FF" }]}>
bip.baliinteraktifperkasa@gmail.com
</Text>
</Text>
<Text style={styles.footer}>
© 2026 Bali Interaktif Perkasa. All rights reserved.
</Text>
</ScrollView>
<TouchableOpacity
onPress={handleAccept}
disabled={!isAtBottom || isLoading}
style={[styles.button, { opacity: !isAtBottom || isLoading ? 0.6 : 1 }]}
>
<Text style={styles.buttonText}>
{isLoading ? "Menyimpan..." : "Saya Setuju"}
</Text>
</TouchableOpacity>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: MainColor.darkblue,
padding: 16,
},
title: {
fontSize: 20,
fontWeight: "bold",
textAlign: "center",
marginBottom: 16,
color: MainColor.white,
},
scrollView: {
flex: 1,
marginBottom: 20,
},
scrollContent: {
paddingBottom: 30,
},
heading: {
fontSize: 16,
fontWeight: "600",
marginTop: 16,
marginBottom: 8,
color: MainColor.white,
},
paragraph: {
fontSize: 14,
lineHeight: 22,
color: MainColor.white,
marginBottom: 12,
},
bold: {
fontWeight: "600",
},
list: {
marginLeft: 8,
marginBottom: 12,
},
listItem: {
fontSize: 14,
lineHeight: 22,
color: MainColor.white,
marginBottom: 6,
},
footer: {
fontSize: 12,
color: MainColor.white,
textAlign: "center",
marginTop: 20,
paddingTop: 10,
borderTopWidth: 2,
borderTopColor: AccentColor.blue,
},
button: {
backgroundColor: MainColor.yellow,
paddingVertical: 14,
borderRadius: 8,
alignItems: "center",
},
buttonText: {
color: "#fff",
fontSize: 16,
fontWeight: "600",
},
});

View File

@@ -1,25 +1,31 @@
import { NewWrapper } from "@/components";
import { NewWrapper, TextCustom } from "@/components";
import ButtonCustom from "@/components/Button/ButtonCustom";
import ModalReactNative from "@/components/Modal/ModalReactNative";
import Spacing from "@/components/_ShareComponent/Spacing";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import { apiVersion } from "@/service/api-config";
import { apiVersion, BASE_URL } from "@/service/api-config";
import { GStyles } from "@/styles/global-styles";
import { openBrowser } from "@/utils/openBrower";
import versionBadge from "@/utils/viersionBadge";
import VersionBadge from "@/utils/viersionBadge";
import { Redirect } from "expo-router";
import { useEffect, useState } from "react";
import { RefreshControl, Text, View } from "react-native";
import { Modal, RefreshControl, Text, View } from "react-native";
import PhoneInput, { ICountry } from "react-native-international-phone-number";
import Toast from "react-native-toast-message";
import EULASection from "./EULASection";
export default function LoginView() {
const url = BASE_URL;
const [version, setVersion] = useState<string>("");
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 [modalVisible, setModalVisible] = useState(false);
const [numberToEULA, setNumberToEULA] = useState<string>("");
const { loginWithNomor, token, isAdmin, isUserActive } = useAuth();
@@ -84,10 +90,17 @@ export default function LoginView() {
let fixNumber = inputValue.replace(/\s+/g, "").replace(/^0+/, "");
const realNumber = callingCode + fixNumber;
try {
setLoading(true);
await loginWithNomor(realNumber);
const loginRes = await loginWithNomor(realNumber);
if (!loginRes) {
setModalVisible(true);
}
setNumberToEULA(realNumber);
} catch (error) {
console.log("Error login", error);
Toast.show({
@@ -98,30 +111,6 @@ 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) {
@@ -148,6 +137,7 @@ export default function LoginView() {
}
>
<View style={GStyles.authContainer}>
<View>
<View style={GStyles.authContainerTitle}>
<Text style={GStyles.authSubTitle}>WELCOME TO</Text>
@@ -185,11 +175,41 @@ export default function LoginView() {
<ButtonCustom onPress={handleLogin} isLoading={loading}>
Login
</ButtonCustom>
<Spacing />
{/* <ButtonCustom onPress={() => router.navigate("/(application)/coba")}>
Coba
<Spacing height={50} />
{/* <ButtonCustom
onPress={() => {
setModalVisible(true);
console.log("Show modal", modalVisible);
}}
>
Show Modal
</ButtonCustom> */}
{/* <CheckboxCustom value={term} onChange={() => setTerm(!term)} /> */}
<Text
style={{ ...GStyles.textLabel, textAlign: "center", fontSize: 12 }}
>
Dengan menggunakan aplikasi ini, Anda telah menyetujui{" "}
<Text
style={{
color: MainColor.yellow,
textDecorationLine: "underline",
}}
onPress={() => {
const toUrl = `${url}/terms-of-service.html`;
openBrowser(toUrl);
}}
>
Syarat & Ketentuan
</Text>{" "}
dan seluruh kebijakan privasi yang berlaku.
</Text>
</View>
<ModalReactNative isVisible={modalVisible}>
<EULASection nomor={numberToEULA || ""} onSetModalVisible={setModalVisible} />
</ModalReactNative>
</NewWrapper>
);
}

View File

@@ -1,4 +1,3 @@
import { CheckboxCustom } from "@/components";
import Spacing from "@/components/_ShareComponent/Spacing";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import ButtonCustom from "@/components/Button/ButtonCustom";
@@ -7,7 +6,6 @@ import { MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import { BASE_URL } from "@/service/api-config";
import { GStyles } from "@/styles/global-styles";
import { openBrowser } from "@/utils/openBrower";
import { MaterialCommunityIcons } from "@expo/vector-icons";
import { useLocalSearchParams } from "expo-router";
import { useState } from "react";
@@ -17,7 +15,7 @@ import Toast from "react-native-toast-message";
export default function RegisterView() {
const { nomor } = useLocalSearchParams();
const [username, setUsername] = useState("");
const [term, setTerm] = useState(false);
// const [term, setTerm] = useState(false);
const url = BASE_URL;
const { registerUser, isLoading } = useAuth();
@@ -65,7 +63,7 @@ export default function RegisterView() {
await registerUser({
nomor: nomor as string,
username: usernameLower,
termsOfServiceAccepted: term,
termsOfServiceAccepted: true,
});
}
@@ -106,12 +104,12 @@ export default function RegisterView() {
flexDirection: "row",
alignItems: "center",
marginTop: 16,
marginBottom: 16,
// marginBottom: 16,
}}
>
<CheckboxCustom value={term} onChange={() => setTerm(!term)} />
{/* <CheckboxCustom value={term} onChange={() => setTerm(!term)} /> */}
<Text style={GStyles.textLabel}>
{/* <Text style={GStyles.textLabel}>
Saya setuju dengan{" "}
<Text
style={{
@@ -126,11 +124,11 @@ export default function RegisterView() {
Syarat & Ketentuan
</Text>{" "}
yang melarang konten tidak pantas dan perilaku merugikan.
</Text>
</Text> */}
</View>
<ButtonCustom
disabled={!term}
disabled={isLoading}
isLoading={isLoading}
onPress={handleRegister}
>

View File

@@ -17,7 +17,12 @@ export default function Home_BottomFeatureSection() {
});
// console.log("[DATA JOB]", JSON.stringify(response.data, null, 2));
const result = response.data.slice(-2);
const result = response.data
.sort(
(a: any, b: any) =>
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
)
.slice(0, 2);
setListData(result);
} catch (error) {
console.log("[ERROR]", error);

View File

@@ -1,12 +1,20 @@
import { ITabs } from "@/components/_Interface/types";
import { Platform } from "react-native";
export const tabsHome: any = ({acceptedForumTermsAt, profileId}: {acceptedForumTermsAt: Date, profileId: string}) => [
export const tabsHome: any = ({
acceptedForumTermsAt,
profileId,
}: {
acceptedForumTermsAt: Date;
profileId: string;
}) => [
{
id: "forum",
icon: "chatbubble-ellipses-outline",
activeIcon: "chatbubble-ellipses",
label: "Forum",
path: acceptedForumTermsAt ? "/forum" : "/forum/terms",
// path: acceptedForumTermsAt ? "/forum" : "/forum/terms",
path: "/forum",
isActive: true,
disabled: false,
},
@@ -25,8 +33,8 @@ export const tabsHome: any = ({acceptedForumTermsAt, profileId}: {acceptedForumT
activeIcon: "map",
label: "Maps",
path: "/maps",
isActive: true,
disabled: false,
isActive: Platform.OS === "ios" ? true : false,
disabled: Platform.OS === "ios" ? false : true,
},
{
id: "profile",

View File

@@ -7,7 +7,6 @@ import {
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";
@@ -22,8 +21,6 @@ export default function Investment_BoxBerandaSection({
id: string;
data: any;
}) {
// console.log("[DATA By one]", JSON.stringify(data, null, 2));
const [value, setValue] = useState({
sisa: 0,
reminder: false,
@@ -33,8 +30,6 @@ export default function Investment_BoxBerandaSection({
updateCountDown();
}, [data]);
console.log("[DATA BERANDA]", JSON.stringify(data, null, 2));
const updateCountDown = () => {
const countDown = countDownAndCondition({
duration: data?.pencarianInvestor,

View File

@@ -1,16 +1,28 @@
import { BaseBox, StackCustom, TextCustom, ProgressCustom } from "@/components";
export default function Invesment_BoxProgressSection({progress, status}: {progress: number, status: string}) {
return (
<>
{status === "publish" && (
<BaseBox>
<StackCustom>
<TextCustom bold>Progress Saham</TextCustom>
<ProgressCustom label={(progress || 0) + "%"} value={progress || 0} size="lg" />
</StackCustom>
</BaseBox>
)}
</>
);
}
export default function Invesment_BoxProgressSection({
progress,
status,
}: {
progress: number;
status: string;
}) {
return (
<>
{status === "publish" && (
<BaseBox>
<StackCustom>
<TextCustom bold>Progress Saham</TextCustom>
<ProgressCustom
label={(progress || 0) + "%"}
value={progress || 0}
size="lg"
animated
color="primary"
/>
</StackCustom>
</BaseBox>
)}
</>
);
}

View File

@@ -70,7 +70,7 @@ export const drawerItemsProfile = ({
color={AccentColor.white}
/>
),
label: "Blocked List",
label: "Daftar Blokir",
path: `/(application)/profile/${id}/blocked-list`,
value: "blocked-list",
},

View File

@@ -15,6 +15,7 @@ export default function AppRoot() {
name="index"
options={{ title: "", headerBackVisible: false }}
/>
<Stack.Screen name="eula" options={{ title: "Terms & Conditions", headerBackVisible: false }} />
<Stack.Screen name="+not-found" options={{ title: "" }} />
<Stack.Screen
name="verification"

View File

@@ -1,3 +1,4 @@
import { typeRejectedData } from "@/types/type-collect-other";
import { apiConfig } from "../api-config";
export async function apiAdminDonation({
@@ -33,7 +34,7 @@ export async function apiAdminDonationUpdateStatus({
}: {
id: string;
changeStatus: "publish" | "review" | "reject";
data?: string;
data?: typeRejectedData;
}) {
try {
const response = await apiConfig.put(

View File

@@ -57,9 +57,19 @@ export async function apiAdminForumListReportCommentById({
}
}
export async function apiAdminForumDeactivateComment({ id }: { id: string }) {
export async function apiAdminForumDeactivateComment({
id,
data,
}: {
id: string;
data: { senderId: string };
}) {
console.log("data", data)
try {
const response = await apiConfig.put(`/mobile/admin/forum/${id}/comment`);
const response = await apiConfig.put(`/mobile/admin/forum/${id}/comment`, {
data: data,
});
return response.data;
} catch (error) {
throw error;
@@ -81,9 +91,17 @@ export async function apiAdminForumListReportPostingById({
}
}
export async function apiAdminForumDeactivatePosting({ id }: { id: string }) {
export async function apiAdminForumDeactivatePosting({
id,
data,
}: {
id: string;
data: { senderId: string };
}) {
try {
const response = await apiConfig.put(`/mobile/admin/forum/${id}`);
const response = await apiConfig.put(`/mobile/admin/forum/${id}`, {
data: data,
});
return response.data;
} catch (error) {
throw error;

View File

@@ -1,3 +1,4 @@
import { typeRejectedData } from "@/types/type-collect-other";
import { apiConfig } from "../api-config";
export async function apiAdminInvestment({
@@ -38,7 +39,7 @@ export async function apiAdminInvestasiUpdateByStatus({
}: {
id: string;
status: "publish" | "review" | "reject";
data: any;
data: typeRejectedData;
}) {
try {
const response = await apiConfig.put(
@@ -97,6 +98,7 @@ export async function apiAdminInvestmentUpdateInvoice({
data: {
investasiId: string;
lembarTerbeli: number;
senderId: string
};
}) {
try {

View File

@@ -28,13 +28,15 @@ export const apiAdminUserAccessUpdateStatus = async ({
id,
active,
role,
category,
}: {
id: string;
active?: boolean;
role?: "user" | "admin" | "super_admin";
category: "access" | "role";
}) => {
try {
const response = await apiConfig.put(`/mobile/admin/user/${id}`, {
const response = await apiConfig.put(`/mobile/admin/user/${id}?category=${category}`, {
data: {
active,
role,

View File

@@ -1,3 +1,4 @@
import { typeRejectedData } from "@/types/type-collect-other";
import { apiConfig } from "../api-config";
export async function apiAdminVoting({
@@ -32,7 +33,7 @@ export async function apiAdminVotingUpdateStatus({
status,
}: {
id: string;
data?: string;
data?: typeRejectedData;
status: "publish" | "review" | "reject";
}) {
try {

View File

@@ -119,3 +119,62 @@ export async function apiForumDeleteComment({ id }: { id: string }) {
throw error;
}
}
export async function apiForumGetReportPosting({id}: {id:string}) {
try {
const response = await apiConfig.get(`/mobile/forum/${id}/preview-report-posting`);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiForumGetReportComment({id}: {id:string}) {
try {
const response = await apiConfig.get(`/mobile/forum/${id}/preview-report-comment`);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiForumCreateReportPosting({
id,
data,
}: {
id: string;
data: any;
}) {
try {
const response = await apiConfig.post(
`/mobile/forum/${id}/report-posting`,
{
data: data,
}
);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiForumCreateReportCommentar({
id,
data,
}: {
id: string;
data: any;
}) {
try {
const response = await apiConfig.post(
`/mobile/forum/${id}/report-commentar`,
{
data: data,
}
);
return response.data;
} catch (error) {
throw error;
}
}

Some files were not shown because too many files have changed in this diff Show More