Compare commits

...

23 Commits

Author SHA1 Message Date
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
d098b8ca16 Setup notifikasi untuk android
Fix:
- modified:   service/api-notifications.ts
- modified:   types/type-notification-category.ts

### No Issue
2026-01-05 12:27:23 +08:00
73a473cdc7 Tambah fitur notifikasi untuk android
Add:
- android/app/src/main/res/xml/

Fix:
- android/app/src/main/AndroidManifest.xml
- android/build.gradle
- app/(application)/(user)/test-notifications.tsx
- bun.lock
- package.json

### No Issue
2026-01-02 17:43:23 +08:00
3f85f330d2 Background notifikasi berhasil dibuat
Add:
-components/Notification/BackgroundNotificationHandler.tsx

### No Issue
2025-12-24 17:43:53 +08:00
7743a2467c Fitur notifikasi dan foreground
Add:
- types/type-notification-category.ts

Fix:
- app/(application)/(user)/notifications/index.tsx
- app/(application)/(user)/test-notifications.tsx
- app/(application)/admin/notification/index.tsx
- components/Notification/NotificationInitializer.tsx
- hooks/use-notification-store.tsx
- service/api-notifications.ts
- utils/formatChatTime.ts

### No Issue
2025-12-24 15:29:58 +08:00
54611ef812 Fix notifikasi system
Add:
- screens/Admin/AdminNotificationBell.tsx

Fix:
- app.config.js
- app/(application)/(user)/home.tsx
- app/(application)/(user)/test-notifications.tsx
- app/(application)/admin/_layout.tsx
- app/_layout.tsx
- components/Notification/NotificationInitializer.tsx
- context/AuthContext.tsx
- hooks/use-notification-store.tsx
- ios/HIPMIBadungConnect/Info.plist
- screens/Home/HeaderBell.tsx
- service/api-device-token.ts
- service/api-notifications.ts

### No Issue
2025-12-23 17:45:30 +08:00
1503707eed Notifikasi terhubung ke DB
Add:
- components/Notification/
- hooks/use-notification-store.tsx.back

Fix:
- app.config.js
- app/(application)/(user)/_layout.tsx
- app/(application)/(user)/home.tsx
- app/(application)/(user)/test-notifications.tsx
- app/(application)/_layout.tsx
- app/_layout.tsx
- components/_Icon/IconComponent.tsx
- components/_Icon/IconPlus.tsx
- components/_ShareComponent/NotificationInitializer.tsx
- context/AuthContext.tsx
- hooks/use-notification-store.tsx
- ios/HIPMIBadungConnect/Info.plist
- screens/Home/HeaderBell.tsx
- service/api-device-token.ts
- service/api-notifications.ts

### No Issue
2025-12-19 17:54:49 +08:00
a01a9bd93f Filter console dan clean code
Add:
- service/api-device-token.ts

Fix:
- app/(application)/(user)/home.tsx
- app/(application)/(user)/test-notifications.tsx
- app/_layout.tsx
- components/_ShareComponent/NotificationInitializer.tsx
- context/AuthContext.tsx
- hooks/use-foreground-notifications.ts
- screens/Home/HeaderBell.tsx
- service/api-notifications.ts

### No Issue
2025-12-17 17:46:28 +08:00
05c1cac10f Penerapan ke database
Fix:
- android/app/src/main/AndroidManifest.xml
- app/(application)/(user)/home.tsx
- components/_ShareComponent/NotificationInitializer.tsx
- ios/HIPMIBadungConnect.xcodeproj/project.pbxproj
- ios/HIPMIBadungConnect/Info.plist
- service/api-notifications.ts

### No Issue
2025-12-16 17:47:50 +08:00
d27c01ed56 Percobaan notifikasi
Add:
- app/(application)/(user)/test-notifications.tsx
- components/_ShareComponent/NotificationInitializer.tsx
- screens/Home/HeaderBell.tsx
- service/api-notifications.ts

Fix:
- app/(application)/(user)/home.tsx
- app/_layout.tsx
- screens/Authentication/LoginView.tsx

### No Issue
2025-12-15 17:46:05 +08:00
34680a4c38 test 2025-12-13 17:31:48 +08:00
43c8c105cf Notification firts commit
### No Issue
2025-12-12 17:50:49 +08:00
98 changed files with 3014 additions and 593 deletions

View File

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

View File

@@ -15,8 +15,8 @@
<data android:scheme="https"/> <data android:scheme="https"/>
</intent> </intent>
</queries> </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"> <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"/> <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="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_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.notifications.default_notification_icon" android:resource="@drawable/notification_icon"/>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
<exclude domain="sharedpref" path="SECURESTORE"/>
</full-backup-content>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<data-extraction-rules>
<cloud-backup>
<exclude domain="sharedpref" path="SECURESTORE"/>
</cloud-backup>
<device-transfer>
<exclude domain="sharedpref" path="SECURESTORE"/>
</device-transfer>
</data-extraction-rules>

View File

@@ -6,7 +6,7 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.google.gms:google-services:4.4.1' classpath 'com.google.gms:google-services:4.4.1'
classpath('com.android.tools.build:gradle') classpath('com.android.tools.build:gradle')
classpath('com.facebook.react:react-native-gradle-plugin') classpath('com.facebook.react:react-native-gradle-plugin')
classpath('org.jetbrains.kotlin:kotlin-gradle-plugin') classpath('org.jetbrains.kotlin:kotlin-gradle-plugin')

View File

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

View File

@@ -1,4 +1,6 @@
import { BackButton } from "@/components"; import { BackButton } from "@/components";
import { IconPlus } from "@/components/_Icon";
import { IconDot } from "@/components/_Icon/IconComponent";
import LeftButtonCustom from "@/components/Button/BackButton"; import LeftButtonCustom from "@/components/Button/BackButton";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value"; import { ICON_SIZE_SMALL } from "@/constants/constans-value";
@@ -51,24 +53,35 @@ export default function UserLayout() {
/> />
{/* ========== Notification Section ========= */} {/* ========== Notification Section ========= */}
<Stack.Screen
{/* DIPINDAH DI FILE NOTIFICATION USER */}
{/* <Stack.Screen
name="notifications/index" name="notifications/index"
options={{ options={{
title: "Notifikasi", title: "Notifikasi",
headerLeft: () => <BackButton />, headerLeft: () => <BackButton />,
// headerRight: () => (
// <IconPlus
// color={MainColor.yellow}
// onPress={() => router.push("/test-notifications")}
// />
// ),
}} }}
/> /> */}
{/* ========== Event Section ========= */} {/* ========== Event Section ========= */}
<Stack.Screen <Stack.Screen
name="event/(tabs)" name="event/(tabs)"
options={{ options={{
title: "Event", title: "Event",
headerLeft: () => ( // NOTE: DIPINDAH DI FILE /Event/(Tabs)/_layout.tsx
<LeftButtonCustom path="/(application)/(user)/home" /> // headerLeft: () => (
), // <LeftButtonCustom path="/(application)/(user)/home" />
// ),
}} }}
/> />
<Stack.Screen <Stack.Screen
name="event/create" name="event/create"
options={{ options={{
@@ -511,7 +524,8 @@ export default function UserLayout() {
name="job/(tabs)" name="job/(tabs)"
options={{ options={{
title: "Job Vacancy", title: "Job Vacancy",
headerLeft: () => <BackButton path="/home" />, // headerLeft: () => <BackButton path="/home" />,
// NOTE: headerLeft di pindahkan ke Tabs Layout
}} }}
/> />
<Stack.Screen <Stack.Screen
@@ -602,6 +616,20 @@ export default function UserLayout() {
headerLeft: () => <BackButton />, 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 ========= */} {/* ========== Maps Section ========= */}
<Stack.Screen <Stack.Screen

View File

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

View File

@@ -4,10 +4,34 @@ import {
IconHome, IconHome,
IconStatus, IconStatus,
} from "@/components/_Icon"; } from "@/components/_Icon";
import BackButtonFromNotification from "@/components/Button/BackButtonFromNotification";
import { TabsStyles } from "@/styles/tabs-styles"; 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() { 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 ( return (
<Tabs screenOptions={TabsStyles}> <Tabs screenOptions={TabsStyles}>
<Tabs.Screen <Tabs.Screen

View File

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

View File

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

View File

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

View File

@@ -14,7 +14,7 @@ import { apiEventCreate } from "@/service/api-client/api-event";
import { apiMasterEventType } from "@/service/api-client/api-master"; import { apiMasterEventType } from "@/service/api-client/api-master";
import { DateTimePickerEvent } from "@react-native-community/datetimepicker"; import { DateTimePickerEvent } from "@react-native-community/datetimepicker";
import { router } from "expo-router"; import { router } from "expo-router";
import React, { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
interface EventCreateProps { interface EventCreateProps {
@@ -78,23 +78,6 @@ export default function EventCreate() {
return; 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 { try {
setIsLoading(true); setIsLoading(true);
@@ -110,7 +93,7 @@ export default function EventCreate() {
const response = await apiEventCreate(newData); const response = await apiEventCreate(newData);
console.log("Response", JSON.stringify(response, null, 2)); console.log("Response", JSON.stringify(response, null, 2));
router.replace("/event/status"); router.replace("/event/status?status=review");
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} finally { } finally {

View File

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

View File

@@ -6,7 +6,7 @@ import {
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth"; 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 { router, useLocalSearchParams } from "expo-router";
import { useState } from "react"; import { useState } from "react";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";

View File

@@ -6,7 +6,7 @@ import {
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth"; 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 { router, useLocalSearchParams } from "expo-router";
import { useState } from "react"; import { useState } from "react";
import Toast from "react-native-toast-message"; 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 { AccentColor, MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth"; import { useAuth } from "@/hooks/use-auth";
import Forum_ReportListSection from "@/screens/Forum/ReportListSection"; 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 { router, useLocalSearchParams } from "expo-router";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";

View File

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

View File

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

View File

@@ -3,7 +3,9 @@
import { StackCustom, ViewWrapper } from "@/components"; import { StackCustom, ViewWrapper } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth"; import { useAuth } from "@/hooks/use-auth";
import { useNotificationStore } from "@/hooks/use-notification-store";
import Home_BottomFeatureSection from "@/screens/Home/bottomFeatureSection"; import Home_BottomFeatureSection from "@/screens/Home/bottomFeatureSection";
import HeaderBell from "@/screens/Home/HeaderBell";
import Home_ImageSection from "@/screens/Home/imageSection"; import Home_ImageSection from "@/screens/Home/imageSection";
import TabSection from "@/screens/Home/tabSection"; import TabSection from "@/screens/Home/tabSection";
import { tabsHome } from "@/screens/Home/tabsList"; import { tabsHome } from "@/screens/Home/tabsList";
@@ -12,21 +14,21 @@ import { apiUser } from "@/service/api-client/api-user";
import { apiVersion } from "@/service/api-config"; import { apiVersion } from "@/service/api-config";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { Redirect, router, Stack, useFocusEffect } from "expo-router"; import { Redirect, router, Stack, useFocusEffect } from "expo-router";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useState } from "react";
import { RefreshControl } from "react-native"; import { RefreshControl } from "react-native";
export default function Application() { export default function Application() {
const { token, user, userData } = useAuth(); const { token, user, userData } = useAuth();
const [data, setData] = useState<any>(); const [data, setData] = useState<any>();
const [refreshing, setRefreshing] = useState(false); const [refreshing, setRefreshing] = useState(false);
console.log("[User] >>", JSON.stringify(user?.id, null, 2)); const { syncUnreadCount } = useNotificationStore();
// ‼️ Untuk cek apakah: 1. user ada, 2. user punya profile, 3. accept temrs of forum nya ada atau tidak
useFocusEffect( useFocusEffect(
useCallback(() => { useCallback(() => {
onLoadData(); onLoadData();
checkVersion(); checkVersion();
userData(token as string); userData(token as string);
syncUnreadCount();
}, [user?.id, token]) }, [user?.id, token])
); );
@@ -52,10 +54,10 @@ export default function Application() {
setRefreshing(false); setRefreshing(false);
}, []); }, []);
if (user && user?.termsOfServiceAccepted === false) { // if (user && user?.termsOfServiceAccepted === false) {
console.log("User is not accept term service"); // console.log("User is not accept term service");
return <Redirect href={`/terms-agreement`} />; // return <Redirect href={`/terms-agreement`} />;
} // }
if (data && data?.active === false) { if (data && data?.active === false) {
console.log("User is not active"); console.log("User is not active");
@@ -82,17 +84,7 @@ export default function Application() {
}} }}
/> />
), ),
headerRight: () => ( headerRight: () => <HeaderBell />,
<Ionicons
disabled={true}
name="notifications"
size={20}
color={MainColor.placeholder}
onPress={() => {
router.push("/notifications");
}}
/>
),
}} }}
/> />
<ViewWrapper <ViewWrapper
@@ -109,6 +101,10 @@ export default function Application() {
} }
> >
<StackCustom> <StackCustom>
{/* <ButtonCustom onPress={() => router.push("./test-notifications")}>
Test Notif
</ButtonCustom> */}
<Home_ImageSection /> <Home_ImageSection />
<Home_FeatureSection /> <Home_FeatureSection />

View File

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

View File

@@ -15,6 +15,7 @@ import Investment_ButtonInvestasiSection from "@/screens/Invesment/ButtonInvesta
import Invesment_ComponentBoxOnBottomDetail from "@/screens/Invesment/ComponentBoxOnBottomDetail"; import Invesment_ComponentBoxOnBottomDetail from "@/screens/Invesment/ComponentBoxOnBottomDetail";
import Invesment_DetailDataPublishSection from "@/screens/Invesment/DetailDataPublishSection"; import Invesment_DetailDataPublishSection from "@/screens/Invesment/DetailDataPublishSection";
import { apiInvestmentGetOne } from "@/service/api-client/api-investment"; import { apiInvestmentGetOne } from "@/service/api-client/api-investment";
import { countDownAndCondition } from "@/utils/countDownAndCondition";
import { AntDesign, MaterialIcons } from "@expo/vector-icons"; import { AntDesign, MaterialIcons } from "@expo/vector-icons";
import { import {
router, router,
@@ -23,7 +24,7 @@ import {
useLocalSearchParams, useLocalSearchParams,
} from "expo-router"; } from "expo-router";
import _ from "lodash"; import _ from "lodash";
import { useCallback, useState } from "react"; import { useCallback, useEffect, useState } from "react";
export default function InvestmentDetailStatus() { export default function InvestmentDetailStatus() {
const { user } = useAuth(); const { user } = useAuth();
@@ -63,6 +64,29 @@ export default function InvestmentDetailStatus() {
setOpenDrawerPublish(false); 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 = ( const bottomSection = (
<Invesment_ComponentBoxOnBottomDetail <Invesment_ComponentBoxOnBottomDetail
id={data?.id} id={data?.id}
@@ -72,7 +96,11 @@ export default function InvestmentDetailStatus() {
); );
const buttonSection = ( 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 ( return (

View File

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

View File

@@ -1,34 +1,61 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { BackButton } from "@/components";
import { IconHome, IconStatus } from "@/components/_Icon"; import { IconHome, IconStatus } from "@/components/_Icon";
import BackButtonFromNotification from "@/components/Button/BackButtonFromNotification";
import { TabsStyles } from "@/styles/tabs-styles"; import { TabsStyles } from "@/styles/tabs-styles";
import { Ionicons } from "@expo/vector-icons"; 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() { 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 ( return (
<Tabs screenOptions={TabsStyles}> <>
<Tabs.Screen <Tabs screenOptions={TabsStyles}>
name="index" <Tabs.Screen
options={{ name="index"
title: "Beranda", options={{
tabBarIcon: ({ color }) => <IconHome color={color} />, title: "Beranda",
}} tabBarIcon: ({ color }) => <IconHome color={color} />,
/> }}
<Tabs.Screen />
name="status" <Tabs.Screen
options={{ name="status"
title: "Status", options={{
tabBarIcon: ({ color }) => <IconStatus color={color} />, title: "Status",
}} tabBarIcon: ({ color }) => <IconStatus color={color} />,
/> }}
<Tabs.Screen />
name="archive" <Tabs.Screen
options={{ name="archive"
title: "Arsip", options={{
tabBarIcon: ({ color }) => ( title: "Arsip",
<Ionicons size={20} name="archive" color={color} /> tabBarIcon: ({ color }) => (
), <Ionicons size={20} name="archive" color={color} />
}} ),
/> }}
</Tabs> />
</Tabs>
</>
); );
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,79 +1,101 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AlertDefaultSystem,
BackButton,
BaseBox, BaseBox,
Grid, DrawerCustom,
MenuDrawerDynamicGrid,
NewWrapper,
ScrollableCustom, ScrollableCustom,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper,
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { IconDot } from "@/components/_Icon/IconComponent";
import { useState } from "react"; import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
import { View } from "react-native"; import NoDataText from "@/components/_ShareComponent/NoDataText";
import { AccentColor, MainColor } from "@/constants/color-palet";
const categories = [ import { ICON_SIZE_SMALL } from "@/constants/constans-value";
{ value: "all", label: "Semua" }, import { useAuth } from "@/hooks/use-auth";
{ value: "event", label: "Event" }, import { useNotificationStore } from "@/hooks/use-notification-store";
{ value: "job", label: "Job" }, import { apiGetNotificationsById } from "@/service/api-notifications";
{ value: "voting", label: "Voting" }, import { listOfcategoriesAppNotification } from "@/types/type-notification-category";
{ value: "donasi", label: "Donasi" }, import { formatChatTime } from "@/utils/formatChatTime";
{ value: "investasi", label: "Investasi" }, import { Ionicons } from "@expo/vector-icons";
{ value: "forum", label: "Forum" }, import { router, Stack, useFocusEffect, useLocalSearchParams } from "expo-router";
{ value: "collaboration", label: "Collaboration" }, import _ from "lodash";
]; import { useCallback, useState } from "react";
import { RefreshControl, View } from "react-native";
const selectedCategory = (value: string) => { const selectedCategory = (value: string) => {
const category = categories.find((c) => c.value === value); const category = listOfcategoriesAppNotification.find(
(c) => c.value === value
);
return category?.label; 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 = ({ const BoxNotification = ({
index, data,
activeCategory, activeCategory,
}: { }: {
index: number; data: any;
activeCategory: string | null; activeCategory: string | null;
}) => { }) => {
// console.log("DATA NOTIFICATION", JSON.stringify(data, null, 2));
const { markAsRead } = useNotificationStore();
return ( return (
<> <>
<BaseBox <BaseBox
onPress={() => backgroundColor={data.isRead ? AccentColor.darkblue : AccentColor.blue}
console.log( onPress={() => {
"Notification >", // console.log(
selectedCategory(activeCategory as string) // "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> <StackCustom>
<TextCustom bold> <TextCustom truncate={2} bold>
# {selectedCategory(activeCategory as string)} {data.title}
</TextCustom> </TextCustom>
<View <TextCustom truncate={2}>{data.pesan}</TextCustom>
style={{
borderBottomColor: MainColor.white_gray,
borderBottomWidth: 1,
}}
/>
<TextCustom truncate={2}> <TextCustom size="small" color="gray">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Sint odio {formatChatTime(data.createdAt)}
unde quidem voluptate quam culpa sequi molestias ipsa corrupti id,
soluta, nostrum adipisci similique, et illo asperiores deleniti eum
labore.
</TextCustom> </TextCustom>
<Grid>
<Grid.Col span={6}>
<TextCustom size="small" color="gray">
{index + 1} Agustus 2025
</TextCustom>
</Grid.Col>
<Grid.Col span={6} style={{ alignItems: "flex-end" }}>
<TextCustom size="small" color="gray">
Belum lihat
</TextCustom>
</Grid.Col>
</Grid>
</StackCustom> </StackCustom>
</BaseBox> </BaseBox>
</> </>
@@ -81,31 +103,146 @@ const BoxNotification = ({
}; };
export default function Notifications() { export default function Notifications() {
const [activeCategory, setActiveCategory] = useState<string | null>("all"); const { user } = useAuth();
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) => { const handlePress = (item: any) => {
setActiveCategory(item.value); setActiveCategory(item.value);
// tambahkan logika lain seperti filter dsb. // tambahkan logika lain seperti filter dsb.
}; };
return (
<ViewWrapper useFocusEffect(
headerComponent={ useCallback(() => {
<ScrollableCustom fecthData();
data={categories.map((e, i) => ({ }, [activeCategory])
id: i, );
label: e.label,
value: e.value, const fecthData = async () => {
}))} try {
onButtonPress={handlePress} setLoading(true);
activeId={activeCategory as string} const response = await apiGetNotificationsById({
/> id: user?.id as any,
category: activeCategory as any,
});
if (response.success) {
setListData(response.data);
} else {
setListData([]);
} }
> } catch (error) {
{Array.from({ length: 20 }).map((e, i) => ( console.log("Error Notification", error);
<View key={i}> } finally {
<BoxNotification index={i} activeCategory={activeCategory as any} /> setLoading(false);
</View> }
))} };
</ViewWrapper>
const onRefresh = () => {
setRefreshing(true);
fecthData();
setRefreshing(false);
};
return (
<>
<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);
},
});
}
}}
/>
</DrawerCustom>
</>
); );
} }

View File

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

View File

@@ -0,0 +1,75 @@
import {
ButtonCustom,
NewWrapper,
StackCustom,
TextInputCustom,
} from "@/components";
import { useAuth } from "@/hooks/use-auth";
import { apiNotificationsSend } from "@/service/api-notifications";
import { useState } from "react";
import Toast from "react-native-toast-message";
export default function TestNotification() {
const { user } = useAuth();
const [data, setData] = useState("");
const [loading, setLoading] = useState(false);
const handleSubmit = async () => {
try {
console.log("[Data Dikirim]", data);
setLoading(true);
const response = await apiNotificationsSend({
data: {
title: "Test Notification !!",
body: data,
userLoginId: user?.id || "",
appId: "hipmi",
status: "publish",
kategoriApp: "JOB",
type: "announcement",
deepLink: "/job/cmhjz8u3h0005cfaxezyeilrr",
},
});
if (response.success) {
console.log("[RES SEND NOTIF]", JSON.stringify(response, null, 2));
Toast.show({
type: "success",
text1: "Notifikasi berhasil dikirim",
});
} else {
Toast.show({
type: "error",
text1: "Gagal mengirim notifikasi",
});
}
} catch (error) {
console.log("[ERROR SEND NOTIF]", error);
Toast.show({
type: "error",
text1: "Gagal mengirim notifikasi",
});
} finally {
setLoading(false);
}
};
return (
<>
<NewWrapper>
<StackCustom>
<TextInputCustom
required
label="Pesan"
placeholder="Masukkan pesan"
value={data}
onChangeText={(text) => setData(text)}
/>
<ButtonCustom onPress={handleSubmit} disabled={loading}>
Kirim
</ButtonCustom>
</StackCustom>
</NewWrapper>
</>
);
}

View File

@@ -1,13 +1,37 @@
import { import {
IconContribution, IconContribution,
IconHistory, IconHistory,
IconHome, IconHome,
IconStatus, IconStatus,
} from "@/components/_Icon"; } from "@/components/_Icon";
import BackButtonFromNotification from "@/components/Button/BackButtonFromNotification";
import { TabsStyles } from "@/styles/tabs-styles"; 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() { 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 ( return (
<Tabs screenOptions={TabsStyles}> <Tabs screenOptions={TabsStyles}>
<Tabs.Screen <Tabs.Screen

View File

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

View File

@@ -13,6 +13,7 @@ import {
} from "@/components"; } from "@/components";
import { IconArchive, IconContribution } from "@/components/_Icon"; import { IconArchive, IconContribution } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types"; import { IMenuDrawerItem } from "@/components/_Interface/types";
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
import { useAuth } from "@/hooks/use-auth"; import { useAuth } from "@/hooks/use-auth";
import Voting_BoxDetailHasilVotingSection from "@/screens/Voting/BoxDetailHasilVotingSection"; import Voting_BoxDetailHasilVotingSection from "@/screens/Voting/BoxDetailHasilVotingSection";
import { Voting_BoxDetailPublishSection } from "@/screens/Voting/BoxDetailPublishSection"; import { Voting_BoxDetailPublishSection } from "@/screens/Voting/BoxDetailPublishSection";
@@ -22,13 +23,14 @@ import {
apiVotingUpdateData, apiVotingUpdateData,
} from "@/service/api-client/api-voting"; } from "@/service/api-client/api-voting";
import { today } from "@/utils/dateTimeView"; import { today } from "@/utils/dateTimeView";
import dayjs from "dayjs";
import { import {
router, router,
Stack, Stack,
useFocusEffect, useFocusEffect,
useLocalSearchParams, useLocalSearchParams,
} from "expo-router"; } from "expo-router";
import React, { useCallback, useState } from "react"; import React, { useCallback, useEffect, useState } from "react";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
export default function VotingDetail() { export default function VotingDetail() {
@@ -119,6 +121,23 @@ export default function VotingDetail() {
setOpenDrawerPublish(false); 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 ( return (
<> <>
<Stack.Screen <Stack.Screen

View File

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

View File

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

View File

@@ -1,16 +1,29 @@
import { BackButton } from "@/components"; import { BackButton } from "@/components";
import BackgroundNotificationHandler from "@/components/Notification/BackgroundNotificationHandler";
import NotificationInitializer from "@/components/Notification/NotificationInitializer";
import { NotificationProvider } from "@/hooks/use-notification-store";
import { HeaderStyles } from "@/styles/header-styles"; import { HeaderStyles } from "@/styles/header-styles";
import { Stack } from "expo-router"; import { Stack } from "expo-router";
export default function ApplicationLayout() { export default function ApplicationLayout() {
return (
<>
<NotificationProvider>
<NotificationInitializer />
<BackgroundNotificationHandler />
<ApplicationStack />
</NotificationProvider>
</>
);
}
function ApplicationStack() {
return ( return (
<> <>
<Stack screenOptions={HeaderStyles}> <Stack screenOptions={HeaderStyles}>
<Stack.Screen name="(user)" options={{ headerShown: false }} /> <Stack.Screen name="(user)" options={{ headerShown: false }} />
<Stack.Screen name="admin" options={{ headerShown: false }} /> <Stack.Screen name="admin" options={{ headerShown: false }} />
{/* Take Picture */} {/* Take Picture */}
<Stack.Screen <Stack.Screen
name="(image)/take-picture/[id]/index" name="(image)/take-picture/[id]/index"

View File

@@ -15,6 +15,7 @@ import {
ICON_SIZE_XLARGE, ICON_SIZE_XLARGE,
} from "@/constants/constans-value"; } from "@/constants/constans-value";
import { useAuth } from "@/hooks/use-auth"; import { useAuth } from "@/hooks/use-auth";
import AdminNotificationBell from "@/screens/Admin/AdminNotificationBell";
import { import {
adminListMenu, adminListMenu,
superAdminListMenu, superAdminListMenu,
@@ -192,11 +193,12 @@ export default function AdminLayout() {
label: "Notifikasi", label: "Notifikasi",
value: "notification", value: "notification",
icon: ( icon: (
<Ionicons // <Ionicons
name="notifications" // name="notifications"
size={ICON_SIZE_SMALL} // size={ICON_SIZE_SMALL}
color={MainColor.white} // color={MainColor.white}
/> // />
<AdminNotificationBell/>
), ),
path: "/admin/notification", path: "/admin/notification",
}, },

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,20 +1,213 @@
import { BackButton, TextCustom, ViewWrapper } from "@/components"; import {
import { Stack } from "expo-router"; 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";
const selectedCategory = (value: string) => {
const category = listOfcategoriesAppNotification.find(
(c) => c.value === value
);
return category?.label;
};
const BoxNotification = ({
data,
activeCategory,
}: {
data: any;
activeCategory: string | null;
}) => {
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);
}}
>
<StackCustom>
<TextCustom truncate={2} bold>
{data.title}
</TextCustom>
<TextCustom truncate={2}>{data.pesan}</TextCustom>
<TextCustom size="small" color="gray">
{formatChatTime(data.createdAt)}
</TextCustom>
</StackCustom>
</BaseBox>
</>
);
};
export default function AdminNotification() { export default function AdminNotification() {
const { user } = useAuth();
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);
// tambahkan logika lain seperti filter dsb.
};
useFocusEffect(
useCallback(() => {
fecthData();
}, [activeCategory])
);
const fecthData = async () => {
try {
setLoading(true);
const response = await apiGetNotificationsById({
id: user?.id as any,
category: activeCategory as any,
});
if (response.success) {
setListData(response.data);
} else {
setListData([]);
}
} catch (error) {
console.log("Error Notification", error);
} finally {
setLoading(false);
}
};
const onRefresh = () => {
setRefreshing(true);
fecthData();
setRefreshing(false);
};
return ( return (
<> <>
<Stack.Screen <Stack.Screen
options={{ options={{
title: "Admin Notifikasi", title: "Admin Notifikasi",
headerLeft: () => <BackButton />, headerLeft: () => <BackButton />,
headerRight: () => <></>, headerRight: () => (
<IconDot
color={MainColor.yellow}
onPress={() => setOpenDrawer(true)}
/>
),
}} }}
/> />
<ViewWrapper> <NewWrapper
<TextCustom>Notification</TextCustom> headerComponent={
</ViewWrapper> <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);
},
});
}
}}
/>
</DrawerCustom>
</> </>
); );
} }

View File

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

View File

@@ -9,15 +9,21 @@ import {
} from "@/components"; } from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import GridTwoView from "@/components/_ShareComponent/GridTwoView"; import GridTwoView from "@/components/_ShareComponent/GridTwoView";
import { useAuth } from "@/hooks/use-auth";
import { routeUser } from "@/lib/routeApp";
import { import {
apiAdminUserAccessGetById, apiAdminUserAccessGetById,
apiAdminUserAccessUpdateStatus, apiAdminUserAccessUpdateStatus,
} from "@/service/api-admin/api-admin-user-access"; } from "@/service/api-admin/api-admin-user-access";
import {
apiNotificationsSendById
} from "@/service/api-notifications";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router"; import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
export default function AdminUserAccessDetail() { export default function AdminUserAccessDetail() {
const { user } = useAuth();
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [data, setData] = useState<any | null>(null); const [data, setData] = useState<any | null>(null);
const [loadData, setLoadData] = useState(false); const [loadData, setLoadData] = useState(false);
@@ -33,6 +39,7 @@ export default function AdminUserAccessDetail() {
try { try {
setLoadData(true); setLoadData(true);
const response = await apiAdminUserAccessGetById({ id: id as string }); const response = await apiAdminUserAccessGetById({ id: id as string });
console.log("[DATA]", JSON.stringify(response.data, null, 2));
setData(response.data); setData(response.data);
} catch (error) { } catch (error) {
@@ -48,6 +55,7 @@ export default function AdminUserAccessDetail() {
const response = await apiAdminUserAccessUpdateStatus({ const response = await apiAdminUserAccessUpdateStatus({
id: id as string, id: id as string,
active: !data?.active, active: !data?.active,
category: "access",
}); });
if (!response.success) { if (!response.success) {
@@ -61,6 +69,21 @@ export default function AdminUserAccessDetail() {
type: "success", type: "success",
text1: "Update aktifasi berhasil ", 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(); router.back();
} catch (error) { } catch (error) {
console.log("[ERROR UPDATE STATUS]", 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 { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
import ReportBox from "@/components/Box/ReportBox"; import ReportBox from "@/components/Box/ReportBox";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import funUpdateStatusVoting from "@/screens/Admin/Voting/funUpdateStatus"; import funUpdateStatusVoting from "@/screens/Admin/Voting/funUpdateStatus";
import { apiAdminVotingById } from "@/service/api-admin/api-admin-voting"; import { apiAdminVotingById } from "@/service/api-admin/api-admin-voting";
import { colorBadgeStatus } from "@/utils/colorBadge"; import { colorBadgeStatus } from "@/utils/colorBadge";
@@ -29,6 +30,7 @@ import { List } from "react-native-paper";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
export default function AdminVotingDetail() { export default function AdminVotingDetail() {
const { user } = useAuth();
const { id, status } = useLocalSearchParams(); const { id, status } = useLocalSearchParams();
const [data, setData] = useState<any | null>(null); const [data, setData] = useState<any | null>(null);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
@@ -139,6 +141,10 @@ export default function AdminVotingDetail() {
const response = await funUpdateStatusVoting({ const response = await funUpdateStatusVoting({
id: id as string, id: id as string,
changeStatus, changeStatus,
data: {
senderId: user?.id as string,
catatan: "",
},
}); });
if (!response.success) { if (!response.success) {

View File

@@ -7,6 +7,7 @@ import {
} from "@/components"; } from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject"; import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import { useAuth } from "@/hooks/use-auth";
import funUpdateStatusVoting from "@/screens/Admin/Voting/funUpdateStatus"; import funUpdateStatusVoting from "@/screens/Admin/Voting/funUpdateStatus";
import { apiAdminVotingById } from "@/service/api-admin/api-admin-voting"; import { apiAdminVotingById } from "@/service/api-admin/api-admin-voting";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router"; import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
@@ -14,6 +15,7 @@ import { useCallback, useState } from "react";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
export default function AdminVotingRejectInput() { export default function AdminVotingRejectInput() {
const { user } = useAuth()
const { id, status } = useLocalSearchParams(); const { id, status } = useLocalSearchParams();
const [data, setData] = useState(""); const [data, setData] = useState("");
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
@@ -48,7 +50,10 @@ export default function AdminVotingRejectInput() {
const response = await funUpdateStatusVoting({ const response = await funUpdateStatusVoting({
id: id as string, id: id as string,
changeStatus, changeStatus,
data: data, data: {
catatan: data,
senderId: user?.id as string,
},
}); });
if (!response.success) { 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() { export default function NotFoundScreen() {
return ( return (
<>
<Stack.Screen
options={{ headerShown: false, headerLeft: () => <BackButton /> }}
/>
<ViewWrapper> <ViewWrapper>
<StackCustom align="center" gap={0} style={{justifyContent: "center", alignItems: "center", flex: 1}}> <StackCustom
<TextCustom size="large" bold style={{fontSize: 100}}> align="center"
gap={0}
style={{ justifyContent: "center", alignItems: "center", flex: 1 }}
>
<TextCustom size="large" bold style={{ fontSize: 100 }}>
404 404
</TextCustom> </TextCustom>
<TextCustom size="large" bold> <TextCustom size="large" bold>
@@ -12,5 +21,6 @@ export default function NotFoundScreen() {
</TextCustom> </TextCustom>
</StackCustom> </StackCustom>
</ViewWrapper> </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 />
</>
);
}

263
bun.lock
View File

@@ -68,6 +68,7 @@
"devDependencies": { "devDependencies": {
"@babel/core": "^7.25.2", "@babel/core": "^7.25.2",
"@react-native-community/cli": "^20.0.2", "@react-native-community/cli": "^20.0.2",
"@react-native/metro-config": "^0.83.1",
"@types/react": "~19.1.10", "@types/react": "~19.1.10",
"eslint": "^9.25.0", "eslint": "^9.25.0",
"eslint-config-expo": "~10.0.0", "eslint-config-expo": "~10.0.0",
@@ -707,9 +708,9 @@
"@react-native/assets-registry": ["@react-native/assets-registry@0.81.4", "", {}, "sha512-AMcDadefBIjD10BRqkWw+W/VdvXEomR6aEZ0fhQRAv7igrBzb4PTn4vHKYg+sUK0e3wa74kcMy2DLc/HtnGcMA=="], "@react-native/assets-registry": ["@react-native/assets-registry@0.81.4", "", {}, "sha512-AMcDadefBIjD10BRqkWw+W/VdvXEomR6aEZ0fhQRAv7igrBzb4PTn4vHKYg+sUK0e3wa74kcMy2DLc/HtnGcMA=="],
"@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.79.5", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@react-native/codegen": "0.79.5" } }, "sha512-Rt/imdfqXihD/sn0xnV4flxxb1aLLjPtMF1QleQjEhJsTUPpH4TFlfOpoCvsrXoDl4OIcB1k4FVM24Ez92zf5w=="], "@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.83.1", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@react-native/codegen": "0.83.1" } }, "sha512-VPj8O3pG1ESjZho9WVKxqiuryrotAECPHGF5mx46zLUYNTWR5u9OMUXYk7LeLy+JLWdGEZ2Gn3KoXeFZbuqE+g=="],
"@react-native/babel-preset": ["@react-native/babel-preset@0.79.5", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-transform-arrow-functions": "^7.24.7", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-computed-properties": "^7.24.7", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", "@babel/plugin-transform-function-name": "^7.25.1", "@babel/plugin-transform-literals": "^7.25.2", "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-numeric-separator": "^7.24.7", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", "@babel/plugin-transform-react-jsx": "^7.25.2", "@babel/plugin-transform-react-jsx-self": "^7.24.7", "@babel/plugin-transform-react-jsx-source": "^7.24.7", "@babel/plugin-transform-regenerator": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-shorthand-properties": "^7.24.7", "@babel/plugin-transform-spread": "^7.24.7", "@babel/plugin-transform-sticky-regex": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/template": "^7.25.0", "@react-native/babel-plugin-codegen": "0.79.5", "babel-plugin-syntax-hermes-parser": "0.25.1", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" } }, "sha512-GDUYIWslMLbdJHEgKNfrOzXk8EDKxKzbwmBXUugoiSlr6TyepVZsj3GZDLEFarOcTwH1EXXHJsixihk8DCRQDA=="], "@react-native/babel-preset": ["@react-native/babel-preset@0.83.1", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-transform-arrow-functions": "^7.24.7", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-computed-properties": "^7.24.7", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", "@babel/plugin-transform-function-name": "^7.25.1", "@babel/plugin-transform-literals": "^7.25.2", "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-numeric-separator": "^7.24.7", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", "@babel/plugin-transform-react-jsx": "^7.25.2", "@babel/plugin-transform-react-jsx-self": "^7.24.7", "@babel/plugin-transform-react-jsx-source": "^7.24.7", "@babel/plugin-transform-regenerator": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-shorthand-properties": "^7.24.7", "@babel/plugin-transform-spread": "^7.24.7", "@babel/plugin-transform-sticky-regex": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/template": "^7.25.0", "@react-native/babel-plugin-codegen": "0.83.1", "babel-plugin-syntax-hermes-parser": "0.32.0", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" } }, "sha512-xI+tbsD4fXcI6PVU4sauRCh0a5fuLQC849SINmU2J5wP8kzKu4Ye0YkGjUW3mfGrjaZcjkWmF6s33jpyd3gdTw=="],
"@react-native/codegen": ["@react-native/codegen@0.81.4", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.25.3", "glob": "^7.1.1", "hermes-parser": "0.29.1", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "yargs": "^17.6.2" } }, "sha512-LWTGUTzFu+qOQnvkzBP52B90Ym3stZT8IFCzzUrppz8Iwglg83FCtDZAR4yLHI29VY/x/+pkcWAMCl3739XHdw=="], "@react-native/codegen": ["@react-native/codegen@0.81.4", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.25.3", "glob": "^7.1.1", "hermes-parser": "0.29.1", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "yargs": "^17.6.2" } }, "sha512-LWTGUTzFu+qOQnvkzBP52B90Ym3stZT8IFCzzUrppz8Iwglg83FCtDZAR4yLHI29VY/x/+pkcWAMCl3739XHdw=="],
@@ -721,7 +722,11 @@
"@react-native/gradle-plugin": ["@react-native/gradle-plugin@0.81.4", "", {}, "sha512-T7fPcQvDDCSusZFVSg6H1oVDKb/NnVYLnsqkcHsAF2C2KGXyo3J7slH/tJAwNfj/7EOA2OgcWxfC1frgn9TQvw=="], "@react-native/gradle-plugin": ["@react-native/gradle-plugin@0.81.4", "", {}, "sha512-T7fPcQvDDCSusZFVSg6H1oVDKb/NnVYLnsqkcHsAF2C2KGXyo3J7slH/tJAwNfj/7EOA2OgcWxfC1frgn9TQvw=="],
"@react-native/js-polyfills": ["@react-native/js-polyfills@0.81.4", "", {}, "sha512-sr42FaypKXJHMVHhgSbu2f/ZJfrLzgaoQ+HdpRvKEiEh2mhFf6XzZwecyLBvWqf2pMPZa+CpPfNPiejXjKEy8w=="], "@react-native/js-polyfills": ["@react-native/js-polyfills@0.83.1", "", {}, "sha512-qgPpdWn/c5laA+3WoJ6Fak8uOm7CG50nBsLlPsF8kbT7rUHIVB9WaP6+GPsoKV/H15koW7jKuLRoNVT7c3Ht3w=="],
"@react-native/metro-babel-transformer": ["@react-native/metro-babel-transformer@0.83.1", "", { "dependencies": { "@babel/core": "^7.25.2", "@react-native/babel-preset": "0.83.1", "hermes-parser": "0.32.0", "nullthrows": "^1.1.1" } }, "sha512-fqt6DHWX1GBGDKa5WJOjDtPPy2M9lkYVLn59fBeFQ0GXhBRzNbUh8JzWWI/Q2CLDZ2tgKCcwaiXJ1OHWVd2BCQ=="],
"@react-native/metro-config": ["@react-native/metro-config@0.83.1", "", { "dependencies": { "@react-native/js-polyfills": "0.83.1", "@react-native/metro-babel-transformer": "0.83.1", "metro-config": "^0.83.3", "metro-runtime": "^0.83.3" } }, "sha512-1rjYZf62fCm6QAinHmRAKnJxIypX0VF/zBPd0qWvWABMZugrS0eACuIbk9Wk0StBod4yL8KnwEJyg77ak8xYzQ=="],
"@react-native/normalize-colors": ["@react-native/normalize-colors@0.81.4", "", {}, "sha512-9nRRHO1H+tcFqjb9gAM105Urtgcanbta2tuqCVY0NATHeFPDEAB7gPyiLxCHKMi1NbhP6TH0kxgSWXKZl1cyRg=="], "@react-native/normalize-colors": ["@react-native/normalize-colors@0.81.4", "", {}, "sha512-9nRRHO1H+tcFqjb9gAM105Urtgcanbta2tuqCVY0NATHeFPDEAB7gPyiLxCHKMi1NbhP6TH0kxgSWXKZl1cyRg=="],
@@ -1585,9 +1590,9 @@
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
"hermes-estree": ["hermes-estree@0.29.1", "", {}, "sha512-jl+x31n4/w+wEqm0I2r4CMimukLbLQEYpisys5oCre611CI5fc9TxhqkBBCJ1edDG4Kza0f7CgNz8xVMLZQOmQ=="], "hermes-estree": ["hermes-estree@0.32.0", "", {}, "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ=="],
"hermes-parser": ["hermes-parser@0.29.1", "", { "dependencies": { "hermes-estree": "0.29.1" } }, "sha512-xBHWmUtRC5e/UL0tI7Ivt2riA/YBq9+SiYFU7C1oBa/j2jYGlIF9043oak1F47ihuDIxQ5nbsKueYJDRY02UgA=="], "hermes-parser": ["hermes-parser@0.32.0", "", { "dependencies": { "hermes-estree": "0.32.0" } }, "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw=="],
"hey-listen": ["hey-listen@1.0.8", "", {}, "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q=="], "hey-listen": ["hey-listen@1.0.8", "", {}, "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q=="],
@@ -1917,17 +1922,17 @@
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
"metro": ["metro@0.83.1", "", { "dependencies": { "@babel/code-frame": "^7.24.7", "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/parser": "^7.25.3", "@babel/template": "^7.25.0", "@babel/traverse": "^7.25.3", "@babel/types": "^7.25.2", "accepts": "^1.3.7", "chalk": "^4.0.0", "ci-info": "^2.0.0", "connect": "^3.6.5", "debug": "^4.4.0", "error-stack-parser": "^2.0.6", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "hermes-parser": "0.29.1", "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", "metro-babel-transformer": "0.83.1", "metro-cache": "0.83.1", "metro-cache-key": "0.83.1", "metro-config": "0.83.1", "metro-core": "0.83.1", "metro-file-map": "0.83.1", "metro-resolver": "0.83.1", "metro-runtime": "0.83.1", "metro-source-map": "0.83.1", "metro-symbolicate": "0.83.1", "metro-transform-plugins": "0.83.1", "metro-transform-worker": "0.83.1", "mime-types": "^2.1.27", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", "source-map": "^0.5.6", "throat": "^5.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": { "metro": "src/cli.js" } }, "sha512-UGKepmTxoGD4HkQV8YWvpvwef7fUujNtTgG4Ygf7m/M0qjvb9VuDmAsEU+UdriRX7F61pnVK/opz89hjKlYTXA=="], "metro": ["metro@0.83.3", "", { "dependencies": { "@babel/code-frame": "^7.24.7", "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/parser": "^7.25.3", "@babel/template": "^7.25.0", "@babel/traverse": "^7.25.3", "@babel/types": "^7.25.2", "accepts": "^1.3.7", "chalk": "^4.0.0", "ci-info": "^2.0.0", "connect": "^3.6.5", "debug": "^4.4.0", "error-stack-parser": "^2.0.6", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "hermes-parser": "0.32.0", "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", "metro-babel-transformer": "0.83.3", "metro-cache": "0.83.3", "metro-cache-key": "0.83.3", "metro-config": "0.83.3", "metro-core": "0.83.3", "metro-file-map": "0.83.3", "metro-resolver": "0.83.3", "metro-runtime": "0.83.3", "metro-source-map": "0.83.3", "metro-symbolicate": "0.83.3", "metro-transform-plugins": "0.83.3", "metro-transform-worker": "0.83.3", "mime-types": "^2.1.27", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", "source-map": "^0.5.6", "throat": "^5.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": { "metro": "src/cli.js" } }, "sha512-+rP+/GieOzkt97hSJ0MrPOuAH/jpaS21ZDvL9DJ35QYRDlQcwzcvUlGUf79AnQxq/2NPiS/AULhhM4TKutIt8Q=="],
"metro-babel-transformer": ["metro-babel-transformer@0.83.1", "", { "dependencies": { "@babel/core": "^7.25.2", "flow-enums-runtime": "^0.0.6", "hermes-parser": "0.29.1", "nullthrows": "^1.1.1" } }, "sha512-r3xAD3964E8dwDBaZNSO2aIIvWXjIK80uO2xo0/pi3WI8XWT9h5SCjtGWtMtE5PRWw+t20TN0q1WMRsjvhC1rQ=="], "metro-babel-transformer": ["metro-babel-transformer@0.83.1", "", { "dependencies": { "@babel/core": "^7.25.2", "flow-enums-runtime": "^0.0.6", "hermes-parser": "0.29.1", "nullthrows": "^1.1.1" } }, "sha512-r3xAD3964E8dwDBaZNSO2aIIvWXjIK80uO2xo0/pi3WI8XWT9h5SCjtGWtMtE5PRWw+t20TN0q1WMRsjvhC1rQ=="],
"metro-cache": ["metro-cache@0.83.1", "", { "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", "https-proxy-agent": "^7.0.5", "metro-core": "0.83.1" } }, "sha512-7N/Ad1PHa1YMWDNiyynTPq34Op2qIE68NWryGEQ4TSE3Zy6a8GpsYnEEZE4Qi6aHgsE+yZHKkRczeBgxhnFIxQ=="], "metro-cache": ["metro-cache@0.83.3", "", { "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", "https-proxy-agent": "^7.0.5", "metro-core": "0.83.3" } }, "sha512-3jo65X515mQJvKqK3vWRblxDEcgY55Sk3w4xa6LlfEXgQ9g1WgMh9m4qVZVwgcHoLy0a2HENTPCCX4Pk6s8c8Q=="],
"metro-cache-key": ["metro-cache-key@0.83.1", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-ZUs+GD5CNeDLxx5UUWmfg26IL+Dnbryd+TLqTlZnDEgehkIa11kUSvgF92OFfJhONeXzV4rZDRGNXoo6JT+8Gg=="], "metro-cache-key": ["metro-cache-key@0.83.1", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-ZUs+GD5CNeDLxx5UUWmfg26IL+Dnbryd+TLqTlZnDEgehkIa11kUSvgF92OFfJhONeXzV4rZDRGNXoo6JT+8Gg=="],
"metro-config": ["metro-config@0.83.1", "", { "dependencies": { "connect": "^3.6.5", "cosmiconfig": "^5.0.5", "flow-enums-runtime": "^0.0.6", "jest-validate": "^29.7.0", "metro": "0.83.1", "metro-cache": "0.83.1", "metro-core": "0.83.1", "metro-runtime": "0.83.1" } }, "sha512-HJhpZx3wyOkux/jeF1o7akFJzZFdbn6Zf7UQqWrvp7gqFqNulQ8Mju09raBgPmmSxKDl4LbbNeigkX0/nKY1QA=="], "metro-config": ["metro-config@0.83.3", "", { "dependencies": { "connect": "^3.6.5", "flow-enums-runtime": "^0.0.6", "jest-validate": "^29.7.0", "metro": "0.83.3", "metro-cache": "0.83.3", "metro-core": "0.83.3", "metro-runtime": "0.83.3", "yaml": "^2.6.1" } }, "sha512-mTel7ipT0yNjKILIan04bkJkuCzUUkm2SeEaTads8VfEecCh+ltXchdq6DovXJqzQAXuR2P9cxZB47Lg4klriA=="],
"metro-core": ["metro-core@0.83.1", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", "metro-resolver": "0.83.1" } }, "sha512-uVL1eAJcMFd2o2Q7dsbpg8COaxjZBBGaXqO2OHnivpCdfanraVL8dPmY6It9ZeqWLOihUKZ2yHW4b6soVCzH/Q=="], "metro-core": ["metro-core@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", "metro-resolver": "0.83.3" } }, "sha512-M+X59lm7oBmJZamc96usuF1kusd5YimqG/q97g4Ac7slnJ3YiGglW5CsOlicTR5EWf8MQFxxjDoB6ytTqRe8Hw=="],
"metro-file-map": ["metro-file-map@0.83.1", "", { "dependencies": { "debug": "^4.4.0", "fb-watchman": "^2.0.0", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "nullthrows": "^1.1.1", "walker": "^1.0.7" } }, "sha512-Yu429lnexKl44PttKw3nhqgmpBR+6UQ/tRaYcxPeEShtcza9DWakCn7cjqDTQZtWR2A8xSNv139izJMyQ4CG+w=="], "metro-file-map": ["metro-file-map@0.83.1", "", { "dependencies": { "debug": "^4.4.0", "fb-watchman": "^2.0.0", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "nullthrows": "^1.1.1", "walker": "^1.0.7" } }, "sha512-Yu429lnexKl44PttKw3nhqgmpBR+6UQ/tRaYcxPeEShtcza9DWakCn7cjqDTQZtWR2A8xSNv139izJMyQ4CG+w=="],
@@ -1935,7 +1940,7 @@
"metro-resolver": ["metro-resolver@0.83.1", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-t8j46kiILAqqFS5RNa+xpQyVjULxRxlvMidqUswPEk5nQVNdlJslqizDm/Et3v/JKwOtQGkYAQCHxP1zGStR/g=="], "metro-resolver": ["metro-resolver@0.83.1", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-t8j46kiILAqqFS5RNa+xpQyVjULxRxlvMidqUswPEk5nQVNdlJslqizDm/Et3v/JKwOtQGkYAQCHxP1zGStR/g=="],
"metro-runtime": ["metro-runtime@0.83.1", "", { "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" } }, "sha512-3Ag8ZS4IwafL/JUKlaeM6/CbkooY+WcVeqdNlBG0m4S0Qz0om3rdFdy1y6fYBpl6AwXJwWeMuXrvZdMuByTcRA=="], "metro-runtime": ["metro-runtime@0.83.3", "", { "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" } }, "sha512-JHCJb9ebr9rfJ+LcssFYA2x1qPYuSD/bbePupIGhpMrsla7RCwC/VL3yJ9cSU+nUhU4c9Ixxy8tBta+JbDeZWw=="],
"metro-source-map": ["metro-source-map@0.83.1", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3", "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-symbolicate": "0.83.1", "nullthrows": "^1.1.1", "ob1": "0.83.1", "source-map": "^0.5.6", "vlq": "^1.0.0" } }, "sha512-De7Vbeo96fFZ2cqmI0fWwVJbtHIwPZv++LYlWSwzTiCzxBDJORncN0LcT48Vi2UlQLzXJg+/CuTAcy7NBVh69A=="], "metro-source-map": ["metro-source-map@0.83.1", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3", "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-symbolicate": "0.83.1", "nullthrows": "^1.1.1", "ob1": "0.83.1", "source-map": "^0.5.6", "vlq": "^1.0.0" } }, "sha512-De7Vbeo96fFZ2cqmI0fWwVJbtHIwPZv++LYlWSwzTiCzxBDJORncN0LcT48Vi2UlQLzXJg+/CuTAcy7NBVh69A=="],
@@ -2717,10 +2722,22 @@
"@expo/json-file/@babel/code-frame": ["@babel/code-frame@7.10.4", "", { "dependencies": { "@babel/highlight": "^7.10.4" } }, "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg=="], "@expo/json-file/@babel/code-frame": ["@babel/code-frame@7.10.4", "", { "dependencies": { "@babel/highlight": "^7.10.4" } }, "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg=="],
"@expo/metro/metro": ["metro@0.83.1", "", { "dependencies": { "@babel/code-frame": "^7.24.7", "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/parser": "^7.25.3", "@babel/template": "^7.25.0", "@babel/traverse": "^7.25.3", "@babel/types": "^7.25.2", "accepts": "^1.3.7", "chalk": "^4.0.0", "ci-info": "^2.0.0", "connect": "^3.6.5", "debug": "^4.4.0", "error-stack-parser": "^2.0.6", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "hermes-parser": "0.29.1", "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", "metro-babel-transformer": "0.83.1", "metro-cache": "0.83.1", "metro-cache-key": "0.83.1", "metro-config": "0.83.1", "metro-core": "0.83.1", "metro-file-map": "0.83.1", "metro-resolver": "0.83.1", "metro-runtime": "0.83.1", "metro-source-map": "0.83.1", "metro-symbolicate": "0.83.1", "metro-transform-plugins": "0.83.1", "metro-transform-worker": "0.83.1", "mime-types": "^2.1.27", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", "source-map": "^0.5.6", "throat": "^5.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": { "metro": "src/cli.js" } }, "sha512-UGKepmTxoGD4HkQV8YWvpvwef7fUujNtTgG4Ygf7m/M0qjvb9VuDmAsEU+UdriRX7F61pnVK/opz89hjKlYTXA=="],
"@expo/metro/metro-cache": ["metro-cache@0.83.1", "", { "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", "https-proxy-agent": "^7.0.5", "metro-core": "0.83.1" } }, "sha512-7N/Ad1PHa1YMWDNiyynTPq34Op2qIE68NWryGEQ4TSE3Zy6a8GpsYnEEZE4Qi6aHgsE+yZHKkRczeBgxhnFIxQ=="],
"@expo/metro/metro-config": ["metro-config@0.83.1", "", { "dependencies": { "connect": "^3.6.5", "cosmiconfig": "^5.0.5", "flow-enums-runtime": "^0.0.6", "jest-validate": "^29.7.0", "metro": "0.83.1", "metro-cache": "0.83.1", "metro-core": "0.83.1", "metro-runtime": "0.83.1" } }, "sha512-HJhpZx3wyOkux/jeF1o7akFJzZFdbn6Zf7UQqWrvp7gqFqNulQ8Mju09raBgPmmSxKDl4LbbNeigkX0/nKY1QA=="],
"@expo/metro/metro-core": ["metro-core@0.83.1", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", "metro-resolver": "0.83.1" } }, "sha512-uVL1eAJcMFd2o2Q7dsbpg8COaxjZBBGaXqO2OHnivpCdfanraVL8dPmY6It9ZeqWLOihUKZ2yHW4b6soVCzH/Q=="],
"@expo/metro/metro-runtime": ["metro-runtime@0.83.1", "", { "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" } }, "sha512-3Ag8ZS4IwafL/JUKlaeM6/CbkooY+WcVeqdNlBG0m4S0Qz0om3rdFdy1y6fYBpl6AwXJwWeMuXrvZdMuByTcRA=="],
"@expo/metro-config/@babel/generator": ["@babel/generator@7.28.3", "", { "dependencies": { "@babel/parser": "^7.28.3", "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw=="], "@expo/metro-config/@babel/generator": ["@babel/generator@7.28.3", "", { "dependencies": { "@babel/parser": "^7.28.3", "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw=="],
"@expo/metro-config/@expo/json-file": ["@expo/json-file@10.0.7", "", { "dependencies": { "@babel/code-frame": "~7.10.4", "json5": "^2.2.3" } }, "sha512-z2OTC0XNO6riZu98EjdNHC05l51ySeTto6GP7oSQrCvQgG9ARBwD1YvMQaVZ9wU7p/4LzSf1O7tckL3B45fPpw=="], "@expo/metro-config/@expo/json-file": ["@expo/json-file@10.0.7", "", { "dependencies": { "@babel/code-frame": "~7.10.4", "json5": "^2.2.3" } }, "sha512-z2OTC0XNO6riZu98EjdNHC05l51ySeTto6GP7oSQrCvQgG9ARBwD1YvMQaVZ9wU7p/4LzSf1O7tckL3B45fPpw=="],
"@expo/metro-config/hermes-parser": ["hermes-parser@0.29.1", "", { "dependencies": { "hermes-estree": "0.29.1" } }, "sha512-xBHWmUtRC5e/UL0tI7Ivt2riA/YBq9+SiYFU7C1oBa/j2jYGlIF9043oak1F47ihuDIxQ5nbsKueYJDRY02UgA=="],
"@expo/metro-config/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], "@expo/metro-config/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"@expo/npm-proofread/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], "@expo/npm-proofread/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="],
@@ -2785,28 +2802,24 @@
"@react-native-community/cli-tools/semver": ["semver@7.7.2", "", { "bin": "bin/semver.js" }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], "@react-native-community/cli-tools/semver": ["semver@7.7.2", "", { "bin": "bin/semver.js" }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"@react-native/babel-plugin-codegen/@react-native/codegen": ["@react-native/codegen@0.79.5", "", { "dependencies": { "glob": "^7.1.1", "hermes-parser": "0.25.1", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "yargs": "^17.6.2" }, "peerDependencies": { "@babel/core": "*" } }, "sha512-FO5U1R525A1IFpJjy+KVznEinAgcs3u7IbnbRJUG9IH/MBXi2lEU2LtN+JarJ81MCfW4V2p0pg6t/3RGHFRrlQ=="], "@react-native/babel-plugin-codegen/@babel/traverse": ["@babel/traverse@7.28.3", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", "@babel/types": "^7.28.2", "debug": "^4.3.1" } }, "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ=="],
"@react-native/babel-preset/@babel/plugin-transform-async-generator-functions": ["@babel/plugin-transform-async-generator-functions@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-remap-async-to-generator": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-eST9RrwlpaoJBDHShc+DS2SG4ATTi2MYNb4OxYkf3n+7eb49LWpnS+HSpVfW4x927qQwgk8A2hGNVaajAEw0EA=="], "@react-native/babel-plugin-codegen/@react-native/codegen": ["@react-native/codegen@0.83.1", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.25.3", "glob": "^7.1.1", "hermes-parser": "0.32.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "yargs": "^17.6.2" } }, "sha512-FpRxenonwH+c2a5X5DZMKUD7sCudHxB3eSQPgV9R+uxd28QWslyAWrpnJM/Az96AEksHnymDzEmzq2HLX5nb+g=="],
"@react-native/babel-preset/@babel/plugin-transform-block-scoping": ["@babel/plugin-transform-block-scoping@7.27.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-JF6uE2s67f0y2RZcm2kpAUEbD50vH62TyWVebxwHAlbSdM49VqPz8t4a1uIjp4NIOIZ4xzLfjY5emt/RCyC7TQ=="], "@react-native/babel-preset/babel-plugin-syntax-hermes-parser": ["babel-plugin-syntax-hermes-parser@0.32.0", "", { "dependencies": { "hermes-parser": "0.32.0" } }, "sha512-m5HthL++AbyeEA2FcdwOLfVFvWYECOBObLHNqdR8ceY4TsEdn4LdX2oTvbB2QJSSElE2AWA/b2MXZ/PF/CqLZg=="],
"@react-native/babel-preset/@babel/plugin-transform-classes": ["@babel/plugin-transform-classes@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-compilation-targets": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/traverse": "^7.27.1", "globals": "^11.1.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-7iLhfFAubmpeJe/Wo2TVuDrykh/zlWXLzPNdL0Jqn/Xu8R3QQ8h9ff8FQoISZOsw74/HFqFI7NX63HN7QFIHKA=="],
"@react-native/babel-preset/@babel/plugin-transform-destructuring": ["@babel/plugin-transform-destructuring@7.27.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-s4Jrok82JpiaIprtY2nHsYmrThKvvwgHwjgd7UMiYhZaN0asdXNLr0y+NjTfkA7SyQE5i2Fb7eawUOZmLvyqOA=="],
"@react-native/babel-preset/@babel/plugin-transform-object-rest-spread": ["@babel/plugin-transform-object-rest-spread@7.27.3", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "@babel/plugin-transform-destructuring": "^7.27.3", "@babel/plugin-transform-parameters": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-7ZZtznF9g4l2JCImCo5LNKFHB5eXnN39lLtLY5Tg+VkR0jwOt7TBciMckuiQIOIW7L5tkQOCh3bVGYeXgMx52Q=="],
"@react-native/babel-preset/@babel/plugin-transform-parameters": ["@babel/plugin-transform-parameters@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-018KRk76HWKeZ5l4oTj2zPpSh+NbGdt0st5S6x0pga6HgrjBOJb24mMDHorFopOOd6YHkLgOZ+zaCjZGPO4aKg=="],
"@react-native/babel-preset/@babel/plugin-transform-regenerator": ["@babel/plugin-transform-regenerator@7.27.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-uhB8yHerfe3MWnuLAhEbeQ4afVoqv8BQsPqrTv7e/jZ9y00kJL6l9a/f4OWaKxotmjzewfEyXE1vgDJenkQ2/Q=="],
"@react-native/babel-preset/babel-plugin-syntax-hermes-parser": ["babel-plugin-syntax-hermes-parser@0.25.1", "", { "dependencies": { "hermes-parser": "0.25.1" } }, "sha512-IVNpGzboFLfXZUAwkLFcI/bnqVbwky0jP3eBno4HKtqvQJAHBLdgxiG6lQ4to0+Q/YCN3PO0od5NZwIKyY4REQ=="],
"@react-native/codegen/@babel/parser": ["@babel/parser@7.28.3", "", { "dependencies": { "@babel/types": "^7.28.2" }, "bin": "./bin/babel-parser.js" }, "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA=="], "@react-native/codegen/@babel/parser": ["@babel/parser@7.28.3", "", { "dependencies": { "@babel/types": "^7.28.2" }, "bin": "./bin/babel-parser.js" }, "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA=="],
"@react-native/codegen/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], "@react-native/codegen/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
"@react-native/codegen/hermes-parser": ["hermes-parser@0.29.1", "", { "dependencies": { "hermes-estree": "0.29.1" } }, "sha512-xBHWmUtRC5e/UL0tI7Ivt2riA/YBq9+SiYFU7C1oBa/j2jYGlIF9043oak1F47ihuDIxQ5nbsKueYJDRY02UgA=="],
"@react-native/community-cli-plugin/metro": ["metro@0.83.1", "", { "dependencies": { "@babel/code-frame": "^7.24.7", "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/parser": "^7.25.3", "@babel/template": "^7.25.0", "@babel/traverse": "^7.25.3", "@babel/types": "^7.25.2", "accepts": "^1.3.7", "chalk": "^4.0.0", "ci-info": "^2.0.0", "connect": "^3.6.5", "debug": "^4.4.0", "error-stack-parser": "^2.0.6", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "hermes-parser": "0.29.1", "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", "metro-babel-transformer": "0.83.1", "metro-cache": "0.83.1", "metro-cache-key": "0.83.1", "metro-config": "0.83.1", "metro-core": "0.83.1", "metro-file-map": "0.83.1", "metro-resolver": "0.83.1", "metro-runtime": "0.83.1", "metro-source-map": "0.83.1", "metro-symbolicate": "0.83.1", "metro-transform-plugins": "0.83.1", "metro-transform-worker": "0.83.1", "mime-types": "^2.1.27", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", "source-map": "^0.5.6", "throat": "^5.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": { "metro": "src/cli.js" } }, "sha512-UGKepmTxoGD4HkQV8YWvpvwef7fUujNtTgG4Ygf7m/M0qjvb9VuDmAsEU+UdriRX7F61pnVK/opz89hjKlYTXA=="],
"@react-native/community-cli-plugin/metro-config": ["metro-config@0.83.1", "", { "dependencies": { "connect": "^3.6.5", "cosmiconfig": "^5.0.5", "flow-enums-runtime": "^0.0.6", "jest-validate": "^29.7.0", "metro": "0.83.1", "metro-cache": "0.83.1", "metro-core": "0.83.1", "metro-runtime": "0.83.1" } }, "sha512-HJhpZx3wyOkux/jeF1o7akFJzZFdbn6Zf7UQqWrvp7gqFqNulQ8Mju09raBgPmmSxKDl4LbbNeigkX0/nKY1QA=="],
"@react-native/community-cli-plugin/metro-core": ["metro-core@0.83.1", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", "metro-resolver": "0.83.1" } }, "sha512-uVL1eAJcMFd2o2Q7dsbpg8COaxjZBBGaXqO2OHnivpCdfanraVL8dPmY6It9ZeqWLOihUKZ2yHW4b6soVCzH/Q=="],
"@react-native/community-cli-plugin/semver": ["semver@7.7.2", "", { "bin": "bin/semver.js" }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], "@react-native/community-cli-plugin/semver": ["semver@7.7.2", "", { "bin": "bin/semver.js" }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"@react-native/dev-middleware/open": ["open@7.4.2", "", { "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" } }, "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q=="], "@react-native/dev-middleware/open": ["open@7.4.2", "", { "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" } }, "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q=="],
@@ -2849,10 +2862,14 @@
"babel-plugin-react-compiler/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="], "babel-plugin-react-compiler/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
"babel-plugin-syntax-hermes-parser/hermes-parser": ["hermes-parser@0.29.1", "", { "dependencies": { "hermes-estree": "0.29.1" } }, "sha512-xBHWmUtRC5e/UL0tI7Ivt2riA/YBq9+SiYFU7C1oBa/j2jYGlIF9043oak1F47ihuDIxQ5nbsKueYJDRY02UgA=="],
"babel-preset-expo/@babel/plugin-transform-object-rest-spread": ["@babel/plugin-transform-object-rest-spread@7.27.3", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "@babel/plugin-transform-destructuring": "^7.27.3", "@babel/plugin-transform-parameters": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-7ZZtznF9g4l2JCImCo5LNKFHB5eXnN39lLtLY5Tg+VkR0jwOt7TBciMckuiQIOIW7L5tkQOCh3bVGYeXgMx52Q=="], "babel-preset-expo/@babel/plugin-transform-object-rest-spread": ["@babel/plugin-transform-object-rest-spread@7.27.3", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "@babel/plugin-transform-destructuring": "^7.27.3", "@babel/plugin-transform-parameters": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-7ZZtznF9g4l2JCImCo5LNKFHB5eXnN39lLtLY5Tg+VkR0jwOt7TBciMckuiQIOIW7L5tkQOCh3bVGYeXgMx52Q=="],
"babel-preset-expo/@babel/plugin-transform-parameters": ["@babel/plugin-transform-parameters@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-018KRk76HWKeZ5l4oTj2zPpSh+NbGdt0st5S6x0pga6HgrjBOJb24mMDHorFopOOd6YHkLgOZ+zaCjZGPO4aKg=="], "babel-preset-expo/@babel/plugin-transform-parameters": ["@babel/plugin-transform-parameters@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-018KRk76HWKeZ5l4oTj2zPpSh+NbGdt0st5S6x0pga6HgrjBOJb24mMDHorFopOOd6YHkLgOZ+zaCjZGPO4aKg=="],
"babel-preset-expo/@react-native/babel-preset": ["@react-native/babel-preset@0.79.5", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-transform-arrow-functions": "^7.24.7", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-computed-properties": "^7.24.7", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", "@babel/plugin-transform-function-name": "^7.25.1", "@babel/plugin-transform-literals": "^7.25.2", "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-numeric-separator": "^7.24.7", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", "@babel/plugin-transform-react-jsx": "^7.25.2", "@babel/plugin-transform-react-jsx-self": "^7.24.7", "@babel/plugin-transform-react-jsx-source": "^7.24.7", "@babel/plugin-transform-regenerator": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-shorthand-properties": "^7.24.7", "@babel/plugin-transform-spread": "^7.24.7", "@babel/plugin-transform-sticky-regex": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/template": "^7.25.0", "@react-native/babel-plugin-codegen": "0.79.5", "babel-plugin-syntax-hermes-parser": "0.25.1", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" } }, "sha512-GDUYIWslMLbdJHEgKNfrOzXk8EDKxKzbwmBXUugoiSlr6TyepVZsj3GZDLEFarOcTwH1EXXHJsixihk8DCRQDA=="],
"babel-preset-expo/babel-plugin-syntax-hermes-parser": ["babel-plugin-syntax-hermes-parser@0.25.1", "", { "dependencies": { "hermes-parser": "0.25.1" } }, "sha512-IVNpGzboFLfXZUAwkLFcI/bnqVbwky0jP3eBno4HKtqvQJAHBLdgxiG6lQ4to0+Q/YCN3PO0od5NZwIKyY4REQ=="], "babel-preset-expo/babel-plugin-syntax-hermes-parser": ["babel-plugin-syntax-hermes-parser@0.25.1", "", { "dependencies": { "hermes-parser": "0.25.1" } }, "sha512-IVNpGzboFLfXZUAwkLFcI/bnqVbwky0jP3eBno4HKtqvQJAHBLdgxiG6lQ4to0+Q/YCN3PO0od5NZwIKyY4REQ=="],
"better-opn/open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="], "better-opn/open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="],
@@ -3039,9 +3056,27 @@
"metro/ci-info": ["ci-info@2.0.0", "", {}, "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ=="], "metro/ci-info": ["ci-info@2.0.0", "", {}, "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ=="],
"metro/metro-babel-transformer": ["metro-babel-transformer@0.83.3", "", { "dependencies": { "@babel/core": "^7.25.2", "flow-enums-runtime": "^0.0.6", "hermes-parser": "0.32.0", "nullthrows": "^1.1.1" } }, "sha512-1vxlvj2yY24ES1O5RsSIvg4a4WeL7PFXgKOHvXTXiW0deLvQr28ExXj6LjwCCDZ4YZLhq6HddLpZnX4dEdSq5g=="],
"metro/metro-cache-key": ["metro-cache-key@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-59ZO049jKzSmvBmG/B5bZ6/dztP0ilp0o988nc6dpaDsU05Cl1c/lRf+yx8m9WW/JVgbmfO5MziBU559XjI5Zw=="],
"metro/metro-file-map": ["metro-file-map@0.83.3", "", { "dependencies": { "debug": "^4.4.0", "fb-watchman": "^2.0.0", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "nullthrows": "^1.1.1", "walker": "^1.0.7" } }, "sha512-jg5AcyE0Q9Xbbu/4NAwwZkmQn7doJCKGW0SLeSJmzNB9Z24jBe0AL2PHNMy4eu0JiKtNWHz9IiONGZWq7hjVTA=="],
"metro/metro-resolver": ["metro-resolver@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-0js+zwI5flFxb1ktmR///bxHYg7OLpRpWZlBBruYG8OKYxeMP7SV0xQ/o/hUelrEMdK4LJzqVtHAhBm25LVfAQ=="],
"metro/metro-source-map": ["metro-source-map@0.83.3", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3", "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-symbolicate": "0.83.3", "nullthrows": "^1.1.1", "ob1": "0.83.3", "source-map": "^0.5.6", "vlq": "^1.0.0" } }, "sha512-xkC3qwUBh2psVZgVavo8+r2C9Igkk3DibiOXSAht1aYRRcztEZNFtAMtfSB7sdO2iFMx2Mlyu++cBxz/fhdzQg=="],
"metro/metro-symbolicate": ["metro-symbolicate@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-source-map": "0.83.3", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "vlq": "^1.0.0" }, "bin": { "metro-symbolicate": "src/index.js" } }, "sha512-F/YChgKd6KbFK3eUR5HdUsfBqVsanf5lNTwFd4Ca7uuxnHgBC3kR/Hba/RGkenR3pZaGNp5Bu9ZqqP52Wyhomw=="],
"metro/metro-transform-plugins": ["metro-transform-plugins@0.83.3", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/template": "^7.25.0", "@babel/traverse": "^7.25.3", "flow-enums-runtime": "^0.0.6", "nullthrows": "^1.1.1" } }, "sha512-eRGoKJU6jmqOakBMH5kUB7VitEWiNrDzBHpYbkBXW7C5fUGeOd2CyqrosEzbMK5VMiZYyOcNFEphvxk3OXey2A=="],
"metro/metro-transform-worker": ["metro-transform-worker@0.83.3", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/parser": "^7.25.3", "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", "metro": "0.83.3", "metro-babel-transformer": "0.83.3", "metro-cache": "0.83.3", "metro-cache-key": "0.83.3", "metro-minify-terser": "0.83.3", "metro-source-map": "0.83.3", "metro-transform-plugins": "0.83.3", "nullthrows": "^1.1.1" } }, "sha512-Ztekew9t/gOIMZX1tvJOgX7KlSLL5kWykl0Iwu2cL2vKMKVALRl1hysyhUw0vjpAvLFx+Kfq9VLjnHIkW32fPA=="],
"metro/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="], "metro/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="],
"metro-config/cosmiconfig": ["cosmiconfig@5.2.1", "", { "dependencies": { "import-fresh": "^2.0.0", "is-directory": "^0.3.1", "js-yaml": "^3.13.1", "parse-json": "^4.0.0" } }, "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA=="], "metro-babel-transformer/hermes-parser": ["hermes-parser@0.29.1", "", { "dependencies": { "hermes-estree": "0.29.1" } }, "sha512-xBHWmUtRC5e/UL0tI7Ivt2riA/YBq9+SiYFU7C1oBa/j2jYGlIF9043oak1F47ihuDIxQ5nbsKueYJDRY02UgA=="],
"metro-core/metro-resolver": ["metro-resolver@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-0js+zwI5flFxb1ktmR///bxHYg7OLpRpWZlBBruYG8OKYxeMP7SV0xQ/o/hUelrEMdK4LJzqVtHAhBm25LVfAQ=="],
"metro-source-map/@babel/traverse": ["@babel/traverse@7.28.3", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", "@babel/types": "^7.28.2", "debug": "^4.3.1" } }, "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ=="], "metro-source-map/@babel/traverse": ["@babel/traverse@7.28.3", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", "@babel/types": "^7.28.2", "debug": "^4.3.1" } }, "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ=="],
@@ -3057,6 +3092,10 @@
"metro-transform-worker/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="], "metro-transform-worker/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
"metro-transform-worker/metro": ["metro@0.83.1", "", { "dependencies": { "@babel/code-frame": "^7.24.7", "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/parser": "^7.25.3", "@babel/template": "^7.25.0", "@babel/traverse": "^7.25.3", "@babel/types": "^7.25.2", "accepts": "^1.3.7", "chalk": "^4.0.0", "ci-info": "^2.0.0", "connect": "^3.6.5", "debug": "^4.4.0", "error-stack-parser": "^2.0.6", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "hermes-parser": "0.29.1", "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", "metro-babel-transformer": "0.83.1", "metro-cache": "0.83.1", "metro-cache-key": "0.83.1", "metro-config": "0.83.1", "metro-core": "0.83.1", "metro-file-map": "0.83.1", "metro-resolver": "0.83.1", "metro-runtime": "0.83.1", "metro-source-map": "0.83.1", "metro-symbolicate": "0.83.1", "metro-transform-plugins": "0.83.1", "metro-transform-worker": "0.83.1", "mime-types": "^2.1.27", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", "source-map": "^0.5.6", "throat": "^5.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": { "metro": "src/cli.js" } }, "sha512-UGKepmTxoGD4HkQV8YWvpvwef7fUujNtTgG4Ygf7m/M0qjvb9VuDmAsEU+UdriRX7F61pnVK/opz89hjKlYTXA=="],
"metro-transform-worker/metro-cache": ["metro-cache@0.83.1", "", { "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", "https-proxy-agent": "^7.0.5", "metro-core": "0.83.1" } }, "sha512-7N/Ad1PHa1YMWDNiyynTPq34Op2qIE68NWryGEQ4TSE3Zy6a8GpsYnEEZE4Qi6aHgsE+yZHKkRczeBgxhnFIxQ=="],
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], "node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
@@ -3085,10 +3124,14 @@
"react-devtools-core/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="], "react-devtools-core/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="],
"react-native/@react-native/js-polyfills": ["@react-native/js-polyfills@0.81.4", "", {}, "sha512-sr42FaypKXJHMVHhgSbu2f/ZJfrLzgaoQ+HdpRvKEiEh2mhFf6XzZwecyLBvWqf2pMPZa+CpPfNPiejXjKEy8w=="],
"react-native/commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="], "react-native/commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="],
"react-native/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], "react-native/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
"react-native/metro-runtime": ["metro-runtime@0.83.1", "", { "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" } }, "sha512-3Ag8ZS4IwafL/JUKlaeM6/CbkooY+WcVeqdNlBG0m4S0Qz0om3rdFdy1y6fYBpl6AwXJwWeMuXrvZdMuByTcRA=="],
"react-native/semver": ["semver@7.7.2", "", { "bin": "bin/semver.js" }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], "react-native/semver": ["semver@7.7.2", "", { "bin": "bin/semver.js" }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"react-native-paper/color": ["color@3.2.1", "", { "dependencies": { "color-convert": "^1.9.3", "color-string": "^1.6.0" } }, "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA=="], "react-native-paper/color": ["color@3.2.1", "", { "dependencies": { "color-convert": "^1.9.3", "color-string": "^1.6.0" } }, "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA=="],
@@ -3265,8 +3308,26 @@
"@expo/metro-config/@expo/json-file/@babel/code-frame": ["@babel/code-frame@7.10.4", "", { "dependencies": { "@babel/highlight": "^7.10.4" } }, "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg=="], "@expo/metro-config/@expo/json-file/@babel/code-frame": ["@babel/code-frame@7.10.4", "", { "dependencies": { "@babel/highlight": "^7.10.4" } }, "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg=="],
"@expo/metro-config/hermes-parser/hermes-estree": ["hermes-estree@0.29.1", "", {}, "sha512-jl+x31n4/w+wEqm0I2r4CMimukLbLQEYpisys5oCre611CI5fc9TxhqkBBCJ1edDG4Kza0f7CgNz8xVMLZQOmQ=="],
"@expo/metro-config/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], "@expo/metro-config/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
"@expo/metro/metro/@babel/generator": ["@babel/generator@7.28.3", "", { "dependencies": { "@babel/parser": "^7.28.3", "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw=="],
"@expo/metro/metro/@babel/parser": ["@babel/parser@7.28.3", "", { "dependencies": { "@babel/types": "^7.28.2" }, "bin": "./bin/babel-parser.js" }, "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA=="],
"@expo/metro/metro/@babel/traverse": ["@babel/traverse@7.28.3", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", "@babel/types": "^7.28.2", "debug": "^4.3.1" } }, "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ=="],
"@expo/metro/metro/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
"@expo/metro/metro/ci-info": ["ci-info@2.0.0", "", {}, "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ=="],
"@expo/metro/metro/hermes-parser": ["hermes-parser@0.29.1", "", { "dependencies": { "hermes-estree": "0.29.1" } }, "sha512-xBHWmUtRC5e/UL0tI7Ivt2riA/YBq9+SiYFU7C1oBa/j2jYGlIF9043oak1F47ihuDIxQ5nbsKueYJDRY02UgA=="],
"@expo/metro/metro/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="],
"@expo/metro/metro-config/cosmiconfig": ["cosmiconfig@5.2.1", "", { "dependencies": { "import-fresh": "^2.0.0", "is-directory": "^0.3.1", "js-yaml": "^3.13.1", "parse-json": "^4.0.0" } }, "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA=="],
"@expo/package-manager/@expo/json-file/@babel/code-frame": ["@babel/code-frame@7.10.4", "", { "dependencies": { "@babel/highlight": "^7.10.4" } }, "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg=="], "@expo/package-manager/@expo/json-file/@babel/code-frame": ["@babel/code-frame@7.10.4", "", { "dependencies": { "@babel/highlight": "^7.10.4" } }, "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg=="],
"@expo/package-manager/ora/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="], "@expo/package-manager/ora/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="],
@@ -3295,16 +3356,44 @@
"@jest/reporters/string-length/char-regex": ["char-regex@1.0.2", "", {}, "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="], "@jest/reporters/string-length/char-regex": ["char-regex@1.0.2", "", {}, "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="],
"@react-native/babel-plugin-codegen/@babel/traverse/@babel/generator": ["@babel/generator@7.28.3", "", { "dependencies": { "@babel/parser": "^7.28.3", "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw=="],
"@react-native/babel-plugin-codegen/@babel/traverse/@babel/parser": ["@babel/parser@7.28.3", "", { "dependencies": { "@babel/types": "^7.28.2" }, "bin": "./bin/babel-parser.js" }, "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA=="],
"@react-native/babel-plugin-codegen/@babel/traverse/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
"@react-native/babel-plugin-codegen/@react-native/codegen/@babel/parser": ["@babel/parser@7.28.3", "", { "dependencies": { "@babel/types": "^7.28.2" }, "bin": "./bin/babel-parser.js" }, "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA=="],
"@react-native/babel-plugin-codegen/@react-native/codegen/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], "@react-native/babel-plugin-codegen/@react-native/codegen/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
"@react-native/babel-plugin-codegen/@react-native/codegen/hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="],
"@react-native/babel-preset/@babel/plugin-transform-classes/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
"@react-native/babel-preset/babel-plugin-syntax-hermes-parser/hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="],
"@react-native/codegen/@babel/parser/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="], "@react-native/codegen/@babel/parser/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
"@react-native/codegen/hermes-parser/hermes-estree": ["hermes-estree@0.29.1", "", {}, "sha512-jl+x31n4/w+wEqm0I2r4CMimukLbLQEYpisys5oCre611CI5fc9TxhqkBBCJ1edDG4Kza0f7CgNz8xVMLZQOmQ=="],
"@react-native/community-cli-plugin/metro/@babel/generator": ["@babel/generator@7.28.3", "", { "dependencies": { "@babel/parser": "^7.28.3", "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw=="],
"@react-native/community-cli-plugin/metro/@babel/parser": ["@babel/parser@7.28.3", "", { "dependencies": { "@babel/types": "^7.28.2" }, "bin": "./bin/babel-parser.js" }, "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA=="],
"@react-native/community-cli-plugin/metro/@babel/traverse": ["@babel/traverse@7.28.3", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", "@babel/types": "^7.28.2", "debug": "^4.3.1" } }, "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ=="],
"@react-native/community-cli-plugin/metro/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
"@react-native/community-cli-plugin/metro/ci-info": ["ci-info@2.0.0", "", {}, "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ=="],
"@react-native/community-cli-plugin/metro/hermes-parser": ["hermes-parser@0.29.1", "", { "dependencies": { "hermes-estree": "0.29.1" } }, "sha512-xBHWmUtRC5e/UL0tI7Ivt2riA/YBq9+SiYFU7C1oBa/j2jYGlIF9043oak1F47ihuDIxQ5nbsKueYJDRY02UgA=="],
"@react-native/community-cli-plugin/metro/metro-cache": ["metro-cache@0.83.1", "", { "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", "https-proxy-agent": "^7.0.5", "metro-core": "0.83.1" } }, "sha512-7N/Ad1PHa1YMWDNiyynTPq34Op2qIE68NWryGEQ4TSE3Zy6a8GpsYnEEZE4Qi6aHgsE+yZHKkRczeBgxhnFIxQ=="],
"@react-native/community-cli-plugin/metro/metro-runtime": ["metro-runtime@0.83.1", "", { "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" } }, "sha512-3Ag8ZS4IwafL/JUKlaeM6/CbkooY+WcVeqdNlBG0m4S0Qz0om3rdFdy1y6fYBpl6AwXJwWeMuXrvZdMuByTcRA=="],
"@react-native/community-cli-plugin/metro/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="],
"@react-native/community-cli-plugin/metro-config/cosmiconfig": ["cosmiconfig@5.2.1", "", { "dependencies": { "import-fresh": "^2.0.0", "is-directory": "^0.3.1", "js-yaml": "^3.13.1", "parse-json": "^4.0.0" } }, "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA=="],
"@react-native/community-cli-plugin/metro-config/metro-cache": ["metro-cache@0.83.1", "", { "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", "https-proxy-agent": "^7.0.5", "metro-core": "0.83.1" } }, "sha512-7N/Ad1PHa1YMWDNiyynTPq34Op2qIE68NWryGEQ4TSE3Zy6a8GpsYnEEZE4Qi6aHgsE+yZHKkRczeBgxhnFIxQ=="],
"@react-native/community-cli-plugin/metro-config/metro-runtime": ["metro-runtime@0.83.1", "", { "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" } }, "sha512-3Ag8ZS4IwafL/JUKlaeM6/CbkooY+WcVeqdNlBG0m4S0Qz0om3rdFdy1y6fYBpl6AwXJwWeMuXrvZdMuByTcRA=="],
"@react-native/dev-middleware/open/is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="], "@react-native/dev-middleware/open/is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
"@testing-library/react-native/pretty-format/@jest/schemas": ["@jest/schemas@30.0.5", "", { "dependencies": { "@sinclair/typebox": "^0.34.0" } }, "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA=="], "@testing-library/react-native/pretty-format/@jest/schemas": ["@jest/schemas@30.0.5", "", { "dependencies": { "@sinclair/typebox": "^0.34.0" } }, "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA=="],
@@ -3321,8 +3410,22 @@
"ansi-fragments/strip-ansi/ansi-regex": ["ansi-regex@4.1.1", "", {}, "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="], "ansi-fragments/strip-ansi/ansi-regex": ["ansi-regex@4.1.1", "", {}, "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="],
"babel-plugin-syntax-hermes-parser/hermes-parser/hermes-estree": ["hermes-estree@0.29.1", "", {}, "sha512-jl+x31n4/w+wEqm0I2r4CMimukLbLQEYpisys5oCre611CI5fc9TxhqkBBCJ1edDG4Kza0f7CgNz8xVMLZQOmQ=="],
"babel-preset-expo/@babel/plugin-transform-object-rest-spread/@babel/plugin-transform-destructuring": ["@babel/plugin-transform-destructuring@7.27.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-s4Jrok82JpiaIprtY2nHsYmrThKvvwgHwjgd7UMiYhZaN0asdXNLr0y+NjTfkA7SyQE5i2Fb7eawUOZmLvyqOA=="], "babel-preset-expo/@babel/plugin-transform-object-rest-spread/@babel/plugin-transform-destructuring": ["@babel/plugin-transform-destructuring@7.27.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-s4Jrok82JpiaIprtY2nHsYmrThKvvwgHwjgd7UMiYhZaN0asdXNLr0y+NjTfkA7SyQE5i2Fb7eawUOZmLvyqOA=="],
"babel-preset-expo/@react-native/babel-preset/@babel/plugin-transform-async-generator-functions": ["@babel/plugin-transform-async-generator-functions@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-remap-async-to-generator": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-eST9RrwlpaoJBDHShc+DS2SG4ATTi2MYNb4OxYkf3n+7eb49LWpnS+HSpVfW4x927qQwgk8A2hGNVaajAEw0EA=="],
"babel-preset-expo/@react-native/babel-preset/@babel/plugin-transform-block-scoping": ["@babel/plugin-transform-block-scoping@7.27.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-JF6uE2s67f0y2RZcm2kpAUEbD50vH62TyWVebxwHAlbSdM49VqPz8t4a1uIjp4NIOIZ4xzLfjY5emt/RCyC7TQ=="],
"babel-preset-expo/@react-native/babel-preset/@babel/plugin-transform-classes": ["@babel/plugin-transform-classes@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-compilation-targets": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/traverse": "^7.27.1", "globals": "^11.1.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-7iLhfFAubmpeJe/Wo2TVuDrykh/zlWXLzPNdL0Jqn/Xu8R3QQ8h9ff8FQoISZOsw74/HFqFI7NX63HN7QFIHKA=="],
"babel-preset-expo/@react-native/babel-preset/@babel/plugin-transform-destructuring": ["@babel/plugin-transform-destructuring@7.27.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-s4Jrok82JpiaIprtY2nHsYmrThKvvwgHwjgd7UMiYhZaN0asdXNLr0y+NjTfkA7SyQE5i2Fb7eawUOZmLvyqOA=="],
"babel-preset-expo/@react-native/babel-preset/@babel/plugin-transform-regenerator": ["@babel/plugin-transform-regenerator@7.27.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-uhB8yHerfe3MWnuLAhEbeQ4afVoqv8BQsPqrTv7e/jZ9y00kJL6l9a/f4OWaKxotmjzewfEyXE1vgDJenkQ2/Q=="],
"babel-preset-expo/@react-native/babel-preset/@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.79.5", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@react-native/codegen": "0.79.5" } }, "sha512-Rt/imdfqXihD/sn0xnV4flxxb1aLLjPtMF1QleQjEhJsTUPpH4TFlfOpoCvsrXoDl4OIcB1k4FVM24Ez92zf5w=="],
"babel-preset-expo/babel-plugin-syntax-hermes-parser/hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="], "babel-preset-expo/babel-plugin-syntax-hermes-parser/hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="],
"better-opn/open/is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="], "better-opn/open/is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
@@ -3405,11 +3508,7 @@
"logkitty/yargs/yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="], "logkitty/yargs/yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="],
"metro-config/cosmiconfig/import-fresh": ["import-fresh@2.0.0", "", { "dependencies": { "caller-path": "^2.0.0", "resolve-from": "^3.0.0" } }, "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg=="], "metro-babel-transformer/hermes-parser/hermes-estree": ["hermes-estree@0.29.1", "", {}, "sha512-jl+x31n4/w+wEqm0I2r4CMimukLbLQEYpisys5oCre611CI5fc9TxhqkBBCJ1edDG4Kza0f7CgNz8xVMLZQOmQ=="],
"metro-config/cosmiconfig/js-yaml": ["js-yaml@3.14.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": "bin/js-yaml.js" }, "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g=="],
"metro-config/cosmiconfig/parse-json": ["parse-json@4.0.0", "", { "dependencies": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" } }, "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw=="],
"metro-source-map/@babel/traverse/@babel/generator": ["@babel/generator@7.28.3", "", { "dependencies": { "@babel/parser": "^7.28.3", "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw=="], "metro-source-map/@babel/traverse/@babel/generator": ["@babel/generator@7.28.3", "", { "dependencies": { "@babel/parser": "^7.28.3", "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw=="],
@@ -3431,10 +3530,30 @@
"metro-transform-worker/@babel/generator/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.30", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q=="], "metro-transform-worker/@babel/generator/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.30", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q=="],
"metro-transform-worker/metro/@babel/traverse": ["@babel/traverse@7.28.3", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", "@babel/types": "^7.28.2", "debug": "^4.3.1" } }, "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ=="],
"metro-transform-worker/metro/ci-info": ["ci-info@2.0.0", "", {}, "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ=="],
"metro-transform-worker/metro/hermes-parser": ["hermes-parser@0.29.1", "", { "dependencies": { "hermes-estree": "0.29.1" } }, "sha512-xBHWmUtRC5e/UL0tI7Ivt2riA/YBq9+SiYFU7C1oBa/j2jYGlIF9043oak1F47ihuDIxQ5nbsKueYJDRY02UgA=="],
"metro-transform-worker/metro/metro-config": ["metro-config@0.83.1", "", { "dependencies": { "connect": "^3.6.5", "cosmiconfig": "^5.0.5", "flow-enums-runtime": "^0.0.6", "jest-validate": "^29.7.0", "metro": "0.83.1", "metro-cache": "0.83.1", "metro-core": "0.83.1", "metro-runtime": "0.83.1" } }, "sha512-HJhpZx3wyOkux/jeF1o7akFJzZFdbn6Zf7UQqWrvp7gqFqNulQ8Mju09raBgPmmSxKDl4LbbNeigkX0/nKY1QA=="],
"metro-transform-worker/metro/metro-core": ["metro-core@0.83.1", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", "metro-resolver": "0.83.1" } }, "sha512-uVL1eAJcMFd2o2Q7dsbpg8COaxjZBBGaXqO2OHnivpCdfanraVL8dPmY6It9ZeqWLOihUKZ2yHW4b6soVCzH/Q=="],
"metro-transform-worker/metro/metro-runtime": ["metro-runtime@0.83.1", "", { "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" } }, "sha512-3Ag8ZS4IwafL/JUKlaeM6/CbkooY+WcVeqdNlBG0m4S0Qz0om3rdFdy1y6fYBpl6AwXJwWeMuXrvZdMuByTcRA=="],
"metro-transform-worker/metro/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="],
"metro-transform-worker/metro-cache/metro-core": ["metro-core@0.83.1", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", "metro-resolver": "0.83.1" } }, "sha512-uVL1eAJcMFd2o2Q7dsbpg8COaxjZBBGaXqO2OHnivpCdfanraVL8dPmY6It9ZeqWLOihUKZ2yHW4b6soVCzH/Q=="],
"metro/@babel/generator/@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], "metro/@babel/generator/@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
"metro/@babel/generator/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.30", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q=="], "metro/@babel/generator/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.30", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q=="],
"metro/metro-source-map/ob1": ["ob1@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-egUxXCDwoWG06NGCS5s5AdcpnumHKJlfd3HH06P3m9TEMwwScfcY35wpQxbm9oHof+dM/lVH9Rfyu1elTVelSA=="],
"metro/metro-transform-worker/metro-minify-terser": ["metro-minify-terser@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "terser": "^5.15.0" } }, "sha512-O2BmfWj6FSfzBLrNCXt/rr2VYZdX5i6444QJU0fFoc7Ljg+Q+iqebwE3K0eTvkI6TRjELsXk1cjU+fXwAR4OjQ=="],
"node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], "node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
"node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], "node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
@@ -3529,6 +3648,18 @@
"@expo/cli/ora/strip-ansi/ansi-regex": ["ansi-regex@4.1.1", "", {}, "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="], "@expo/cli/ora/strip-ansi/ansi-regex": ["ansi-regex@4.1.1", "", {}, "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="],
"@expo/metro/metro-config/cosmiconfig/import-fresh": ["import-fresh@2.0.0", "", { "dependencies": { "caller-path": "^2.0.0", "resolve-from": "^3.0.0" } }, "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg=="],
"@expo/metro/metro-config/cosmiconfig/js-yaml": ["js-yaml@3.14.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": "bin/js-yaml.js" }, "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g=="],
"@expo/metro/metro-config/cosmiconfig/parse-json": ["parse-json@4.0.0", "", { "dependencies": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" } }, "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw=="],
"@expo/metro/metro/@babel/generator/@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
"@expo/metro/metro/@babel/generator/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.30", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q=="],
"@expo/metro/metro/hermes-parser/hermes-estree": ["hermes-estree@0.29.1", "", {}, "sha512-jl+x31n4/w+wEqm0I2r4CMimukLbLQEYpisys5oCre611CI5fc9TxhqkBBCJ1edDG4Kza0f7CgNz8xVMLZQOmQ=="],
"@expo/package-manager/ora/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="], "@expo/package-manager/ora/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="],
"@expo/package-manager/ora/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="], "@expo/package-manager/ora/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
@@ -3543,12 +3674,30 @@
"@jest/reporters/istanbul-lib-instrument/@babel/parser/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="], "@jest/reporters/istanbul-lib-instrument/@babel/parser/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
"@react-native/babel-plugin-codegen/@react-native/codegen/hermes-parser/hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="], "@react-native/babel-plugin-codegen/@babel/traverse/@babel/generator/@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
"@react-native/babel-preset/babel-plugin-syntax-hermes-parser/hermes-parser/hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="], "@react-native/babel-plugin-codegen/@babel/traverse/@babel/generator/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.30", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q=="],
"@react-native/babel-plugin-codegen/@react-native/codegen/@babel/parser/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
"@react-native/community-cli-plugin/metro-config/cosmiconfig/import-fresh": ["import-fresh@2.0.0", "", { "dependencies": { "caller-path": "^2.0.0", "resolve-from": "^3.0.0" } }, "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg=="],
"@react-native/community-cli-plugin/metro-config/cosmiconfig/js-yaml": ["js-yaml@3.14.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": "bin/js-yaml.js" }, "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g=="],
"@react-native/community-cli-plugin/metro-config/cosmiconfig/parse-json": ["parse-json@4.0.0", "", { "dependencies": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" } }, "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw=="],
"@react-native/community-cli-plugin/metro/@babel/generator/@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
"@react-native/community-cli-plugin/metro/@babel/generator/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.30", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q=="],
"@react-native/community-cli-plugin/metro/hermes-parser/hermes-estree": ["hermes-estree@0.29.1", "", {}, "sha512-jl+x31n4/w+wEqm0I2r4CMimukLbLQEYpisys5oCre611CI5fc9TxhqkBBCJ1edDG4Kza0f7CgNz8xVMLZQOmQ=="],
"@testing-library/react-native/pretty-format/@jest/schemas/@sinclair/typebox": ["@sinclair/typebox@0.34.40", "", {}, "sha512-gwBNIP8ZAYev/ORDWW0QvxdwPXwxBtLsdsJgSc7eDIRt8ubP+rxUBzPsrwnu16fgEF8Bx4lh/+mvQvJzcTM6Kw=="], "@testing-library/react-native/pretty-format/@jest/schemas/@sinclair/typebox": ["@sinclair/typebox@0.34.40", "", {}, "sha512-gwBNIP8ZAYev/ORDWW0QvxdwPXwxBtLsdsJgSc7eDIRt8ubP+rxUBzPsrwnu16fgEF8Bx4lh/+mvQvJzcTM6Kw=="],
"babel-preset-expo/@react-native/babel-preset/@babel/plugin-transform-classes/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
"babel-preset-expo/@react-native/babel-preset/@react-native/babel-plugin-codegen/@react-native/codegen": ["@react-native/codegen@0.79.5", "", { "dependencies": { "glob": "^7.1.1", "hermes-parser": "0.25.1", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "yargs": "^17.6.2" }, "peerDependencies": { "@babel/core": "*" } }, "sha512-FO5U1R525A1IFpJjy+KVznEinAgcs3u7IbnbRJUG9IH/MBXi2lEU2LtN+JarJ81MCfW4V2p0pg6t/3RGHFRrlQ=="],
"babel-preset-expo/babel-plugin-syntax-hermes-parser/hermes-parser/hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="], "babel-preset-expo/babel-plugin-syntax-hermes-parser/hermes-parser/hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="],
"expo-constants/@expo/config/@expo/config-plugins/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], "expo-constants/@expo/config/@expo/config-plugins/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
@@ -3575,14 +3724,14 @@
"logkitty/yargs/yargs-parser/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], "logkitty/yargs/yargs-parser/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="],
"metro-config/cosmiconfig/import-fresh/resolve-from": ["resolve-from@3.0.0", "", {}, "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw=="],
"metro-config/cosmiconfig/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
"metro-source-map/@babel/traverse/@babel/generator/@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], "metro-source-map/@babel/traverse/@babel/generator/@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
"metro-source-map/@babel/traverse/@babel/generator/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.30", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q=="], "metro-source-map/@babel/traverse/@babel/generator/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.30", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q=="],
"metro-transform-worker/metro/hermes-parser/hermes-estree": ["hermes-estree@0.29.1", "", {}, "sha512-jl+x31n4/w+wEqm0I2r4CMimukLbLQEYpisys5oCre611CI5fc9TxhqkBBCJ1edDG4Kza0f7CgNz8xVMLZQOmQ=="],
"metro-transform-worker/metro/metro-config/cosmiconfig": ["cosmiconfig@5.2.1", "", { "dependencies": { "import-fresh": "^2.0.0", "is-directory": "^0.3.1", "js-yaml": "^3.13.1", "parse-json": "^4.0.0" } }, "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA=="],
"pkg-dir/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], "pkg-dir/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
"qrcode/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "qrcode/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
@@ -3613,6 +3762,10 @@
"@expo/cli/ora/cli-cursor/restore-cursor/onetime": ["onetime@2.0.1", "", { "dependencies": { "mimic-fn": "^1.0.0" } }, "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ=="], "@expo/cli/ora/cli-cursor/restore-cursor/onetime": ["onetime@2.0.1", "", { "dependencies": { "mimic-fn": "^1.0.0" } }, "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ=="],
"@expo/metro/metro-config/cosmiconfig/import-fresh/resolve-from": ["resolve-from@3.0.0", "", {}, "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw=="],
"@expo/metro/metro-config/cosmiconfig/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
"@expo/package-manager/ora/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="], "@expo/package-manager/ora/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
"@expo/package-manager/ora/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="], "@expo/package-manager/ora/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
@@ -3621,12 +3774,28 @@
"@istanbuljs/load-nyc-config/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], "@istanbuljs/load-nyc-config/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
"@react-native/community-cli-plugin/metro-config/cosmiconfig/import-fresh/resolve-from": ["resolve-from@3.0.0", "", {}, "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw=="],
"@react-native/community-cli-plugin/metro-config/cosmiconfig/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
"babel-preset-expo/@react-native/babel-preset/@react-native/babel-plugin-codegen/@react-native/codegen/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
"babel-preset-expo/@react-native/babel-preset/@react-native/babel-plugin-codegen/@react-native/codegen/hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="],
"expo/babel-preset-expo/@react-native/babel-preset/@react-native/babel-plugin-codegen/@babel/traverse": ["@babel/traverse@7.28.3", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", "@babel/types": "^7.28.2", "debug": "^4.3.1" } }, "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ=="], "expo/babel-preset-expo/@react-native/babel-preset/@react-native/babel-plugin-codegen/@babel/traverse": ["@babel/traverse@7.28.3", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", "@babel/types": "^7.28.2", "debug": "^4.3.1" } }, "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ=="],
"expo/babel-preset-expo/@react-native/babel-preset/babel-plugin-syntax-hermes-parser/hermes-parser": ["hermes-parser@0.29.1", "", { "dependencies": { "hermes-estree": "0.29.1" } }, "sha512-xBHWmUtRC5e/UL0tI7Ivt2riA/YBq9+SiYFU7C1oBa/j2jYGlIF9043oak1F47ihuDIxQ5nbsKueYJDRY02UgA=="],
"expo/babel-preset-expo/babel-plugin-syntax-hermes-parser/hermes-parser/hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="], "expo/babel-preset-expo/babel-plugin-syntax-hermes-parser/hermes-parser/hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="],
"logkitty/yargs/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], "logkitty/yargs/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
"metro-transform-worker/metro/metro-config/cosmiconfig/import-fresh": ["import-fresh@2.0.0", "", { "dependencies": { "caller-path": "^2.0.0", "resolve-from": "^3.0.0" } }, "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg=="],
"metro-transform-worker/metro/metro-config/cosmiconfig/js-yaml": ["js-yaml@3.14.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": "bin/js-yaml.js" }, "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g=="],
"metro-transform-worker/metro/metro-config/cosmiconfig/parse-json": ["parse-json@4.0.0", "", { "dependencies": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" } }, "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw=="],
"pkg-dir/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], "pkg-dir/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
"qrcode/yargs/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], "qrcode/yargs/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
@@ -3639,14 +3808,22 @@
"@expo/package-manager/ora/cli-cursor/restore-cursor/onetime/mimic-fn": ["mimic-fn@1.2.0", "", {}, "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ=="], "@expo/package-manager/ora/cli-cursor/restore-cursor/onetime/mimic-fn": ["mimic-fn@1.2.0", "", {}, "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ=="],
"babel-preset-expo/@react-native/babel-preset/@react-native/babel-plugin-codegen/@react-native/codegen/hermes-parser/hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="],
"expo/babel-preset-expo/@react-native/babel-preset/@react-native/babel-plugin-codegen/@babel/traverse/@babel/generator": ["@babel/generator@7.28.3", "", { "dependencies": { "@babel/parser": "^7.28.3", "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw=="], "expo/babel-preset-expo/@react-native/babel-preset/@react-native/babel-plugin-codegen/@babel/traverse/@babel/generator": ["@babel/generator@7.28.3", "", { "dependencies": { "@babel/parser": "^7.28.3", "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw=="],
"expo/babel-preset-expo/@react-native/babel-preset/@react-native/babel-plugin-codegen/@babel/traverse/@babel/parser": ["@babel/parser@7.28.3", "", { "dependencies": { "@babel/types": "^7.28.2" }, "bin": "./bin/babel-parser.js" }, "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA=="], "expo/babel-preset-expo/@react-native/babel-preset/@react-native/babel-plugin-codegen/@babel/traverse/@babel/parser": ["@babel/parser@7.28.3", "", { "dependencies": { "@babel/types": "^7.28.2" }, "bin": "./bin/babel-parser.js" }, "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA=="],
"expo/babel-preset-expo/@react-native/babel-preset/@react-native/babel-plugin-codegen/@babel/traverse/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="], "expo/babel-preset-expo/@react-native/babel-preset/@react-native/babel-plugin-codegen/@babel/traverse/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
"expo/babel-preset-expo/@react-native/babel-preset/babel-plugin-syntax-hermes-parser/hermes-parser/hermes-estree": ["hermes-estree@0.29.1", "", {}, "sha512-jl+x31n4/w+wEqm0I2r4CMimukLbLQEYpisys5oCre611CI5fc9TxhqkBBCJ1edDG4Kza0f7CgNz8xVMLZQOmQ=="],
"logkitty/yargs/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], "logkitty/yargs/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
"metro-transform-worker/metro/metro-config/cosmiconfig/import-fresh/resolve-from": ["resolve-from@3.0.0", "", {}, "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw=="],
"metro-transform-worker/metro/metro-config/cosmiconfig/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
"qrcode/yargs/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], "qrcode/yargs/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
"expo/babel-preset-expo/@react-native/babel-preset/@react-native/babel-plugin-codegen/@babel/traverse/@babel/generator/@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], "expo/babel-preset-expo/@react-native/babel-preset/@react-native/babel-plugin-codegen/@babel/traverse/@babel/generator/@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],

View File

@@ -12,10 +12,12 @@ const LeftButtonCustom = ({
path, path,
icon = "arrow-back", icon = "arrow-back",
iconCustom, iconCustom,
onPress,
}: { }: {
path?: Href; path?: Href;
icon?: React.ReactNode | any; icon?: React.ReactNode | any;
iconCustom?: React.ReactNode; iconCustom?: React.ReactNode;
onPress?: () => void;
}) => { }) => {
return ( return (
<> <>
@@ -26,7 +28,7 @@ const LeftButtonCustom = ({
name={icon} name={icon}
size={20} size={20}
color={MainColor.yellow} 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, disabled = false,
}) => { }) => {
console.log("Date Android Comp", value)
return ( return (
<> <>
{Platform.OS === "ios" ? ( {Platform.OS === "ios" ? (

View File

@@ -0,0 +1,140 @@
// src/components/BackgroundNotificationHandler.tsx
import { useNotificationStore } from "@/hooks/use-notification-store";
import AsyncStorage from "@react-native-async-storage/async-storage";
import {
FirebaseMessagingTypes,
getInitialNotification,
getMessaging,
onNotificationOpenedApp,
} from "@react-native-firebase/messaging";
import { router } from "expo-router";
import { useEffect, useRef } from "react";
const HANDLED_NOTIFICATIONS_KEY = "handled_notifications";
export default function BackgroundNotificationHandler() {
const { addNotification, markAsRead } = useNotificationStore();
const messaging = getMessaging();
const unsubscribeRef = useRef<(() => void) | null>(null); // 🔑 cegah duplikasi
useEffect(() => {
const init = async () => {
// 1. Handle (cold start)
const initialNotification = await getInitialNotification(messaging);
if (initialNotification) {
handleNotification(initialNotification);
return;
}
// 2. Handle background
if (unsubscribeRef.current) {
unsubscribeRef.current();
}
const unsubscribe = onNotificationOpenedApp(
messaging,
(remoteMessage) => {
handleNotification(remoteMessage);
}
);
unsubscribeRef.current = unsubscribe;
};
init();
// Cleanup saat komponen unmount
return () => {
if (unsubscribeRef.current) {
unsubscribeRef.current();
unsubscribeRef.current = null;
}
};
}, [addNotification, messaging]);
const isNotificationHandled = async (
notificationId: string
): Promise<boolean> => {
const handled = await AsyncStorage.getItem(HANDLED_NOTIFICATIONS_KEY);
const ids = handled ? JSON.parse(handled) : [];
return ids.includes(notificationId);
};
const markNotificationAsHandled = async (notificationId: string) => {
const handled = await AsyncStorage.getItem(HANDLED_NOTIFICATIONS_KEY);
const ids = handled ? JSON.parse(handled) : [];
if (!ids.includes(notificationId)) {
ids.push(notificationId);
// Simpan maksimal 50 ID terakhir untuk hindari memori bocor
await AsyncStorage.setItem(
HANDLED_NOTIFICATIONS_KEY,
JSON.stringify(ids.slice(-50))
);
}
};
const handleNotification = async (
remoteMessage: FirebaseMessagingTypes.RemoteMessage
) => {
const { notification, data } = remoteMessage;
if (!notification?.title) return;
console.log(
"🚀 Notification received:",
JSON.stringify(remoteMessage, null, 2)
);
const notificationId = data?.id;
if (!notificationId || typeof notificationId !== "string") {
console.warn("Notification missing notificationId, skipping navigation");
return;
}
// ✅ Cek apakah sudah pernah ditangani
if (await isNotificationHandled(notificationId)) {
console.log("Notification already handled, skipping:", notificationId);
return;
}
// ✅ Tandai sebagai ditangani
await markNotificationAsHandled(notificationId);
// ✅ Normalisasi deepLink: pastikan string
let deepLink: string | undefined;
if (data?.deepLink) {
if (typeof data.deepLink === "string") {
deepLink = data.deepLink;
} else {
// Jika object (jarang), coba string-kan
deepLink = JSON.stringify(data.deepLink);
}
}
// Tambahkan ke UI state (agar muncul di daftar notifikasi & badge)
addNotification({
title: notification.title,
body: notification.body || "",
type: "announcement",
data: data as Record<string, string>, // aman karena di-normalisasi di useNotificationStore
});
markAsRead(data?.id as any);
// Navigasi
if (
data?.deepLink &&
typeof data.deepLink === "string" &&
data.deepLink.startsWith("/")
) {
setTimeout(() => {
try {
router.push(data.deepLink as any);
} catch (error) {
console.warn("Navigation failed:", error);
}
}, 100);
}
};
return null;
}

View File

@@ -0,0 +1,112 @@
// src/components/NotificationInitializer.tsx
import { useEffect } from "react";
import { useForegroundNotifications } from "@/hooks/use-foreground-notifications";
import { useNotificationStore } from "@/hooks/use-notification-store";
import type { FirebaseMessagingTypes } from "@react-native-firebase/messaging";
import { useAuth } from "@/hooks/use-auth";
import { Platform } from "react-native";
import * as Device from "expo-device";
import * as Application from "expo-application";
import { apiDeviceRegisterToken } from "@/service/api-device-token";
import messaging, {
isSupported,
requestPermission,
getToken,
AuthorizationStatus,
} from "@react-native-firebase/messaging";
// ✅ Modular imports (sesuai v22+)
export default function NotificationInitializer() {
// Setup handler notifikasi
const { user, logout } = useAuth(); // dari AuthContext
const { addNotification } = useNotificationStore();
// Ambil token FCM (opsional, hanya untuk log)
useEffect(() => {
if (!user) {
console.log("User not available, skipping token sync");
return;
}
const registerDeviceToken = async () => {
try {
// ✅ Dapatkan instance messaging
const messagingInstance = messaging();
// ✅ Gunakan instance sebagai argumen
const supported = await isSupported(messagingInstance);
if (!supported) {
console.log("‼️ FCM tidak didukung");
return;
}
const authStatus = await requestPermission(messagingInstance);
if (authStatus !== AuthorizationStatus.AUTHORIZED) {
console.warn("Izin telah ditolak");
return;
}
const fcmToken = await getToken(messagingInstance);
if (!fcmToken) {
console.warn("Tidak bisa mendapatkan FCM token");
// logout();
return;
}
console.log("✅ FCM Token:", fcmToken);
const platform = Platform.OS; // "ios" | "android"
const model = Device.modelName || "unknown";
const appVersion =
(Application.nativeApplicationVersion || "unknown") +
"-" +
(Application.nativeBuildVersion || "unknown");
const deviceId =
Device.osInternalBuildId || Device.modelName || "unknown";
// Kirim ke backend
await apiDeviceRegisterToken({
data: {
fcmToken,
platform,
deviceId,
model,
appVersion,
userId: user?.id || "",
},
});
console.log("✅ Device token berhasil didaftarkan ke backend");
} catch (error) {
console.error("❌ Gagal mendaftarkan device token:", error);
}
};
registerDeviceToken();
}, [user?.id]);
const handleForegroundNotification = (
message: FirebaseMessagingTypes.RemoteMessage
) => {
const title = message.notification?.title || "Notifikasi";
const body = message.notification?.body || "";
const rawData = message.data || {};
const safeData: Record<string, string> = {};
for (const key in rawData) {
safeData[key] =
typeof rawData[key] === "string"
? rawData[key]
: JSON.stringify(rawData[key]);
}
console.log("📥 Menambahkan ke store:", { title, body, safeData });
addNotification({ title, body, data: safeData, type: "announcement" });
console.log("✅ Notifikasi ditambahkan ke state");
};
useForegroundNotifications(handleForegroundNotification);
return null; // komponen ini tidak merender apa-apa
}

View File

@@ -98,13 +98,14 @@ export const IconView = ({
); );
}; };
export const IconDot = ({ size, color }: { size?: number; color?: string }) => { export const IconDot = ({ size, color, onPress }: { size?: number; color?: string , onPress?: () => void}) => {
return ( return (
<> <>
<Ionicons <Ionicons
name="ellipsis-vertical" name="ellipsis-vertical"
size={size || ICON_SIZE_MEDIUM} size={size || ICON_SIZE_MEDIUM}
color={color || MainColor.darkblue} color={color || MainColor.darkblue}
onPress={onPress}
/> />
</> </>
); );

View File

@@ -4,12 +4,21 @@ import { Octicons } from "@expo/vector-icons";
export { IconPlus }; export { IconPlus };
function IconPlus({ color, size }: { color?: string; size?: number }) { function IconPlus({
color,
size,
onPress,
}: {
color?: string;
size?: number;
onPress?: () => void;
}) {
return ( return (
<Octicons <Octicons
name="plus-circle" name="plus-circle"
size={size || ICON_SIZE_MEDIUM} size={size || ICON_SIZE_MEDIUM}
color={color || MainColor.white} color={color || MainColor.white}
onPress={onPress}
/> />
); );
} }

View File

@@ -2,10 +2,13 @@ import {
apiConfig, apiConfig,
apiLogin, apiLogin,
apiRegister, apiRegister,
apiUpdatedTermCondition,
apiValidationCode, apiValidationCode,
} from "@/service/api-config"; } from "@/service/api-config";
import { apiDeviceTokenDeleted } from "@/service/api-device-token";
import { IUser } from "@/types/User"; import { IUser } from "@/types/User";
import AsyncStorage from "@react-native-async-storage/async-storage"; import AsyncStorage from "@react-native-async-storage/async-storage";
import * as Device from "expo-device";
import { router } from "expo-router"; import { router } from "expo-router";
import { createContext, useEffect, useState } from "react"; import { createContext, useEffect, useState } from "react";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
@@ -27,6 +30,7 @@ type AuthContextType = {
termsOfServiceAccepted: boolean; termsOfServiceAccepted: boolean;
}) => Promise<void>; }) => Promise<void>;
userData: (token: string) => Promise<any>; userData: (token: string) => Promise<any>;
acceptedTerms: (nomor: string) => Promise<any>;
}; };
// --- Create Context --- // --- Create Context ---
@@ -72,29 +76,32 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
const loginWithNomor = async (nomor: string) => { const loginWithNomor = async (nomor: string) => {
setIsLoading(true); setIsLoading(true);
try { try {
console.log("[Masuk provider]", nomor);
const response = await apiLogin({ nomor: 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) { if (response.success) {
console.log("[Keluar provider]", nomor); if (response.isAcceptTerms) {
Toast.show({ Toast.show({
type: "success", type: "success",
text1: "Sukses", text1: "Sukses",
text2: "Kode OTP berhasil dikirim", text2: "Kode OTP berhasil dikirim",
}); });
await AsyncStorage.setItem("kode_otp", response.kodeId); await AsyncStorage.setItem("kode_otp", response.kodeId);
router.push(`/verification?nomor=${nomor}`); router.push(`/verification?nomor=${nomor}`);
return; return;
} else {
router.push(`/eula?nomor=${nomor}`);
return;
}
} else { } else {
router.push(`/register?nomor=${nomor}`); router.push(`/eula?nomor=${nomor}`);
Toast.show({
type: "info", // Toast.show({
text1: "Info", // type: "info",
text2: "Silahkan mendaftar", // text1: "Info",
}); // text2: "Silahkan mendaftar",
// });
return; return;
} }
} catch (error: any) { } catch (error: any) {
@@ -104,18 +111,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 --- // --- 2. Validasi OTP & cek user ---
const validateOtp = async (nomor: string) => { const validateOtp = async (nomor: string) => {
try { try {
@@ -139,13 +134,15 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
await AsyncStorage.setItem("userData", JSON.stringify(dataUser)); await AsyncStorage.setItem("userData", JSON.stringify(dataUser));
if (response.active) { if (response.active) {
if (response.roleId === "1") { // if (response.roleId === "1") {
router.replace("/(application)/(user)/home"); // router.replace("/(application)/(user)/home");
return; // return;
} else { // } else {
router.replace("/(application)/admin/dashboard"); // router.replace("/(application)/admin/dashboard");
return; // return;
} // }
router.replace("/(application)/(user)/home");
return;
} else { } else {
router.replace("/(application)/(user)/waiting-room"); router.replace("/(application)/(user)/waiting-room");
return; return;
@@ -206,7 +203,6 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
setIsLoading(true); setIsLoading(true);
try { try {
const response = await apiRegister({ data: userData }); const response = await apiRegister({ data: userData });
console.log("[REGISTER FETCH]", JSON.stringify(response, null, 2));
if (!response.success) { if (!response.success) {
Toast.show({ Toast.show({
@@ -236,42 +232,6 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
setIsLoading(false); 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 --- // --- 5. Logout ---
@@ -280,9 +240,13 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
setIsLoading(true); setIsLoading(true);
setToken(null); setToken(null);
setUser(null); setUser(null);
const deviceId =
Device.osInternalBuildId || Device.modelName || "unknown";
await AsyncStorage.removeItem("authToken"); await AsyncStorage.removeItem("authToken");
await AsyncStorage.removeItem("userData"); await AsyncStorage.removeItem("userData");
setIsLoading(false); await apiDeviceTokenDeleted({ userId: user?.id as any, deviceId });
Toast.show({ Toast.show({
type: "success", type: "success",
@@ -297,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 ( return (
<> <>
<AuthContext.Provider <AuthContext.Provider
@@ -312,6 +301,7 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
logout, logout,
registerUser, registerUser,
userData, userData,
acceptedTerms,
}} }}
> >
{children} {children}

View File

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

View File

@@ -13,3 +13,13 @@ Exp: open ios/HIPMIBADUNG.xcworkspace
perubahan versi : npm version patch perubahan versi : npm version patch
ios: bunx expo prebuild --platform ios ios: bunx expo prebuild --platform ios
android: bunx expo prebuild --platform android 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

View File

@@ -0,0 +1,37 @@
import { useEffect } from "react";
import {
getMessaging,
onMessage,
FirebaseMessagingTypes,
} from "@react-native-firebase/messaging";
import { useAuth } from "./use-auth";
// Gunakan tipe resmi dari library
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(data, null, 2)
);
onMessageReceived(remoteMessage);
});
return unsubscribe;
}, [user?.id, onMessageReceived]);
}

View File

@@ -0,0 +1,168 @@
// hooks/useNotificationStore.ts
import {
apiNotificationMarkAsRead,
apiNotificationUnreadCount,
} from "@/service/api-notifications";
import {
createContext,
ReactNode,
useContext,
useEffect,
useState,
} from "react";
import { useAuth } from "./use-auth";
type AppNotification = {
id: string;
title: string;
body: string;
data?: Record<string, string>;
isRead: boolean;
timestamp: number;
type: "announcement" | "trigger";
// untuk id dari setiap kategori app
appId?: string;
kategoriApp?:
| "JOB"
| "VOTING"
| "EVENT"
| "DONASI"
| "INVESTASI"
| "COLLABORATION"
| "FORUM"
| "ACCESS"; // Untuk trigger akses user;
};
type NotificationContextType = {
notifications: AppNotification[];
unreadCount: number;
addNotification: (
notif: Omit<AppNotification, "id" | "isRead" | "timestamp">
) => void;
markAsRead: (id: string) => void;
markAsReadAll: (id: string) => void;
syncUnreadCount: () => Promise<void>;
};
const NotificationContext = createContext<NotificationContextType>({
notifications: [],
unreadCount: 0,
addNotification: () => {},
markAsRead: () => {},
markAsReadAll: () => {},
syncUnreadCount: async () => {},
});
export const NotificationProvider = ({ children }: { children: ReactNode }) => {
const { user } = useAuth();
const [notifications, setNotifications] = useState<AppNotification[]>([]);
const [unreadCount, setUnreadCount] = useState(0);
console.log(
"🚀 Notifications Masuk:",
JSON.stringify(notifications, null, 2)
);
// Sync unread count dari backend saat provider di-mount
useEffect(() => {
fetchUnreadCount();
}, [user?.id]);
const fetchUnreadCount = async () => {
try {
const count = await apiNotificationUnreadCount({
id: user?.id as any,
role: user?.masterUserRoleId as any,
}); // ← harus return number
const result = count.data;
console.log("📖 Unread count:", result);
setUnreadCount(result);
} catch (error) {
console.error("Gagal fetch unread count:", error);
}
};
const addNotification = (
notif: Omit<AppNotification, "id" | "isRead" | "timestamp">
) => {
setNotifications((prev) => [
{
...notif,
id: Date.now().toString(),
isRead: false,
timestamp: Date.now(),
},
...prev,
]);
setUnreadCount((prev) => prev + 1);
};
const markAsRead = async (id: string) => {
try {
const response = await apiNotificationMarkAsRead({ id, category: "one" });
console.log("🚀 Response Mark As Read:", 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 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({
id: user?.id as any,
role: user?.masterUserRoleId as any,
}); // ← harus return number
const result = count.data;
console.log("📖 Unread count sync:", result);
setUnreadCount(result);
} catch (error) {
console.warn("⚠️ Gagal sync unread count:", error);
}
};
return (
<NotificationContext.Provider
value={{
notifications,
addNotification,
unreadCount,
markAsRead,
markAsReadAll,
syncUnreadCount,
}}
>
{children}
</NotificationContext.Provider>
);
};
export const useNotificationStore = () => useContext(NotificationContext);

View File

@@ -0,0 +1,113 @@
// hooks/useNotificationStore.ts
import { apiGetNotificationsById } from "@/service/api-notifications";
import { createContext, ReactNode, useContext, useState, useEffect } from "react";
import { useAuth } from "./use-auth";
type AppNotification = {
id: string;
title: string;
body: string;
data?: Record<string, string>;
isRead: boolean;
timestamp: number;
type: "notification" | "trigger";
appId?: string;
kategoriApp?:
| "JOB"
| "VOTING"
| "EVENT"
| "DONASI"
| "INVESTASI"
| "COLLABORATION"
| "FORUM"
| "ACCESS";
};
type NotificationContextType = {
notifications: AppNotification[];
unreadCount: number;
addNotification: (
notif: Omit<AppNotification, "id" | "isRead" | "timestamp">
) => void;
markAsRead: (id: string) => void;
syncUnreadCount: () => Promise<void>;
};
const NotificationContext = createContext<NotificationContextType>({
notifications: [],
unreadCount: 0,
addNotification: () => {},
markAsRead: () => {},
syncUnreadCount: async () => {},
});
export const NotificationProvider = ({ children }: { children: ReactNode }) => {
const {user} = useAuth()
const [notifications, setNotifications] = useState<AppNotification[]>([]);
const [unreadCount, setUnreadCount] = useState(0);
// 🔔 Sync unread count dari backend saat provider di-mount
useEffect(() => {
const fetchUnreadCount = async () => {
try {
const count = await apiGetNotificationsById({
id: user?.id as any,
category: "count-as-unread"
}); // ← harus return number
const result = count.data
setUnreadCount(result);
} catch (error) {
console.erro("⚠️ Gagal fetch unread count:", error);
}
};
fetchUnreadCount();
}, []);
const addNotification = (
notif: Omit<AppNotification, "id" | "isRead" | "timestamp">
) => {
setNotifications((prev) => [
{
...notif,
id: Date.now().toString(),
isRead: false,
timestamp: Date.now(),
},
...prev,
]);
// Tambahkan ke unread count (untuk notifikasi foreground)
setUnreadCount((prev) => prev + 1);
};
const markAsRead = (id: string) => {
setNotifications((prev) =>
prev.map((n) => (n.id === id ? { ...n, isRead: true } : n))
);
// Kurangi unread count
setUnreadCount((prev) => Math.max(0, prev - 1));
};
const syncUnreadCount = async () => {
try {
const count = await apiGetNotificationsById({
id: user?.id as any,
category: "count-as-unread"
}); // ← harus return number
const result = count.data
setUnreadCount(result);
} catch (error) {
console.warn("⚠️ Gagal sync unread count:", error);
}
};
return (
<NotificationContext.Provider
value={{ notifications, unreadCount, addNotification, markAsRead, syncUnreadCount }}
>
{children}
</NotificationContext.Provider>
);
};
export const useNotificationStore = () => useContext(NotificationContext);

File diff suppressed because one or more lines are too long

View File

@@ -39,7 +39,7 @@
</dict> </dict>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>15</string> <string>19</string>
<key>ITSAppUsesNonExemptEncryption</key> <key>ITSAppUsesNonExemptEncryption</key>
<false/> <false/>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
@@ -55,6 +55,8 @@
</dict> </dict>
<key>NSCameraUsageDescription</key> <key>NSCameraUsageDescription</key>
<string>Allow $(PRODUCT_NAME) to access your camera</string> <string>Allow $(PRODUCT_NAME) to access your camera</string>
<key>NSFaceIDUsageDescription</key>
<string>Allow $(PRODUCT_NAME) to access your Face ID biometric data.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key> <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Allow $(PRODUCT_NAME) to access your location</string> <string>Allow $(PRODUCT_NAME) to access your location</string>
<key>NSLocationAlwaysUsageDescription</key> <key>NSLocationAlwaysUsageDescription</key>

View File

@@ -51,6 +51,7 @@ target 'HIPMIBadungConnect' do
:privacy_file_aggregation_enabled => podfile_properties['apple.privacyManifestAggregationEnabled'] != 'false', :privacy_file_aggregation_enabled => podfile_properties['apple.privacyManifestAggregationEnabled'] != 'false',
) )
pod 'Firebase'
pod 'Firebase/Messaging' pod 'Firebase/Messaging'
# @generated begin post_installer - expo prebuild (DO NOT MODIFY) sync-4092f82b887b5b9edb84642c2a56984d69b9a403 # @generated begin post_installer - expo prebuild (DO NOT MODIFY) sync-4092f82b887b5b9edb84642c2a56984d69b9a403

View File

@@ -279,31 +279,83 @@ PODS:
- EXUpdatesInterface (2.0.0): - EXUpdatesInterface (2.0.0):
- ExpoModulesCore - ExpoModulesCore
- FBLazyVector (0.81.4) - FBLazyVector (0.81.4)
- Firebase/CoreOnly (12.7.0): - Firebase (12.6.0):
- FirebaseCore (~> 12.7.0) - Firebase/Core (= 12.6.0)
- Firebase/Messaging (12.7.0): - Firebase/Core (12.6.0):
- Firebase/CoreOnly - Firebase/CoreOnly
- FirebaseMessaging (~> 12.7.0) - FirebaseAnalytics (~> 12.6.0)
- FirebaseCore (12.7.0): - Firebase/CoreOnly (12.6.0):
- FirebaseCoreInternal (~> 12.7.0) - FirebaseCore (~> 12.6.0)
- Firebase/Messaging (12.6.0):
- Firebase/CoreOnly
- FirebaseMessaging (~> 12.6.0)
- FirebaseAnalytics (12.6.0):
- FirebaseAnalytics/Default (= 12.6.0)
- FirebaseCore (~> 12.6.0)
- FirebaseInstallations (~> 12.6.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
- GoogleUtilities/MethodSwizzler (~> 8.1)
- GoogleUtilities/Network (~> 8.1)
- "GoogleUtilities/NSData+zlib (~> 8.1)"
- nanopb (~> 3.30910.0)
- FirebaseAnalytics/Default (12.6.0):
- FirebaseCore (~> 12.6.0)
- FirebaseInstallations (~> 12.6.0)
- GoogleAppMeasurement/Default (= 12.6.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
- GoogleUtilities/MethodSwizzler (~> 8.1)
- GoogleUtilities/Network (~> 8.1)
- "GoogleUtilities/NSData+zlib (~> 8.1)"
- nanopb (~> 3.30910.0)
- FirebaseCore (12.6.0):
- FirebaseCoreInternal (~> 12.6.0)
- GoogleUtilities/Environment (~> 8.1) - GoogleUtilities/Environment (~> 8.1)
- GoogleUtilities/Logger (~> 8.1) - GoogleUtilities/Logger (~> 8.1)
- FirebaseCoreInternal (12.7.0): - FirebaseCoreExtension (12.6.0):
- FirebaseCore (~> 12.6.0)
- FirebaseCoreInternal (12.6.0):
- "GoogleUtilities/NSData+zlib (~> 8.1)" - "GoogleUtilities/NSData+zlib (~> 8.1)"
- FirebaseInstallations (12.7.0): - FirebaseInstallations (12.6.0):
- FirebaseCore (~> 12.7.0) - FirebaseCore (~> 12.6.0)
- GoogleUtilities/Environment (~> 8.1) - GoogleUtilities/Environment (~> 8.1)
- GoogleUtilities/UserDefaults (~> 8.1) - GoogleUtilities/UserDefaults (~> 8.1)
- PromisesObjC (~> 2.4) - PromisesObjC (~> 2.4)
- FirebaseMessaging (12.7.0): - FirebaseMessaging (12.6.0):
- FirebaseCore (~> 12.7.0) - FirebaseCore (~> 12.6.0)
- FirebaseInstallations (~> 12.7.0) - FirebaseInstallations (~> 12.6.0)
- GoogleDataTransport (~> 10.1) - GoogleDataTransport (~> 10.1)
- GoogleUtilities/AppDelegateSwizzler (~> 8.1) - GoogleUtilities/AppDelegateSwizzler (~> 8.1)
- GoogleUtilities/Environment (~> 8.1) - GoogleUtilities/Environment (~> 8.1)
- GoogleUtilities/Reachability (~> 8.1) - GoogleUtilities/Reachability (~> 8.1)
- GoogleUtilities/UserDefaults (~> 8.1) - GoogleUtilities/UserDefaults (~> 8.1)
- nanopb (~> 3.30910.0) - nanopb (~> 3.30910.0)
- GoogleAdsOnDeviceConversion (3.2.0):
- GoogleUtilities/Environment (~> 8.1)
- GoogleUtilities/Logger (~> 8.1)
- GoogleUtilities/Network (~> 8.1)
- nanopb (~> 3.30910.0)
- GoogleAppMeasurement/Core (12.6.0):
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
- GoogleUtilities/MethodSwizzler (~> 8.1)
- GoogleUtilities/Network (~> 8.1)
- "GoogleUtilities/NSData+zlib (~> 8.1)"
- nanopb (~> 3.30910.0)
- GoogleAppMeasurement/Default (12.6.0):
- GoogleAdsOnDeviceConversion (~> 3.2.0)
- GoogleAppMeasurement/Core (= 12.6.0)
- GoogleAppMeasurement/IdentitySupport (= 12.6.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
- GoogleUtilities/MethodSwizzler (~> 8.1)
- GoogleUtilities/Network (~> 8.1)
- "GoogleUtilities/NSData+zlib (~> 8.1)"
- nanopb (~> 3.30910.0)
- GoogleAppMeasurement/IdentitySupport (12.6.0):
- GoogleAppMeasurement/Core (= 12.6.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
- GoogleUtilities/MethodSwizzler (~> 8.1)
- GoogleUtilities/Network (~> 8.1)
- "GoogleUtilities/NSData+zlib (~> 8.1)"
- nanopb (~> 3.30910.0)
- GoogleDataTransport (10.1.0): - GoogleDataTransport (10.1.0):
- nanopb (~> 3.30910.0) - nanopb (~> 3.30910.0)
- PromisesObjC (~> 2.4) - PromisesObjC (~> 2.4)
@@ -317,6 +369,9 @@ PODS:
- GoogleUtilities/Logger (8.1.0): - GoogleUtilities/Logger (8.1.0):
- GoogleUtilities/Environment - GoogleUtilities/Environment
- GoogleUtilities/Privacy - GoogleUtilities/Privacy
- GoogleUtilities/MethodSwizzler (8.1.0):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- GoogleUtilities/Network (8.1.0): - GoogleUtilities/Network (8.1.0):
- GoogleUtilities/Logger - GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib" - "GoogleUtilities/NSData+zlib"
@@ -2198,6 +2253,14 @@ PODS:
- ReactCommon/turbomodule/core - ReactCommon/turbomodule/core
- ReactNativeDependencies - ReactNativeDependencies
- Yoga - Yoga
- RNFBApp (23.7.0):
- Firebase/CoreOnly (= 12.6.0)
- React-Core
- RNFBMessaging (23.7.0):
- Firebase/Messaging (= 12.6.0)
- FirebaseCoreExtension
- React-Core
- RNFBApp
- RNGestureHandler (2.28.0): - RNGestureHandler (2.28.0):
- hermes-engine - hermes-engine
- RCTRequired - RCTRequired
@@ -2559,6 +2622,7 @@ DEPENDENCIES:
- ExpoWebBrowser (from `../node_modules/expo-web-browser/ios`) - ExpoWebBrowser (from `../node_modules/expo-web-browser/ios`)
- EXUpdatesInterface (from `../node_modules/expo-updates-interface/ios`) - EXUpdatesInterface (from `../node_modules/expo-updates-interface/ios`)
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
- Firebase
- Firebase/Messaging - Firebase/Messaging
- hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
- RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`) - RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`)
@@ -2633,6 +2697,8 @@ DEPENDENCIES:
- ReactNativeDependencies (from `../node_modules/react-native/third-party-podspecs/ReactNativeDependencies.podspec`) - ReactNativeDependencies (from `../node_modules/react-native/third-party-podspecs/ReactNativeDependencies.podspec`)
- "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)" - "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)"
- "RNDateTimePicker (from `../node_modules/@react-native-community/datetimepicker`)" - "RNDateTimePicker (from `../node_modules/@react-native-community/datetimepicker`)"
- "RNFBApp (from `../node_modules/@react-native-firebase/app`)"
- "RNFBMessaging (from `../node_modules/@react-native-firebase/messaging`)"
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`) - RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
- "rnmapbox-maps (from `../node_modules/@rnmapbox/maps`)" - "rnmapbox-maps (from `../node_modules/@rnmapbox/maps`)"
- RNReanimated (from `../node_modules/react-native-reanimated`) - RNReanimated (from `../node_modules/react-native-reanimated`)
@@ -2645,10 +2711,14 @@ DEPENDENCIES:
SPEC REPOS: SPEC REPOS:
trunk: trunk:
- Firebase - Firebase
- FirebaseAnalytics
- FirebaseCore - FirebaseCore
- FirebaseCoreExtension
- FirebaseCoreInternal - FirebaseCoreInternal
- FirebaseInstallations - FirebaseInstallations
- FirebaseMessaging - FirebaseMessaging
- GoogleAdsOnDeviceConversion
- GoogleAppMeasurement
- GoogleDataTransport - GoogleDataTransport
- GoogleUtilities - GoogleUtilities
- libavif - libavif
@@ -2878,6 +2948,10 @@ EXTERNAL SOURCES:
:path: "../node_modules/@react-native-async-storage/async-storage" :path: "../node_modules/@react-native-async-storage/async-storage"
RNDateTimePicker: RNDateTimePicker:
:path: "../node_modules/@react-native-community/datetimepicker" :path: "../node_modules/@react-native-community/datetimepicker"
RNFBApp:
:path: "../node_modules/@react-native-firebase/app"
RNFBMessaging:
:path: "../node_modules/@react-native-firebase/messaging"
RNGestureHandler: RNGestureHandler:
:path: "../node_modules/react-native-gesture-handler" :path: "../node_modules/react-native-gesture-handler"
rnmapbox-maps: rnmapbox-maps:
@@ -2929,11 +3003,15 @@ SPEC CHECKSUMS:
ExpoWebBrowser: b973e1351fdcf5fec0c400997b1851f5a8219ec3 ExpoWebBrowser: b973e1351fdcf5fec0c400997b1851f5a8219ec3
EXUpdatesInterface: 5adf50cb41e079c861da6d9b4b954c3db9a50734 EXUpdatesInterface: 5adf50cb41e079c861da6d9b4b954c3db9a50734
FBLazyVector: 9e0cd874afd81d9a4d36679daca991b58b260d42 FBLazyVector: 9e0cd874afd81d9a4d36679daca991b58b260d42
Firebase: 2d19a10c9a2e48ac532a4303115d3fc9b2798396 Firebase: a451a7b61536298fd5cbfe3a746fd40443a50679
FirebaseCore: c7b57863ce0859281a66d16ca36d665c45d332b5 FirebaseAnalytics: d0a97a0db6425e5a5d966340b87f92ca7b13a557
FirebaseCoreInternal: 571a2dd8c975410966199623351db3a3265c874d FirebaseCore: 0e38ad5d62d980a47a64b8e9301ffa311457be04
FirebaseInstallations: 6d05424a046b68ca146b4de4376f05b4e9262fc3 FirebaseCoreExtension: 032fd6f8509e591fda8cb76f6651f20d926b121f
FirebaseMessaging: b5f7bdc62b91b6102015991fb7bc6fa75f643908 FirebaseCoreInternal: 69bf1306a05b8ac43004f6cc1f804bb7b05b229e
FirebaseInstallations: 631b38da2e11a83daa4bfb482f79d286a5dfa7ad
FirebaseMessaging: a61bc42dcab3f7a346d94bbb54dab2c9435b18b2
GoogleAdsOnDeviceConversion: d68c69dd9581a0f5da02617b6f377e5be483970f
GoogleAppMeasurement: 3bf40aff49a601af5da1c3345702fcb4991d35ee
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
hermes-engine: 35c763d57c9832d0eef764316ca1c4d043581394 hermes-engine: 35c763d57c9832d0eef764316ca1c4d043581394
@@ -3016,6 +3094,8 @@ SPEC CHECKSUMS:
ReactNativeDependencies: ed6d1e64802b150399f04f1d5728ec16b437251e ReactNativeDependencies: ed6d1e64802b150399f04f1d5728ec16b437251e
RNCAsyncStorage: 3a4f5e2777dae1688b781a487923a08569e27fe4 RNCAsyncStorage: 3a4f5e2777dae1688b781a487923a08569e27fe4
RNDateTimePicker: be0e44bcb9ed0607c7c5f47dbedd88cf091f6791 RNDateTimePicker: be0e44bcb9ed0607c7c5f47dbedd88cf091f6791
RNFBApp: 0c4cadae3900893d4631ea9a9effd14f853cd8e0
RNFBMessaging: 873220424f6f6aa5c787baf5c7099e1e2ba087c9
RNGestureHandler: 2914750df066d89bf9d8f48a10ad5f0051108ac3 RNGestureHandler: 2914750df066d89bf9d8f48a10ad5f0051108ac3
rnmapbox-maps: 3c20ce786a7991498445c32de4fe4244e32aa0ee rnmapbox-maps: 3c20ce786a7991498445c32de4fe4244e32aa0ee
RNReanimated: 8d3a14606ad49f022c17d9e12a2d339e9e5ad9b0 RNReanimated: 8d3a14606ad49f022c17d9e12a2d339e9e5ad9b0
@@ -3031,6 +3111,6 @@ SPEC CHECKSUMS:
Yoga: 051f086b5ccf465ff2ed38a2cf5a558ae01aaaa1 Yoga: 051f086b5ccf465ff2ed38a2cf5a558ae01aaaa1
ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5 ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5
PODFILE CHECKSUM: 61c4a9d35618e5f2a420f47e5a24c89e86706f00 PODFILE CHECKSUM: 9c1ecbc7e57ca21dc7c93635b18e66f8f6d7bdd9
COCOAPODS: 1.16.2 COCOAPODS: 1.16.2

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

@@ -75,6 +75,7 @@
"devDependencies": { "devDependencies": {
"@babel/core": "^7.25.2", "@babel/core": "^7.25.2",
"@react-native-community/cli": "^20.0.2", "@react-native-community/cli": "^20.0.2",
"@react-native/metro-config": "^0.83.1",
"@types/react": "~19.1.10", "@types/react": "~19.1.10",
"eslint": "^9.25.0", "eslint": "^9.25.0",
"eslint-config-expo": "~10.0.0", "eslint-config-expo": "~10.0.0",

View File

@@ -0,0 +1,41 @@
// components/HeaderBell.tsx
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { useNotificationStore } from "@/hooks/use-notification-store";
import { Ionicons } from "@expo/vector-icons";
import { router } from "expo-router";
import { Text, View } from "react-native";
export default function AdminNotificationBell() {
const { unreadCount } = useNotificationStore();
return (
<View style={{ position: "relative" }}>
<Ionicons
name="notifications"
size={ICON_SIZE_SMALL}
color={MainColor.white}
/>
{unreadCount > 0 && (
<View
style={{
position: "absolute",
top: -4,
right: -4,
backgroundColor: "red",
borderRadius: 8,
minWidth: 16,
height: 16,
justifyContent: "center",
alignItems: "center",
paddingHorizontal: 2,
}}
>
<Text style={{ color: "white", fontSize: 10, fontWeight: "bold" }}>
{unreadCount > 9 ? "9+" : unreadCount}
</Text>
</View>
)}
</View>
);
}

View File

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

View File

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

View File

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

View 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",
},
});

View File

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

View File

@@ -0,0 +1,48 @@
// components/HeaderBell.tsx
import { MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import { useNotificationStore } from "@/hooks/use-notification-store";
import { Ionicons } from "@expo/vector-icons";
import { router } from "expo-router";
import { Text, View } from "react-native";
export default function HeaderBell() {
const { unreadCount } = useNotificationStore();
const { user } = useAuth();
const pathDetector =
user?.masterUserRoleId === "1" ? "/notifications" : "/admin/notification";
return (
<View style={{ position: "relative" }}>
<Ionicons
name="notifications"
size={20}
color={MainColor.yellow}
onPress={() => {
router.push(pathDetector);
}}
/>
{unreadCount > 0 && (
<View
style={{
position: "absolute",
top: -4,
right: -4,
backgroundColor: "red",
borderRadius: 8,
minWidth: 16,
height: 16,
justifyContent: "center",
alignItems: "center",
paddingHorizontal: 2,
}}
>
<Text style={{ color: "white", fontSize: 10, fontWeight: "bold" }}>
{unreadCount > 9 ? "9+" : unreadCount}
</Text>
</View>
)}
</View>
);
}

View File

@@ -17,7 +17,12 @@ export default function Home_BottomFeatureSection() {
}); });
// console.log("[DATA JOB]", JSON.stringify(response.data, null, 2)); // 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); setListData(result);
} catch (error) { } catch (error) {
console.log("[ERROR]", error); console.log("[ERROR]", error);

View File

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

View File

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

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

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 { 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; return response.data;
} catch (error) { } catch (error) {
throw 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 { try {
const response = await apiConfig.put(`/mobile/admin/forum/${id}`); const response = await apiConfig.put(`/mobile/admin/forum/${id}`, {
data: data,
});
return response.data; return response.data;
} catch (error) { } catch (error) {
throw error; throw error;

View File

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

View File

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

View File

@@ -119,3 +119,62 @@ export async function apiForumDeleteComment({ id }: { id: string }) {
throw error; 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;
}
}

View File

@@ -77,46 +77,6 @@ export async function apiMasterForumReportList() {
// ================== END MASTER FORUM ================== // // ================== END MASTER FORUM ================== //
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;
}
}
// ================== START MASTER INVESTMENT ================== // // ================== START MASTER INVESTMENT ================== //
export async function apiMasterInvestment({ export async function apiMasterInvestment({

View File

@@ -74,3 +74,10 @@ export async function apiAcceptTermService({
return response.data; return response.data;
} }
export async function apiUpdatedTermCondition({nomor}: {nomor: string}) {
const response = await apiConfig.post(`/auth/mobile-eula`, {
nomor: nomor,
});
return response.data;
}

View File

@@ -0,0 +1,51 @@
import { apiConfig } from "./api-config";
type DeviceTokenData = {
fcmToken: string;
platform: string;
deviceId: string;
model: string;
appVersion: string;
userId: string;
};
export async function apiDeviceRegisterToken({
data,
}: {
data: DeviceTokenData;
}) {
try {
const response = await apiConfig.post(`/mobile/auth/device-tokens`, {
data: data,
});
return response.data;
} catch (error) {
console.error("Failed to register device token:", error);
throw error;
}
}
export async function apiDeviceTokenDeleted({ userId, deviceId }: { userId: string, deviceId: string }) {
try {
const response = await apiConfig.delete(
`/mobile/auth/device-tokens/${userId}?deviceId=${deviceId}`
);
return response.data;
} catch (error) {
console.error("Failed to delete device token:", error);
throw error;
}
}
export async function apiGetAllTokenDevice() {
try {
const response = await apiConfig.get(`/mobile/auth/device-tokens`);
return response.data;
} catch (error) {
console.error("Failed to delete device token:", error);
throw error;
}
}

View File

@@ -0,0 +1,101 @@
import {
NotificationProp,
TypeNotificationCategoryApp,
} from "@/types/type-notification-category";
import { apiConfig } from "./api-config";
export async function apiNotificationsSend({
data,
}: {
data: NotificationProp;
}) {
try {
const response = await apiConfig.post(`/mobile/notification`, {
data: data,
});
return response.data;
} catch (error) {
throw error;
}
}
export async function apiNotificationsSendById({
data,
id,
}: {
data: NotificationProp;
id: string;
}) {
try {
const response = await apiConfig.post(`/mobile/notification/${id}`, {
data: data,
});
return response.data;
} catch (error) {
throw error;
}
}
export async function apiGetNotificationsById({
id,
category,
}: {
id: string;
category: TypeNotificationCategoryApp;
}) {
console.log("ID", id);
console.log("Category", category);
try {
const response = await apiConfig.get(
`/mobile/notification/${id}?category=${category}`
);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiNotificationUnreadCount({
id,
role,
}: {
id: string;
role: "user" | "admin";
}) {
try {
const response = await apiConfig.get(
`/mobile/notification/${id}/unread-count?role=${role}`
);
console.log("Response Unread Count", response.data);
return response.data;
} catch (error) {
throw error;
}
}
/**
* @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}?category=${category}`
);
return response.data;
} catch (error) {
throw error;
}
}

View File

@@ -0,0 +1,4 @@
export type typeRejectedData = {
catatan?: string;
senderId: string;
};

15
types/type-forum.ts Normal file
View 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;
};
};
}

View File

@@ -0,0 +1,35 @@
export type NotificationProp = {
title: TypeOfTilteCategoryApp | string
body: string;
userLoginId?: string;
appId?: string;
status?: string;
type?: "announcement" | "trigger";
deepLink?: string;
kategoriApp?: TypeNotificationCategoryApp
};
export type TypeNotificationCategoryApp =
| "EVENT"
| "JOB"
| "VOTING"
| "DONASI"
| "INVESTASI"
| "COLLABORATION"
| "FORUM"
| "OTHER";
export type TypeOfTilteCategoryApp = "Pendaftaran User Baru" | "Other"
export const listOfcategoriesAppNotification = [
{ value: "event", label: "Event" },
{ value: "job", label: "Job" },
{ value: "voting", label: "Voting" },
{ value: "donasi", label: "Donasi" },
{ value: "investasi", label: "Investasi" },
{ value: "forum", label: "Forum" },
{ value: "collaboration", label: "Collaboration" },
{ value: "other", label: "Lainnya" },
];

View File

@@ -5,11 +5,11 @@ const badWordsIndonesia = [
'anjing', 'babi', 'bangsat', 'bodoh', 'goblok', 'idiot', 'jancok', 'jembut', 'kampret', 'anjing', 'babi', 'bangsat', 'bodoh', 'goblok', 'idiot', 'jancok', 'jembut', 'kampret',
'kontol', 'memek', 'ngentot', 'peler', 'puki', 'sialan', 'tai', 'tolol', 'wibu', 'kontol', 'memek', 'ngentot', 'peler', 'puki', 'sialan', 'tai', 'tolol', 'wibu',
'anjingg', 'babbii', 'bangsaat', 'gobllokk', 'jancokk', 'kontoll', 'memekk', 'ngentott', 'anjingg', 'babbii', 'bangsaat', 'gobllokk', 'jancokk', 'kontoll', 'memekk', 'ngentott',
'pelerr', 'puuki', 'sialann', 'taii', 'tololl', 'wibuu', 'pelerr', 'puuki', 'sialann', 'taii', 'tololl', 'wibuu', 'cicing',
// 🔥 Kata Sindiran & Penghinaan // 🔥 Kata Sindiran & Penghinaan
'bego', 'dungu', 'edan', 'gila', 'goblog', 'kampang', 'kampret', 'keparat', 'lonte', 'bego', 'dungu', 'edan', 'gila', 'goblog', 'kampang', 'kampret', 'keparat', 'lonte',
'main mata', 'monyet', 'najis', 'ngeyel', 'ngibul', 'ngomong seenaknya', 'ngurangin', 'monyet', 'najis', 'ngeyel', 'ngibul', 'ngomong seenaknya', 'ngurangin',
'ngutang', 'ngurusin urusan orang', 'pemalas', 'pengecut', 'penipu', 'sinting', 'ngutang', 'ngurusin urusan orang', 'pemalas', 'pengecut', 'penipu', 'sinting',
'begoo', 'dunguu', 'goblogg', 'kampangg', 'keparatt', 'lontee', 'monyyet', 'najiss', 'begoo', 'dunguu', 'goblogg', 'kampangg', 'keparatt', 'lontee', 'monyyet', 'najiss',
'ngeyell', 'ngibull', 'ngomongg seenaknya', 'nguranginn', 'ngutangg', 'pemalass', 'ngeyell', 'ngibull', 'ngomongg seenaknya', 'nguranginn', 'ngutangg', 'pemalass',
@@ -23,18 +23,17 @@ const badWordsIndonesia = [
// 💸 Kata Spam / Promosi Ilegal // 💸 Kata Spam / Promosi Ilegal
'judi', 'togel', 'slot', 'casino', 'poker', 'qq', 'bandar', 'agen', 'link', 'wa', 'judi', 'togel', 'slot', 'casino', 'poker', 'qq', 'bandar', 'agen', 'link', 'wa',
'whatsapp', 'telepon', 'nomor', 'hp', 'sms', 'grup', 'join', 'daftar', 'bonus', 'deposit', 'withdraw',
'deposit', 'withdraw', 'uang', 'duit', 'rp', 'ratusan', 'juta', 'milyar',
'judii', 'togell', 'slotss', 'casinoo', 'pokerr', 'qqq', 'bandarr', 'agenn', 'linkk', 'judii', 'togell', 'slotss', 'casinoo', 'pokerr', 'qqq', 'bandarr', 'agenn', 'linkk',
'waa', 'whatsappp', 'teleponn', 'nomorr', 'hpp', 'smss', 'grupp', 'jooin', 'daftarr', 'waa',
'bonuss', 'depositt', 'withdraww', 'uangs', 'duitt', 'rpp', 'ratusann', 'jutaa', 'milyarr', 'depositt', 'withdraww', 'rpp',
// 🧩 Variasi Penulisan (Bypass Filter) // 🧩 Variasi Penulisan (Bypass Filter)
'a*njing', 'b*b*i', 'b*ngsat', 'g*blok', 'k*nt*l', 'm*m*k', 'n*g*nt*t', 'p*l*r', 'a*njing', 'b*b*i', 'b*ngsat', 'g*blok', 'k*nt*l', 'm*m*k', 'n*g*nt*t', 'p*l*r',
't*i', 't*l*l', 'j*n*c*k', 'j*m*b*t', 'k*m*p*r*t', 's*i*l*a*n', 'w*b*u', 't*i', 't*l*l', 'j*n*c*k', 'j*m*b*t', 'k*m*p*r*t', 's*i*l*a*n', 'w*b*u',
'a.n.j.i.n.g', 'b.a.b.i', 'b.a.n.g.s.a.t', 'g.o.b.l.o.k', 'k.o.n.t.o.l', 'm.e.m.e.k', 'a.n.j.i.n.g', 'b.a.b.i', 'b.a.n.g.s.a.t', 'g.o.b.l.o.k', 'k.o.n.t.o.l', 'm.e.m.e.k',
'n.g.e.n.t.o.t', 'p.e.l.e.r', 't.a.i', 't.o.l.o.l', 'j.a.n.c.o.k', 'j.e.m.b.u.t', 'n.g.e.n.t.o.t', 'p.e.l.e.r', 't.a.i', 't.o.l.o.l', 'j.a.n.c.o.k', 'j.e.m.b.u.t',
'k.a.m.p.r.e.t', 's.i.a.l.a.n', 'w.i.b.u', 'k.a.m.p.r.e.t', 's.i.a.l.a.n', 'w.i.b.u', 'c.i.c.i.n.g',
// 📱 Variasi dengan Angka & Simbol // 📱 Variasi dengan Angka & Simbol
'4nj1ng', 'b4b1', 'b4ngs4t', 'g0bl0k', 'k0nt0l', 'm3m3k', 'ng3nt0t', 'p3l3r', '4nj1ng', 'b4b1', 'b4ngs4t', 'g0bl0k', 'k0nt0l', 'm3m3k', 'ng3nt0t', 'p3l3r',
@@ -43,8 +42,6 @@ const badWordsIndonesia = [
'p3l3rr', 't4ii', 't0l0ll', 'j4nc0kk', 'j3mbutt', 'k4mpr3tt', 's14l4nn', 'w1buu', 'p3l3rr', 't4ii', 't0l0ll', 'j4nc0kk', 'j3mbutt', 'k4mpr3tt', 's14l4nn', 'w1buu',
// 🗣️ Kata yang Sering Digunakan dalam Konteks Negatif // 🗣️ Kata yang Sering Digunakan dalam Konteks Negatif
'dasar', 'kamu', 'kau', 'lu', 'lo', 'gue', 'gua', 'kita', 'kami', 'mereka',
'dasarr', 'kamuu', 'kauu', 'luu', 'loo', 'guee', 'guua', 'kitaa', 'kamii', 'merekaa',
'dasar bodoh', 'dasar goblok', 'dasar bangsat', 'dasar idiot', 'dasar sialan', 'dasar bodoh', 'dasar goblok', 'dasar bangsat', 'dasar idiot', 'dasar sialan',
'dasar bego', 'dasar dungu', 'dasar edan', 'dasar gila', 'dasar sinting', 'dasar bego', 'dasar dungu', 'dasar edan', 'dasar gila', 'dasar sinting',
'dasar pemalas', 'dasar pengecut', 'dasar penipu', 'dasar najis', 'dasar kampret', 'dasar pemalas', 'dasar pengecut', 'dasar penipu', 'dasar najis', 'dasar kampret',

View File

@@ -15,21 +15,21 @@ export const formatChatTime = (date: string | Date): string => {
const messageDate = dayjs(date); const messageDate = dayjs(date);
const now = dayjs(); const now = dayjs();
// Jika hari ini // Hari ini
if (messageDate.isSame(now, 'day')) { 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')) { 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) { 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 // Lebih dari 7 hari lalu
return messageDate.format('D MMM YYYY'); // contoh: "12 Mei 2024" return messageDate.format('DD - MM - YYYY, HH.mm'); // "05 - 11 - 2025, 14.00"
}; };