Compare commits
8 Commits
notificati
...
notificati
| Author | SHA1 | Date | |
|---|---|---|---|
| c2682246d6 | |||
| 465e01015e | |||
| 3b15871ad4 | |||
| 9123e73606 | |||
| 6e2046467f | |||
| ca33dd83bb | |||
| ea3fbdc541 | |||
| 33cd47aaed |
@@ -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'}\""
|
||||
|
||||
@@ -21,7 +21,7 @@ export default {
|
||||
"Aplikasi membutuhkan akses lokasi untuk menampilkan peta.",
|
||||
},
|
||||
associatedDomains: ["applinks:cld-dkr-staging-hipmi.wibudev.com"],
|
||||
buildNumber: "17",
|
||||
buildNumber: "19",
|
||||
},
|
||||
|
||||
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: [
|
||||
{
|
||||
|
||||
@@ -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={{
|
||||
@@ -520,7 +525,7 @@ export default function UserLayout() {
|
||||
options={{
|
||||
title: "Job Vacancy",
|
||||
// headerLeft: () => <BackButton path="/home" />,
|
||||
// Note: headerLeft di pindahkan ke Tabs Layout
|
||||
// NOTE: headerLeft di pindahkan ke Tabs Layout
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
@@ -611,6 +616,15 @@ export default function UserLayout() {
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="forum/[id]/preview-report-posting"
|
||||
options={{
|
||||
title: "Preview Laporan Diskusi",
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
|
||||
{/* ========== Maps Section ========= */}
|
||||
<Stack.Screen
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 })}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { NewWrapper, TextCustom } from "@/components";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import {
|
||||
apiForumGetOne,
|
||||
apiForumGetReportPosting,
|
||||
} from "@/service/api-client/api-forum";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function ForumPreviewReportPosting() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const { user } = useAuth();
|
||||
const [data, setData] = useState<any | null>(null);
|
||||
// Status
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData(id as string);
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadData = async (id: string) => {
|
||||
try {
|
||||
const response = await apiForumGetReportPosting({ id });
|
||||
setData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
<NewWrapper>
|
||||
<TextCustom>Halaman preview repost posting</TextCustom>
|
||||
<TextCustom>{JSON.stringify(data, null, 2)}</TextCustom>
|
||||
|
||||
<TextCustom>untuk report jomentar beda halaman</TextCustom>
|
||||
|
||||
</NewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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>
|
||||
))
|
||||
)}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* 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 {
|
||||
@@ -23,19 +24,7 @@ export default function JobTabsLayout() {
|
||||
useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerLeft: () => (
|
||||
<BackButton
|
||||
onPress={() => {
|
||||
if (from === "notifications") {
|
||||
router.replace(`/notifications?category=${category}`);
|
||||
} else {
|
||||
if (from) {
|
||||
router.replace(`/${from}` as any);
|
||||
} else {
|
||||
router.navigate("/home");
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<BackButtonFromNotification from={from as string} category={category as string} />
|
||||
),
|
||||
});
|
||||
}, [from, router, navigation]);
|
||||
|
||||
@@ -1,20 +1,27 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AlertDefaultSystem,
|
||||
BackButton,
|
||||
BaseBox,
|
||||
DrawerCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
NewWrapper,
|
||||
ScrollableCustom,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import { IconDot } from "@/components/_Icon/IconComponent";
|
||||
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
|
||||
import NoDataText from "@/components/_ShareComponent/NoDataText";
|
||||
import { AccentColor } from "@/constants/color-palet";
|
||||
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, useLocalSearchParams } 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";
|
||||
@@ -39,8 +46,9 @@ const fixPath = ({
|
||||
|
||||
const separator = deepLink.includes("?") ? "&" : "?";
|
||||
|
||||
const fixedPath =
|
||||
`${deepLink}${separator}from=notifications&category=${_.lowerCase(categoryApp)}`;
|
||||
const fixedPath = `${deepLink}${separator}from=notifications&category=${_.lowerCase(
|
||||
categoryApp
|
||||
)}`;
|
||||
|
||||
console.log("Fix Path", fixedPath);
|
||||
|
||||
@@ -70,7 +78,7 @@ const BoxNotification = ({
|
||||
categoryApp: data.kategoriApp,
|
||||
});
|
||||
|
||||
router.replace(newPath as any);
|
||||
router.navigate(newPath as any);
|
||||
selectedCategory(activeCategory as string);
|
||||
|
||||
if (!data.isRead) {
|
||||
@@ -103,6 +111,9 @@ export default function Notifications() {
|
||||
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);
|
||||
@@ -142,33 +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 />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<NoDataText text="Belum ada notifikasi" />
|
||||
) : (
|
||||
listData.map((e, i) => (
|
||||
<View key={i}>
|
||||
<BoxNotification data={e} activeCategory={activeCategory as any} />
|
||||
</View>
|
||||
))
|
||||
)}
|
||||
</NewWrapper>
|
||||
</DrawerCustom>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,20 +1,26 @@
|
||||
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";
|
||||
@@ -70,6 +76,9 @@ export default function AdminNotification() {
|
||||
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);
|
||||
@@ -89,7 +98,7 @@ export default function AdminNotification() {
|
||||
id: user?.id as any,
|
||||
category: activeCategory as any,
|
||||
});
|
||||
|
||||
|
||||
if (response.success) {
|
||||
setListData(response.data);
|
||||
} else {
|
||||
@@ -115,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)}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
@@ -154,6 +163,51 @@ export default function AdminNotification() {
|
||||
))
|
||||
)}
|
||||
</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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
9
app/eula.tsx
Normal file
9
app/eula.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import EULAView from "@/screens/Authentication/EULAView";
|
||||
|
||||
export default function EULA() {
|
||||
return (
|
||||
<>
|
||||
<EULAView />
|
||||
</>
|
||||
);
|
||||
}
|
||||
29
components/Button/BackButtonFromNotification.tsx
Normal file
29
components/Button/BackButtonFromNotification.tsx
Normal 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");
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -24,8 +24,6 @@ const DateTimePickerCustom: React.FC<Props> = ({
|
||||
disabled = false,
|
||||
}) => {
|
||||
|
||||
console.log("Date Android Comp", value)
|
||||
|
||||
return (
|
||||
<>
|
||||
{Platform.OS === "ios" ? (
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
apiConfig,
|
||||
apiLogin,
|
||||
apiRegister,
|
||||
apiUpdatedTermCondition,
|
||||
apiValidationCode,
|
||||
} from "@/service/api-config";
|
||||
import { apiDeviceTokenDeleted } from "@/service/api-device-token";
|
||||
@@ -29,6 +30,7 @@ type AuthContextType = {
|
||||
termsOfServiceAccepted: boolean;
|
||||
}) => Promise<void>;
|
||||
userData: (token: string) => Promise<any>;
|
||||
acceptedTerms: (nomor: string) => Promise<any>;
|
||||
};
|
||||
|
||||
// --- Create Context ---
|
||||
@@ -74,28 +76,32 @@ 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));
|
||||
console.log("[RESPONSE AUTH]", JSON.stringify(response, null, 2));
|
||||
|
||||
if (response.success) {
|
||||
console.log("[Keluar provider]", nomor);
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Sukses",
|
||||
text2: "Kode OTP berhasil dikirim",
|
||||
});
|
||||
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;
|
||||
await AsyncStorage.setItem("kode_otp", response.kodeId);
|
||||
router.push(`/verification?nomor=${nomor}`);
|
||||
return;
|
||||
} else {
|
||||
router.push(`/eula?nomor=${nomor}`);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
router.push(`/register?nomor=${nomor}`);
|
||||
Toast.show({
|
||||
type: "info",
|
||||
text1: "Info",
|
||||
text2: "Silahkan mendaftar",
|
||||
});
|
||||
router.push(`/eula?nomor=${nomor}`);
|
||||
|
||||
// Toast.show({
|
||||
// type: "info",
|
||||
// text1: "Info",
|
||||
// text2: "Silahkan mendaftar",
|
||||
// });
|
||||
return;
|
||||
}
|
||||
} catch (error: any) {
|
||||
@@ -226,7 +232,6 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// --- 5. Logout ---
|
||||
|
||||
@@ -256,6 +261,31 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const acceptedTerms = async (nomor: string) => {
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<AuthContext.Provider
|
||||
@@ -271,6 +301,7 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
logout,
|
||||
registerUser,
|
||||
userData,
|
||||
acceptedTerms,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
2
eas.json
2
eas.json
@@ -11,7 +11,7 @@
|
||||
"preview": {
|
||||
"distribution": "internal",
|
||||
"android": {
|
||||
"buildType": "app-bundle"
|
||||
"buildType": "apk"
|
||||
},
|
||||
"ios": {
|
||||
"simulator": false
|
||||
|
||||
@@ -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
|
||||
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
|
||||
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -458,7 +458,7 @@
|
||||
);
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.anonymous.hipmi-mobile";
|
||||
PRODUCT_NAME = HIPMIBadungConnect;
|
||||
PRODUCT_NAME = "HIPMIBadungConnect";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "HIPMIBadungConnect/HIPMIBadungConnect-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -490,7 +490,7 @@
|
||||
);
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.anonymous.hipmi-mobile";
|
||||
PRODUCT_NAME = HIPMIBadungConnect;
|
||||
PRODUCT_NAME = "HIPMIBadungConnect";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "HIPMIBadungConnect/HIPMIBadungConnect-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>17</string>
|
||||
<string>19</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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({
|
||||
|
||||
278
screens/Authentication/EULAView.tsx
Normal file
278
screens/Authentication/EULAView.tsx
Normal file
@@ -0,0 +1,278 @@
|
||||
// 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";
|
||||
|
||||
// Ganti dengan API call ke backend Anda
|
||||
// const acceptEula = async (): Promise<boolean> => {
|
||||
// try {
|
||||
// const response = await fetch("/api/user/update-eula", {
|
||||
// method: "PATCH",
|
||||
// headers: { "Content-Type": "application/json" },
|
||||
// credentials: "include",
|
||||
// body: JSON.stringify({
|
||||
// eulaAcceptedAt: new Date().toISOString(),
|
||||
// eulaVersion: "2026-01-v1", // sesuaikan versi Anda
|
||||
// }),
|
||||
// });
|
||||
// return response.ok;
|
||||
// } catch (error) {
|
||||
// console.error("Gagal menyimpan persetujuan EULA:", error);
|
||||
// return false;
|
||||
// }
|
||||
// };
|
||||
|
||||
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",
|
||||
},
|
||||
});
|
||||
@@ -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}
|
||||
>
|
||||
|
||||
@@ -13,7 +13,8 @@ export const tabsHome: any = ({
|
||||
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,
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -81,9 +81,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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -119,3 +119,12 @@ 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;
|
||||
}
|
||||
}
|
||||
@@ -74,3 +74,10 @@ export async function apiAcceptTermService({
|
||||
return response.data;
|
||||
}
|
||||
|
||||
|
||||
export async function apiUpdatedTermCondition({nomor}: {nomor: string}) {
|
||||
const response = await apiConfig.post(`/auth/mobile-eula`, {
|
||||
nomor: nomor,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
NotificationProp,
|
||||
TypeNotificationCategoryApp
|
||||
TypeNotificationCategoryApp,
|
||||
} from "@/types/type-notification-category";
|
||||
import { apiConfig } from "./api-config";
|
||||
|
||||
@@ -78,9 +78,22 @@ export async function apiNotificationUnreadCount({
|
||||
}
|
||||
}
|
||||
|
||||
export async function apiNotificationMarkAsRead({ id }: { id: string }) {
|
||||
/**
|
||||
* @param id | notification id atau user id
|
||||
* @param category | "all" | "one" , jika "all" id yang harus di masukan adalah user id, jika "one" id yang harus di masukan adalah notification id
|
||||
* @type {string}
|
||||
*/
|
||||
export async function apiNotificationMarkAsRead({
|
||||
id,
|
||||
category,
|
||||
}: {
|
||||
id: string;
|
||||
category: "all" | "one";
|
||||
}) {
|
||||
try {
|
||||
const response = await apiConfig.put(`/mobile/notification/${id}`);
|
||||
const response = await apiConfig.put(
|
||||
`/mobile/notification/${id}?category=${category}`
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
|
||||
4
types/type-collect-other.ts
Normal file
4
types/type-collect-other.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export type typeRejectedData = {
|
||||
catatan?: string;
|
||||
senderId: string;
|
||||
};
|
||||
15
types/type-forum.ts
Normal file
15
types/type-forum.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export interface TypeForum_CommentProps {
|
||||
id: string;
|
||||
isActive: boolean;
|
||||
komentar: string;
|
||||
createdAt: Date;
|
||||
authorId: string;
|
||||
Author: {
|
||||
id: string;
|
||||
username: string;
|
||||
Profile: {
|
||||
id: string;
|
||||
imageId: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -15,21 +15,21 @@ export const formatChatTime = (date: string | Date): string => {
|
||||
const messageDate = dayjs(date);
|
||||
const now = dayjs();
|
||||
|
||||
// Jika hari ini
|
||||
// Hari ini
|
||||
if (messageDate.isSame(now, 'day')) {
|
||||
return messageDate.format('HH:mm'); // contoh: "14.30"
|
||||
return messageDate.format('HH.mm'); // "14.30"
|
||||
}
|
||||
|
||||
// Jika kemarin
|
||||
// Kemarin
|
||||
if (messageDate.isSame(now.subtract(1, 'day'), 'day')) {
|
||||
return messageDate.format('dddd HH:mm');
|
||||
return `Kemarin, ${messageDate.format('HH.mm')}`; // "Kemarin, 14.30"
|
||||
}
|
||||
|
||||
// Jika dalam 7 hari terakhir (tapi bukan kemarin/ hari ini)
|
||||
// Dalam 7 hari terakhir (termasuk hari ini & kemarin sudah di-handle, jadi aman)
|
||||
if (now.diff(messageDate, 'day') < 7) {
|
||||
return messageDate.format('dddd HH:mm'); // contoh: "Senin 14:30"
|
||||
return `${messageDate.format('dddd')}, ${messageDate.format('HH.mm')}`; // "Senin, 13.00"
|
||||
}
|
||||
|
||||
// Lebih dari seminggu lalu → tampilkan tanggal
|
||||
return messageDate.format('D MMM YYYY HH:mm'); // contoh: "12 Mei 2024 14:30"
|
||||
};
|
||||
// Lebih dari 7 hari lalu
|
||||
return messageDate.format('DD - MM - YYYY, HH.mm'); // "05 - 11 - 2025, 14.00"
|
||||
};
|
||||
Reference in New Issue
Block a user