Compare commits

...

45 Commits

Author SHA1 Message Date
17396cea68 Merge pull request 'clean-code/11-mar-26' (#62) from clean-code/11-mar-26 into staging
Reviewed-on: #62
2026-03-12 10:58:40 +08:00
57c9215771 Feat validasi mobile otp
modified:   context/AuthContext.tsx
        modified:   screens/Authentication/VerificationView.tsx
        modified:   service/api-config.ts

### No Issue
2026-03-11 15:04:32 +08:00
4efdbd3c7b feat: update admin features, user confirmation, and native configs
- Admin: Update layout, notification bell, and event detail screen
- User: Improve event confirmation flow
- Config: Update AndroidManifest, Info.plist, entitlements, and app.config.js

### No Issue
2026-03-11 11:29:20 +08:00
ad32eb6fe6 feat: implement deep linking & universal links for event confirmation
- Add QR code toggle for HTTPS/Custom Scheme links
- Add Universal Links (iOS) and App Links (Android) support
- Create deep link route handler with platform detection
- Add .well-known files for domain verification
- Fix Content-Type headers for apple-app-site-association
- Add environment configuration for staging & production
- Add comprehensive testing documentation

Testing:
- QR scan → Safari → App switch working 
- Platform detection working 
- Auto redirect to custom scheme working 
- Web fallback JSON response working 

### No Issue
2026-03-09 16:39:01 +08:00
fa4c9005de Merge pull request 'add: Admin Event detail screen dan komponen pendukung' (#61) from clean-code/6-mar-26 into staging
Reviewed-on: #61
2026-03-06 16:53:44 +08:00
a5026cc285 add: Admin Event detail screen dan komponen pendukung
Deskripsi:

Menambahkan halaman detail event pada admin panel dengan status parameter

Menambahkan beberapa komponen UI untuk menampilkan detail event, drawer informasi, dan QR Code

Update konfigurasi aplikasi dan iOS project

Perbaikan pada halaman verifikasi authentication

Update dokumentasi prompt untuk Qwen

File yang diubah:

Modified

app.config.js

app/(application)/admin/event/[id]/[status]/index.tsx

docs/prompt-for-qwen-code.md

ios/HIPMIBadungConnect.xcodeproj/project.pbxproj

ios/HIPMIBadungConnect/Info.plist

screens/Authentication/VerificationView.tsx

New Admin Event Components

screens/Admin/Event/BoxEventDetail.tsx

screens/Admin/Event/EventDetailDrawer.tsx

screens/Admin/Event/EventDetailQRCode.tsx

screens/Admin/Event/ScreenEventDetail.tsx

### No Issue
2026-03-06 16:39:55 +08:00
c86a3f0ffa Merge pull request 'Fix bug home' (#60) from fix-bug/5-mar-26 into staging
Reviewed-on: #60
2026-03-05 16:45:48 +08:00
836ef709d2 Fix bug home
### No Issue
2026-03-05 16:41:29 +08:00
c2e6ce7b06 Merge pull request 'fix-bug' (#59) from fix-bug/4-mar-26 into staging
Reviewed-on: #59
2026-03-04 16:46:31 +08:00
3bbee15c3a Perbaikan Bug & Error Handling: │
│                                                                                                            │
  │    1. Device Token Registration Error (HTTP 500)                                                           │
  │       - File: service/api-device-token.ts                                                                  │
  │       - Fix: Hapus nested data wrapper pada payload                                                        │
  │       - Improvement: Tambahkan error logging detail                                                        │
  │                                                                                                            │
  │    2. Uncaught Promise Errors                                                                              │
  │       - File: components/Notification/NotificationInitializer.tsx                                          │
  │       - Fix: Better error handling untuk device token registration                                         │
  │       - File: app/(application)/(user)/home.tsx                                                            │
  │       - Fix: Add .catch() untuk userData() dan error handling apiUser()                                    │
  │       - File: app/(application)/(user)/profile/[id]/index.tsx                                              │
  │       - Fix: Add error handling untuk apiProfile(), apiUser(), userData()                                  │
  │                                                                                                            │
  │    3. UI Improvements                                                                                      │
  │       - File: app/(application)/(user)/home.tsx                                                            │
  │       - Feature: 4 skeleton lingkaran untuk loading state grid features                                    │
  │                                                                                                            │
  │    4. Maps Migration                                                                                       │
  │       - File: app/(application)/admin/maps.tsx                                                             │
  │       - Change: Replace react-native-maps dengan MapsV2Custom (Maplibre)                                   │
  │       - Cleanup: Hapus unused imports dan interfaces                                                       │
  │                                                                                                            │
  │   Files Modified (7)                                                                                       │
  │    - app/(application)/(user)/home.tsx                                                                     │
  │    - app/(application)/(user)/profile/[id]/index.tsx                                                       │
  │    - app/(application)/admin/maps.tsx                                                                      │
  │    - components/Notification/NotificationInitializer.tsx                                                   │
  │    - service/api-device-token.ts                                                                           │
  │    - constants/constans-value.ts                                                                           │
  │    - screens/Home/bottomFeatureSection.tsx                                                                 │
  │    - screens/UserSeach/MainView_V2.tsx

### No Issue
2026-03-04 16:39:57 +08:00
b1a5be5105 Merge pull request 'fix-maps dan bug' (#58) from fix-maps/2-mar-26 into staging
Reviewed-on: #58
2026-03-02 16:35:45 +08:00
9e902cec6e Merge pull request 'fix-maps' (#57) from fix-maps/26-feb-26 into staging
Reviewed-on: #57
2026-02-26 18:11:13 +08:00
088c46de3e Merge pull request 'fix-ios/25-feb-26' (#56) from fix-ios/25-feb-26 into staging
Reviewed-on: #56
2026-02-25 16:53:06 +08:00
52d8e91eb2 Merge pull request 'fixed-admin/18-feb-26' (#55) from fixed-admin/18-feb-26 into staging
Reviewed-on: #55
2026-02-18 17:35:18 +08:00
064cf18cba Merge pull request 'fixed-admin' (#54) from fixed-admin/14-feb-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/54
2026-02-14 16:26:43 +08:00
435c5834e9 Merge pull request 'Fix Admin' (#53) from fixed-admin/13-feb-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/53
2026-02-13 17:42:40 +08:00
155cfae331 Merge pull request 'fixed-admin' (#52) from fixed-admin/12-feb-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/52
2026-02-12 17:46:35 +08:00
415243ec6d Merge pull request 'Saya telah melakukan serangkaian perubahan penting dalam pengembangan aplikasi HIPMI Mobile, khususnya dalam modul' (#51) from loaddata/10-feb-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/51
2026-02-10 17:35:05 +08:00
2f2b5a460f Merge pull request 'loaddata/9-feb-26' (#50) from loaddata/9-feb-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/50
2026-02-09 17:40:13 +08:00
b5b4e0816c Merge pull request 'Fix Loaddata Invesment & Clearing code' (#49) from loaddata/6-feb-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/49
2026-02-06 17:29:14 +08:00
32eee66a41 Merge pull request 'loaddata & fix wrapper ui' (#48) from loaddata/5-feb-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/48
2026-02-05 17:37:28 +08:00
3ad8fba6d2 Merge pull request 'loaddata/4-feb-26' (#47) from loaddata/4-feb-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/47
2026-02-05 10:10:25 +08:00
a7022e5afd Merge pull request 'loaddata event' (#46) from loaddata/3-feb-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/46
2026-02-04 10:45:04 +08:00
0cacb5f7de Merge pull request 'Fix Load data pada halaman yang membutuhkan infinite load' (#45) from loaddata/2-feb-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/45
2026-02-02 17:12:28 +08:00
03f53d7b48 Merge pull request 'loaddata/30-jan-26' (#44) from loaddata/30-jan-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/44
2026-01-30 17:21:49 +08:00
c4859b7e82 Merge pull request 'Donation – User' (#43) from notification/27-jan-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/43
2026-01-27 17:44:27 +08:00
e4a66f3bc3 Merge pull request 'notification donation & EULA Logic' (#42) from notification/23-jan-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/42
2026-01-23 17:14:30 +08:00
6d352dd091 Merge pull request 'notification invesment' (#41) from notification/21-jan-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/41
2026-01-21 15:42:52 +08:00
0f62243410 Merge pull request 'notification forum' (#40) from notification/19-jan-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/40
2026-01-20 10:41:06 +08:00
4f32d522a4 Merge pull request 'notification event dan voting' (#39) from notification/15-jan-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/39
2026-01-15 17:42:33 +08:00
1357dff2e3 Merge pull request 'Penerapan Notif & Resolve bug' (#38) from notification/14-jan-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/38
2026-01-14 17:42:33 +08:00
0ba77e5160 Merge pull request 'Perbaikan untuk apple store dan push notifikasi' (#37) from notification/12-jan-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/37
2026-01-12 17:42:12 +08:00
186c667016 Merge pull request 'notification job & EULA metode' (#36) from notification/9-jan-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/36
2026-01-09 17:48:42 +08:00
16f4bf0e62 Merge pull request 'Fix job notifikasi' (#35) from notification/7-jan-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/35
2026-01-08 15:26:01 +08:00
d66dab1a6f Merge pull request 'notification' (#34) from notification/6-jan-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/34
2026-01-06 17:49:21 +08:00
e9b6c54c6e Merge pull request 'Notifikasi : Foreground & Background' (#33) from notification/24-dec-25 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/33
2025-12-24 17:46:13 +08:00
f201f61ce1 Merge pull request 'Filter console dan clean code' (#32) from notification/17-dec-25 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/32
2025-12-18 11:22:22 +08:00
e407598c7b Merge pull request 'Penerapan ke database' (#31) from notification/16-dec-25 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/31
2025-12-16 18:01:25 +08:00
c8e2119e99 Merge pull request 'Percobaan notfikasi' (#30) from notification/15-dec-25 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/30
2025-12-15 17:51:41 +08:00
a287d5545f Merge pull request 'notification/12-dec-25' (#29) from notification/12-dec-25 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/29
2025-12-12 17:51:58 +08:00
12034ebbfb Merge pull request 'Delete Account' (#15) from delete-account/19-nov-25 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/15
2025-11-19 17:47:52 +08:00
359daf4e4b Merge pull request 'Try to notification' (#13) from qc/18-nov-25 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/13
2025-11-18 17:48:03 +08:00
4f0cd3df02 Merge pull request 'Fix rejected apple delete account & start for notification' (#12) from qc/18-nov-25 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/12
2025-11-18 15:14:10 +08:00
a614cfaac9 Merge pull request 'QR Code & Notification' (#10) from qc/14-nov-25 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/10
2025-11-14 17:45:29 +08:00
1a7ad58505 Merge pull request 'QR Code Scan' (#9) from qrcode-access/13-nov-25 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/9
2025-11-13 17:43:08 +08:00
23 changed files with 1046 additions and 444 deletions

View File

@@ -37,7 +37,7 @@
</intent-filter>
<intent-filter android:autoVerify="true" data-generated="true">
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="https" android:host="cld-dkr-staging-hipmi.wibudev.com" android:pathPrefix="/"/>
<data android:scheme="https" android:host="cld-dkr-hipmi-stg.wibudev.com" android:pathPrefix="/"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>

View File

@@ -1,6 +1,17 @@
// app.config.js
require("dotenv").config();
// const isDev = process.env.NODE_ENV === "development";
// const isStaging = process.env.NEXT_PUBLIC_ENV === "staging";
// const isProd = process.env.NEXT_PUBLIC_ENV === "production";
// Domain berdasarkan environment
// const domain = isDev
// ? "localhost:3000"
// : isStaging
// ? "cld-dkr-hipmi-stg.wibudev.com"
// : "hipmi.muku.id"; // Production domain
export default {
name: "HIPMI Badung Connect",
slug: "hipmi-mobile",
@@ -20,8 +31,10 @@ export default {
NSLocationWhenInUseUsageDescription:
"Aplikasi membutuhkan akses lokasi untuk menampilkan peta.",
},
associatedDomains: ["applinks:cld-dkr-staging-hipmi.wibudev.com"],
buildNumber: "2",
associatedDomains: [
"applinks:cld-dkr-hipmi-stg.wibudev.com",
],
buildNumber: "4",
},
android: {
@@ -41,7 +54,7 @@ export default {
data: [
{
scheme: "https",
host: "cld-dkr-staging-hipmi.wibudev.com",
host: "cld-dkr-hipmi-stg.wibudev.com",
pathPrefix: "/",
},
],

View File

@@ -9,7 +9,7 @@ import {
TextCustom,
ViewWrapper,
} from "@/components";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import {
apiEventConfirmationAction,
@@ -60,7 +60,7 @@ export default function UserEventConfirmation() {
useFocusEffect(
useCallback(() => {
checkTokenAndDataParticipants() || console.log("Token is null");
}, [token, id, user?.id])
}, [token, id, user?.id]),
);
const checkTokenAndDataParticipants = async () => {
@@ -113,7 +113,7 @@ export default function UserEventConfirmation() {
confirmationStart,
confirmationEnd,
null,
"[]"
"[]",
);
// --- [4] Status waktu event (untuk pesan UI) ---
@@ -218,9 +218,14 @@ export default function UserEventConfirmation() {
if (isWithinConfirmationWindow) {
if (konfirmasi === false) {
return (
<TamplateBox data={data}>
<TamplateText text="Konfirmasi Kehadiran" />
</TamplateBox>
// <TamplateBox data={data}>
// <TamplateText text="Konfirmasi Kehadiran" />
// </TamplateBox>
<UserParticipan_And_DuringEvent
id={data.id}
userId={user?.id as string}
data={data}
/>
);
}
return (
@@ -261,17 +266,15 @@ export default function UserEventConfirmation() {
<Stack.Screen
options={{
title: "Konfirmasi Event",
// headerLeft: () => (
// <Ionicons
// name="arrow-back"
// size={20}
// color={MainColor.yellow}
// onPress={() =>
// router.navigate("/(application)/(user)/event/create")
// }
// />
// ),
}}
headerLeft: () => (
<Ionicons
name="arrow-back"
size={20}
color={MainColor.yellow}
onPress={() => router.navigate("/")}
/>
),
}}
/>
<ViewWrapper>{handlerReturn()}</ViewWrapper>
</>
@@ -497,7 +500,6 @@ const UserNotParticipan_And_DuringEvent = ({
);
};
// 🟡 ZONA ACARA BERLANGSUN
// User sudah terdaftar & Event sedang berlangsung & user harus konfirmasi
const UserParticipan_And_DuringEvent = ({

View File

@@ -12,23 +12,26 @@ import Home_ImageSection from "@/screens/Home/imageSection";
import TabSection from "@/screens/Home/tabSection";
import { tabsHome } from "@/screens/Home/tabsList";
import Home_FeatureSection from "@/screens/Home/topFeatureSection";
import { apiJobGetAll } from "@/service/api-client/api-job";
import { apiUser } from "@/service/api-client/api-user";
import { apiVersion } from "@/service/api-config";
import { GStyles } from "@/styles/global-styles";
import { Ionicons } from "@expo/vector-icons";
import { Redirect, router, Stack, useFocusEffect } from "expo-router";
import { useCallback, useState } from "react";
import { RefreshControl, TouchableOpacity, View } from "react-native";
import { RefreshControl, View } from "react-native";
export default function Application() {
const { token, user, userData } = useAuth();
const [data, setData] = useState<any>();
const [refreshing, setRefreshing] = useState(false);
const { syncUnreadCount } = useNotificationStore();
const [listData, setListData] = useState<any[] | null>(null);
useFocusEffect(
useCallback(() => {
onLoadData();
onLoadDataJob();
checkVersion();
userData(token as string).catch((error) => {
console.log("[ERROR userData]", error?.message);
@@ -50,6 +53,22 @@ export default function Application() {
}
}
const onLoadDataJob = async () => {
try {
const response = await apiJobGetAll({
category: "beranda",
});
const result = response.data
.sort(
(a: any, b: any) =>
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(),
)
.slice(0, 2);
setListData(result);
} catch (error) {
console.log("[ERROR]", error);
}
};
const checkVersion = async () => {
try {
const response = await apiVersion();
@@ -62,12 +81,13 @@ export default function Application() {
const onRefresh = useCallback(() => {
setRefreshing(true);
onLoadData();
onLoadDataJob();
checkVersion();
setRefreshing(false);
}, []);
if (data && data?.active === false) {
console.log("User is not active");
console.warn("User is not active");
return (
<BasicWrapper>
<Redirect href={`/waiting-room`} />
@@ -76,7 +96,7 @@ export default function Application() {
}
if (data && data?.Profile === null) {
console.log("Profile is null");
console.warn("Profile is null");
return (
<BasicWrapper>
<Redirect href={`/profile/create`} />
@@ -170,7 +190,7 @@ export default function Application() {
)}
{data ? (
<Home_BottomFeatureSection />
<Home_BottomFeatureSection listData={listData} />
) : (
<CustomSkeleton height={200} />
)}

View File

@@ -1,7 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { LoaderCustom, StackCustom } from "@/components";
import { NewWrapper, StackCustom } from "@/components";
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import LeftButtonCustom from "@/components/Button/BackButton";
import DrawerCustom from "@/components/Drawer/DrawerCustom";
import { MainColor } from "@/constants/color-palet";
@@ -17,8 +16,8 @@ import { GStyles } from "@/styles/global-styles";
import { IProfile } from "@/types/Type-Profile";
import { Ionicons } from "@expo/vector-icons";
import { Stack, useFocusEffect, useLocalSearchParams } from "expo-router";
import React, { useCallback, useState } from "react";
import { TouchableOpacity } from "react-native";
import { useCallback, useState } from "react";
import { RefreshControl, TouchableOpacity } from "react-native";
export default function Profile() {
const { id } = useLocalSearchParams();
@@ -26,6 +25,7 @@ export default function Profile() {
const [data, setData] = useState<IProfile>();
const [dataToken, setDataToken] = useState<IProfile>();
const [listPortofolio, setListPortofolio] = useState<any[]>();
const [refreshing, setRefreshing] = useState(false);
const { token, logout, isAdmin, user, userData } = useAuth();
@@ -55,13 +55,21 @@ export default function Profile() {
};
const onLoadData = async (id: string) => {
const response = await apiProfile({ id: id });
setData(response.data);
try {
const response = await apiProfile({ id: id });
setData(response.data);
} catch (error) {
console.log("[ERROR onLoadData]", error);
}
};
const onLoadUserByToken = async () => {
const response = await apiUser(user?.id as string);
setDataToken(response?.data?.Profile);
try {
const response = await apiUser(user?.id as string);
setDataToken(response?.data?.Profile);
} catch (error) {
console.log("[ERROR onLoadUserByToken]", error);
}
};
const onLoadPortofolio = async (id: string) => {
@@ -75,10 +83,20 @@ export default function Profile() {
.slice(0, 2);
setListPortofolio(lastTwoByDate);
} catch (error) {
console.log("[ERROR]", error);
console.log("[ERROR onLoadPortofolio]", error);
}
};
const onRefresh = useCallback(() => {
setRefreshing(true);
onLoadData(id as string);
onLoadPortofolio(id as string);
onLoadUserByToken();
isUserCheck();
userData(token as string);
setRefreshing(false);
}, [id, token]);
return (
<>
<Stack.Screen
@@ -98,7 +116,16 @@ export default function Profile() {
}}
/>
{/* Main View */}
<ViewWrapper>
<NewWrapper
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
tintColor={MainColor.yellow}
colors={[MainColor.yellow]}
/>
}
>
{!data || !dataToken ? (
<StackCustom>
<CustomSkeleton height={400} />
@@ -114,7 +141,7 @@ export default function Profile() {
/>
</>
)}
</ViewWrapper>
</NewWrapper>
{/* Drawer Komponen Eksternal */}
<DrawerCustom

View File

@@ -17,6 +17,7 @@ import {
ICON_SIZE_XLARGE,
} from "@/constants/constans-value";
import { useAuth } from "@/hooks/use-auth";
import { useNotificationStore } from "@/hooks/use-notification-store";
import AdminNotificationBell from "@/screens/Admin/AdminNotificationBell";
import {
adminListMenu,

View File

@@ -1,254 +1,5 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
ActionIcon,
AlertDefaultSystem,
BadgeCustom,
BaseBox,
DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { IconDot, IconList } from "@/components/_Icon/IconComponent";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import AdminButtonReview from "@/components/_ShareComponent/Admin/ButtonReview";
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
import ReportBox from "@/components/Box/ReportBox";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { useAuth } from "@/hooks/use-auth";
import { funUpdateStatusEvent } from "@/screens/Admin/Event/funUpdateStatus";
import { apiAdminEventById } from "@/service/api-admin/api-admin-event";
import { DEEP_LINK_URL } from "@/service/api-config";
import { colorBadgeStatus } from "@/utils/colorBadge";
import { dateTimeView } from "@/utils/dateTimeView";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import React, { useCallback } from "react";
import QRCode from "react-native-qrcode-svg";
import Toast from "react-native-toast-message";
import { Admin_ScreenEventDetail } from "@/screens/Admin/Event/ScreenEventDetail";
export default function AdminEventDetail() {
const { user } = useAuth();
const { id, status } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = React.useState(false);
const [data, setData] = React.useState<any | null>(null);
const [loadData, setLoadData] = React.useState(false);
const deepLinkURL = `${DEEP_LINK_URL}/event/${id}/confirmation?userId=${user?.id}`;
const deepLinkURLDEV = `${DEEP_LINK_URL}/--/event/${id}/confirmation?userId=${user?.id}`;
const isDevLink =
process.env.NODE_ENV === "development" ? deepLinkURLDEV : deepLinkURL;
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
setLoadData(true);
const response = await apiAdminEventById({
id: id as string,
});
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadData(false);
}
};
const listData = [
{
label: "Pembuat Event",
value: (data && data?.Author?.username) || "-",
},
{
label: "Judul Event",
value: (data && data?.title) || "-",
},
{
label: "Status",
value:
(data && (
<BadgeCustom color={colorBadgeStatus({ status: status as string })}>
{_.startCase(status as string)}
</BadgeCustom>
)) ||
"-",
},
{
label: "Lokasi",
value: (data && data?.lokasi) || "-",
},
{
label: "Tipe Acara",
value: (data && data?.EventMaster_TipeAcara?.name) || "-",
},
{
label: "Mulai Event",
value:
(data && data?.tanggal && dateTimeView({ date: data?.tanggal })) || "-",
},
{
label: "Event Berakhir",
value:
(data &&
data?.tanggalSelesai &&
dateTimeView({ date: data?.tanggalSelesai })) ||
"-",
},
{
label: "Deskripsi",
value: (data && data?.deskripsi) || "-",
},
];
const rightComponent = (
<ActionIcon
icon={<IconDot size={ICON_SIZE_BUTTON} />}
onPress={() => {
setOpenDrawer(true);
}}
/>
);
const handlerSubmit = async () => {
try {
const response = await funUpdateStatusEvent({
id: id as string,
changeStatus: "publish",
data: { catatan: "", senderId: user?.id as string },
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Gagal mempublikasikan event",
});
return;
}
Toast.show({
type: "success",
text1: "Event berhasil dipublikasikan",
});
router.back();
} catch (error) {
console.log("[ERROR]", error);
}
};
return (
<>
<ViewWrapper
headerComponent={
<AdminBackButtonAntTitle
title={`Detail Data`}
rightComponent={
(status === "publish" || status === "history") && rightComponent
}
/>
}
>
<BaseBox>
<StackCustom>
{listData.map((item, i) => (
<GridSpan_4_8
key={i}
label={<TextCustom bold>{item.label}</TextCustom>}
value={<TextCustom>{item.value}</TextCustom>}
/>
))}
</StackCustom>
<Spacing />
</BaseBox>
{data &&
data?.catatan &&
(status === "reject" || status === "review") && (
<ReportBox text={data?.catatan} />
)}
{(status === "publish" || status === "history") && (
<BaseBox>
<StackCustom style={{ alignItems: "center" }}>
<TextCustom bold>QR Code Event</TextCustom>
{loadData ? (
<LoaderCustom />
) : (
<QRCode
value={isDevLink}
size={200}
// logo={require("@/assets/images/logo-hipmi.png")}
// logoSize={70}
// logoBackgroundColor="transparent"
// logoBorderRadius={50}
// color="black"
/>
)}
{/* <TextCustom align="center">{isDevLink}</TextCustom> */}
</StackCustom>
</BaseBox>
)}
{status === "review" && (
<AdminButtonReview
onPublish={() => {
AlertDefaultSystem({
title: "Publish",
message: "Apakah anda yakin ingin mempublikasikan data ini?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: () => handlerSubmit(),
});
}}
onReject={() => {
router.push(`/admin/event/${id}/reject-input?status=${status}`);
}}
/>
)}
{status === "reject" && (
<AdminButtonReject
title="Tambah Catatan"
onReject={() => {
router.push(`/admin/event/${id}/reject-input?status=${status}`);
}}
/>
)}
<Spacing />
</ViewWrapper>
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
label: "Daftar Peserta",
icon: <IconList />,
path: `/admin/event/${id}/list-of-participants`,
},
]}
onPressItem={(item) => {
setOpenDrawer(false);
router.push(item.path as any);
}}
/>
</DrawerCustom>
</>
);
return <Admin_ScreenEventDetail />;
}

View File

@@ -9,32 +9,14 @@ import {
ViewWrapper,
} from "@/components";
import GridTwoView from "@/components/_ShareComponent/GridTwoView";
import API_IMAGE from "@/constants/api-storage";
import { MapMarker, MapsV2Custom } from "@/components/Map/MapsV2Custom";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { apiMapsGetAll } from "@/service/api-client/api-maps";
import { openInDeviceMaps } from "@/utils/openInDeviceMaps";
import { FontAwesome, Ionicons } from "@expo/vector-icons";
import { Image } from "expo-image";
import { router, useFocusEffect } from "expo-router";
import { useCallback, useState } from "react";
import { View } from "react-native";
import MapView, { Marker } from "react-native-maps";
const defaultRegion = {
latitude: -8.737109,
longitude: 115.1756897,
latitudeDelta: 0.1,
longitudeDelta: 0.1,
height: 300,
};
export interface LocationItem {
id: string | number;
latitude: number;
longitude: number;
name: string;
imageId?: string;
}
export default function AdminMaps() {
const [list, setList] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState(false);
@@ -72,74 +54,30 @@ export default function AdminMaps() {
}
};
const markers: MapMarker[] = list?.map((item) => ({
id: item.id,
coordinate: [item.longitude, item.latitude] as [number, number],
imageId: item.Portofolio?.logoId,
onSelected: () => {
setOpenDrawer(true);
setSelected({
id: item?.id,
bidangBisnis: item?.Portofolio?.MasterBidangBisnis?.name,
nomorTelepon: item?.Portofolio?.tlpn,
alamatBisnis: item?.Portofolio?.alamatKantor,
namePin: item?.namePin,
imageId: item?.imageId,
portofolioId: item?.Portofolio?.id,
latitude: item?.latitude,
longitude: item?.longitude,
});
},
})) || [];
return (
<>
<ViewWrapper style={{ paddingInline: 0, paddingBlock: 0 }}>
{/* <MapCustom height={"100%"} /> */}
<View style={{ flex: 1 }}>
{loadList ? (
<MapView
initialRegion={defaultRegion}
style={{
width: "100%",
height: "100%",
}}
/>
) : (
<MapView
initialRegion={defaultRegion}
style={{
width: "100%",
height: "100%",
}}
>
{list?.map((item: any, index: number) => {
return (
<Marker
key={item?.id}
coordinate={{
latitude: item?.latitude,
longitude: item?.longitude,
}}
title={item?.namePin}
onPress={() => {
setOpenDrawer(true);
setSelected({
id: item?.id,
bidangBisnis:
item?.Portofolio?.MasterBidangBisnis?.name,
nomorTelepon: item?.Portofolio?.tlpn,
alamatBisnis: item?.Portofolio?.alamatKantor,
namePin: item?.namePin,
imageId: item?.imageId,
portofolioId: item?.Portofolio?.id,
latitude: item?.latitude,
longitude: item?.longitude,
});
}}
// Gunakan gambar kustom jika tersedia
>
<View>
<Image
source={{
uri: API_IMAGE.GET({
fileId: item?.Portofolio?.logoId,
}),
}}
style={{
width: 30,
height: 30,
borderRadius: 100,
borderWidth: 1,
}}
/>
</View>
</Marker>
);
})}
</MapView>
)}
</View>
<MapsV2Custom markers={markers} />
</ViewWrapper>
<DrawerCustom
@@ -147,7 +85,9 @@ export default function AdminMaps() {
closeDrawer={() => setOpenDrawer(false)}
height={"auto"}
>
<DummyLandscapeImage height={200} imageId={selected.imageId} />
{selected.imageId && (
<DummyLandscapeImage height={200} imageId={selected.imageId} />
)}
<Spacing />
<StackCustom gap={"xs"}>
<GridTwoView

View File

@@ -22,7 +22,7 @@ type AuthContextType = {
isAdmin: boolean;
isUserActive: boolean;
loginWithNomor: (nomor: string) => Promise<boolean>;
validateOtp: (nomor: string) => Promise<any>;
validateOtp: (nomor: string, code: string) => Promise<any>;
logout: () => Promise<void>;
registerUser: (userData: {
username: string;
@@ -97,10 +97,10 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
};
// --- 2. Validasi OTP & cek user ---
const validateOtp = async (nomor: string) => {
const validateOtp = async (nomor: string, code: string) => {
try {
setIsLoading(true);
const response = await apiValidationCode({ nomor: nomor });
const response = await apiValidationCode({ nomor: nomor, code: code });
const { token } = response;
console.log("[RESPONSE VALIDASI OTP]", JSON.stringify(response, null, 2));

253
docs/QR_CODE_TESTING.md Normal file
View File

@@ -0,0 +1,253 @@
# QR Code Testing Guide - HIPMI Mobile
## 📋 Overview
Dokumentasi ini menjelaskan cara testing QR Code untuk Universal Links (iOS) dan App Links (Android) pada fitur Event Confirmation.
## 🔧 Update Terbaru
File `screens/Admin/Event/EventDetailQRCode.tsx` telah diupdate dengan fitur:
- **Toggle Button**: Switch antara HTTPS link dan Custom Scheme link
- **HTTPS Link**: Untuk testing Universal Links/App Links dengan domain staging
- **Custom Scheme**: Untuk testing langsung tanpa domain verification
## 🎯 Cara Testing QR Code
### Opsi 1: HTTPS Link (Recommended untuk Production)
**Gunakan tombol "HTTPS"** di component QR Code.
**Link yang di-generate:**
```
https://cld-dkr-staging-hipmi.wibudev.com/event/{id}/confirmation?userId={userId}
```
**Cara kerja:**
1. User scan QR code dengan kamera
2. Safari/Chrome terbuka dengan URL HTTPS
3. iOS/Android mendeteksi domain terverifikasi
4. App terbuka otomatis dan menuju halaman confirmation
**Prerequisites:**
- ✅ File `apple-app-site-association` harus accessible di Next.js server
- ✅ File `assetlinks.json` harus accessible di Next.js server
- ✅ Domain harus terverifikasi di app.config.js
- ✅ App harus di-build ulang setelah perubahan domain
**Testing Steps:**
```bash
# 1. Pastikan .well-known files accessible
curl https://cld-dkr-staging-hipmi.wibudev.com/.well-known/apple-app-site-association
curl https://cld-dkr-staging-hipmi.wibudev.com/.well-known/assetlinks.json
# 2. Rebuild app
bunx expo prebuild --clean
# 3. Run di physical device (bukan simulator)
bun run android # untuk Android
bun run ios # untuk iOS
```
### Opsi 2: Custom Scheme Link (Untuk Development/Testing Cepat)
**Gunakan tombol "Custom Scheme"** di component QR Code.
**Link yang di-generate:**
```
hipmimobile://event/{id}/confirmation?userId={userId}
```
**Cara kerja:**
1. User scan QR code dengan kamera
2. iOS: Pilih "Open in HIPMI Badung Connect"
3. Android: Langsung buka app
4. App terbuka dan menuju halaman confirmation
**Keuntungan:**
- ✅ Tidak butuh domain verification
- ✅ Bisa testing langsung tanpa rebuild
- ✅ Cocok untuk development
**Kekurangan:**
- ❌ Tidak bisa dibuka dari web browser
- ❌ Tidak support universal linking dari website lain
## 📱 Testing Checklist
### iOS (Universal Links)
- [ ] File `apple-app-site-association` valid dan accessible
- [ ] Domain terdaftar di `app.config.js``ios.associatedDomains`
- [ ] Bundle ID match dengan konfigurasi
- [ ] Team ID benar di apple-app-site-association
- [ ] Test dengan **physical device** (simulator tidak support)
- [ ] Test dengan **Safari** (bukan Chrome)
- [ ] Long press link → ada opsi "Open"
**Debug iOS:**
```bash
# Cek apple-app-site-association
curl -I https://cld-dkr-staging-hipmi.wibudev.com/.well-known/apple-app-site-association
# Harus return:
# Content-Type: application/json
# HTTP/2 200
```
### Android (App Links)
- [ ] File `assetlinks.json` valid dan accessible
- [ ] SHA256 fingerprint benar
- [ ] Package name match
- [ ] Intent filters terdaftar di app.config.js
- [ ] Test dengan **physical device**
- [ ] Test dengan **Chrome**
**Debug Android:**
```bash
# Dapatkan SHA256 fingerprint
cd android
./gradlew signingReport
# Cek assetlinks.json
curl https://cld-dkr-staging-hipmi.wibudev.com/.well-known/assetlinks.json
```
## 🐛 Troubleshooting
### Problem: QR Scan Terbuka di Safari, Tidak Balik ke App
**Penyebab:**
- Domain belum terverifikasi untuk Universal Links/App Links
- File `.well-known` tidak accessible atau invalid
- App belum di-rebuild setelah perubahan domain
**Solusi:**
1. Pastikan file `.well-known` accessible:
```bash
curl https://cld-dkr-staging-hipmi.wibudev.com/.well-known/apple-app-site-association
```
2. Rebuild app:
```bash
bunx expo prebuild --clean
bun run android # atau bun run ios
```
3. Gunakan **Custom Scheme** untuk testing cepat
### Problem: Link Tidak Membuka App Sama Sekali
**Cek:**
1. App sudah terinstall di device
2. Link format benar (hipmimobile:// atau https://)
3. Route handler sudah ada di app folder
**Test manual:**
```bash
# iOS Simulator
xcrun simctl openurl booted "hipmimobile://event/123/confirmation?userId=456"
# Android Emulator
adb shell am start -W -a android.intent.action.VIEW \
-d "hipmimobile://event/123/confirmation?userId=456" \
com.bip.hipmimobileapp
```
### Problem: "Cannot GET /event/..." di Next.js
**Penyebab:**
Route `/event/[id]/confirmation` tidak ada di Next.js server
**Solusi:**
Pastikan Next.js project punya file:
```
public/.well-known/apple-app-site-association
public/.well-known/assetlinks.json
```
Dan API route untuk handle:
```
pages/api/event/[id]/confirmation.ts
```
## 📄 File Configuration
### app.config.js - iOS
```javascript
ios: {
associatedDomains: ["applinks:cld-dkr-staging-hipmi.wibudev.com"],
}
```
### app.config.js - Android
```javascript
android: {
intentFilters: [
{
action: "VIEW",
autoVerify: true,
data: [
{
scheme: "https",
host: "cld-dkr-staging-hipmi.wibudev.com",
pathPrefix: "/",
},
],
category: ["BROWSABLE", "DEFAULT"],
},
],
}
```
### apple-app-site-association (Next.js)
```json
{
"applinks": {
"apps": [],
"details": [
{
"appID": "TEAM_ID.com.anonymous.hipmi-mobile",
"paths": ["/event/*/confirmation"]
}
]
}
}
```
### assetlinks.json (Next.js)
```json
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.bip.hipmimobileapp",
"sha256_cert_fingerprints": ["YOUR_SHA256_FINGERPRINT"]
}
}
]
```
## 🎓 Best Practices
1. **Development**: Gunakan Custom Scheme untuk testing cepat
2. **Staging**: Gunakan HTTPS link dengan domain staging
3. **Production**: Gunakan HTTPS link dengan domain production
4. **Testing**: Selalu test di physical device, bukan simulator
5. **Debugging**: Enable logging di confirmation page untuk track deep link
## 🔗 Related Files
- `screens/Admin/Event/EventDetailQRCode.tsx` - QR Code generator
- `app/(application)/(user)/event/[id]/confirmation.tsx` - Confirmation page
- `app.config.js` - App configuration
- `service/api-config.ts` - API configuration (DEEP_LINK_URL)
## 📞 Support
Jika masih ada masalah:
1. Cek logs di console
2. Test manual dengan adb/xcrun
3. Verify .well-known files dengan curl
4. Pastikan app rebuild setelah perubahan config

View File

@@ -55,10 +55,10 @@ Component yang digunakan: components/_ShareComponent/NewWrapper.tsx
<!-- START Prompt Admin Refactoring -->
<!-- Pindah kode ke Screen Component -->
File source: app/(application)/admin/forum/[id]/list-comment.tsx
Folder tujuan: screens/Admin/Forum
Nama file utama: ScreenForumListComment.tsx
Nama function utama: Admin_ScreenForumListComment
File source: app/(application)/admin/event/[id]/[status]/index.tsx
Folder tujuan: screens/Admin/Event
Nama file utama: ScreenEventDetail.tsx
Nama function utama: Admin_ScreenEventDetail
File komponen wrapper: components/_ShareComponent/NewWrapper.tsx
Buat file baru pada "Folder tujuan" dengan nama "Nama file utama" dan ubah nama function menjadi "Nama function utama" kemudian clean code, import dan panggil function tersebut pada file "File source"

View File

@@ -176,6 +176,19 @@
9B007D2599C64C7F8F525B86 /* Remove signature files (Xcode workaround) */,
1393AE9C86924FA8B1F8D11E /* Remove signature files (Xcode workaround) */,
E1F9AE3DCABE4A088A05E180 /* Remove signature files (Xcode workaround) */,
211F6E22A1B24524B67693F8 /* Remove signature files (Xcode workaround) */,
469F2CAA8928481CA86EB0F4 /* Remove signature files (Xcode workaround) */,
0F9297956F4F4FC9881920F8 /* Remove signature files (Xcode workaround) */,
058D2457CFA64FD9AC31C74F /* Remove signature files (Xcode workaround) */,
FB0CB57BF4D74C1D87C2036C /* Remove signature files (Xcode workaround) */,
14B3DE54EE4049AEB1EADA6B /* Remove signature files (Xcode workaround) */,
B4CF5E09DBB44A4FB9CB91B9 /* Remove signature files (Xcode workaround) */,
C894BD25C8224984AAD73398 /* Remove signature files (Xcode workaround) */,
F0C608193824414E93E23BC7 /* Remove signature files (Xcode workaround) */,
A3E2EDBCFB514A6487E28BEC /* Remove signature files (Xcode workaround) */,
0D62979D96BF4B99AB9FBE7C /* Remove signature files (Xcode workaround) */,
49B80EF12BE8476C86534CEA /* Remove signature files (Xcode workaround) */,
6218417B3C954EFF9B5F4853 /* Remove signature files (Xcode workaround) */,
);
buildRules = (
);
@@ -869,6 +882,227 @@
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
";
};
211F6E22A1B24524B67693F8 /* Remove signature files (Xcode workaround) */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
name = "Remove signature files (Xcode workaround)";
inputPaths = (
);
outputPaths = (
);
shellPath = /bin/sh;
shellScript = "
echo \"Remove signature files (Xcode workaround)\";
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
";
};
469F2CAA8928481CA86EB0F4 /* Remove signature files (Xcode workaround) */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
name = "Remove signature files (Xcode workaround)";
inputPaths = (
);
outputPaths = (
);
shellPath = /bin/sh;
shellScript = "
echo \"Remove signature files (Xcode workaround)\";
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
";
};
0F9297956F4F4FC9881920F8 /* Remove signature files (Xcode workaround) */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
name = "Remove signature files (Xcode workaround)";
inputPaths = (
);
outputPaths = (
);
shellPath = /bin/sh;
shellScript = "
echo \"Remove signature files (Xcode workaround)\";
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
";
};
058D2457CFA64FD9AC31C74F /* Remove signature files (Xcode workaround) */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
name = "Remove signature files (Xcode workaround)";
inputPaths = (
);
outputPaths = (
);
shellPath = /bin/sh;
shellScript = "
echo \"Remove signature files (Xcode workaround)\";
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
";
};
FB0CB57BF4D74C1D87C2036C /* Remove signature files (Xcode workaround) */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
name = "Remove signature files (Xcode workaround)";
inputPaths = (
);
outputPaths = (
);
shellPath = /bin/sh;
shellScript = "
echo \"Remove signature files (Xcode workaround)\";
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
";
};
14B3DE54EE4049AEB1EADA6B /* Remove signature files (Xcode workaround) */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
name = "Remove signature files (Xcode workaround)";
inputPaths = (
);
outputPaths = (
);
shellPath = /bin/sh;
shellScript = "
echo \"Remove signature files (Xcode workaround)\";
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
";
};
B4CF5E09DBB44A4FB9CB91B9 /* Remove signature files (Xcode workaround) */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
name = "Remove signature files (Xcode workaround)";
inputPaths = (
);
outputPaths = (
);
shellPath = /bin/sh;
shellScript = "
echo \"Remove signature files (Xcode workaround)\";
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
";
};
C894BD25C8224984AAD73398 /* Remove signature files (Xcode workaround) */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
name = "Remove signature files (Xcode workaround)";
inputPaths = (
);
outputPaths = (
);
shellPath = /bin/sh;
shellScript = "
echo \"Remove signature files (Xcode workaround)\";
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
";
};
F0C608193824414E93E23BC7 /* Remove signature files (Xcode workaround) */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
name = "Remove signature files (Xcode workaround)";
inputPaths = (
);
outputPaths = (
);
shellPath = /bin/sh;
shellScript = "
echo \"Remove signature files (Xcode workaround)\";
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
";
};
A3E2EDBCFB514A6487E28BEC /* Remove signature files (Xcode workaround) */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
name = "Remove signature files (Xcode workaround)";
inputPaths = (
);
outputPaths = (
);
shellPath = /bin/sh;
shellScript = "
echo \"Remove signature files (Xcode workaround)\";
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
";
};
0D62979D96BF4B99AB9FBE7C /* Remove signature files (Xcode workaround) */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
name = "Remove signature files (Xcode workaround)";
inputPaths = (
);
outputPaths = (
);
shellPath = /bin/sh;
shellScript = "
echo \"Remove signature files (Xcode workaround)\";
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
";
};
49B80EF12BE8476C86534CEA /* Remove signature files (Xcode workaround) */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
name = "Remove signature files (Xcode workaround)";
inputPaths = (
);
outputPaths = (
);
shellPath = /bin/sh;
shellScript = "
echo \"Remove signature files (Xcode workaround)\";
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
";
};
6218417B3C954EFF9B5F4853 /* Remove signature files (Xcode workaround) */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
name = "Remove signature files (Xcode workaround)";
inputPaths = (
);
outputPaths = (
);
shellPath = /bin/sh;
shellScript = "
echo \"Remove signature files (Xcode workaround)\";
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */

View File

@@ -6,7 +6,7 @@
<string>development</string>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:cld-dkr-staging-hipmi.wibudev.com</string>
<string>applinks:cld-dkr-hipmi-stg.wibudev.com</string>
</array>
</dict>
</plist>

View File

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

View File

@@ -4,10 +4,16 @@ 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 { useEffect } from "react";
import { Text, View } from "react-native";
export default function AdminNotificationBell() {
const { unreadCount } = useNotificationStore();
const { unreadCount, syncUnreadCount } = useNotificationStore();
useEffect(() => {
console.log("Syncing unread count");
syncUnreadCount();
}, [syncUnreadCount]);
return (
<View style={{ position: "relative" }}>

View File

@@ -0,0 +1,85 @@
import { BadgeCustom, BaseBox, Spacing, StackCustom, TextCustom } from "@/components";
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
import { colorBadgeStatus } from "@/utils/colorBadge";
import { dateTimeView } from "@/utils/dateTimeView";
import _ from "lodash";
interface EventDetailData {
Author?: {
username?: string;
};
title?: string;
lokasi?: string;
EventMaster_TipeAcara?: {
name?: string;
};
tanggal?: string;
tanggalSelesai?: string;
deskripsi?: string;
catatan?: string;
}
interface BoxEventDetailProps {
data: EventDetailData | null;
status: string;
}
export function BoxEventDetail({ data, status }: BoxEventDetailProps) {
const listData = [
{
label: "Pembuat Event",
value: data?.Author?.username || "-",
},
{
label: "Judul Event",
value: data?.title || "-",
},
{
label: "Status",
value: data ? (
<BadgeCustom color={colorBadgeStatus({ status })}>
{_.startCase(status)}
</BadgeCustom>
) : (
"-"
),
},
{
label: "Lokasi",
value: data?.lokasi || "-",
},
{
label: "Tipe Acara",
value: data?.EventMaster_TipeAcara?.name || "-",
},
{
label: "Mulai Event",
value: data?.tanggal ? dateTimeView({ date: data.tanggal }) : "-",
},
{
label: "Event Berakhir",
value: data?.tanggalSelesai
? dateTimeView({ date: data.tanggalSelesai })
: "-",
},
{
label: "Deskripsi",
value: data?.deskripsi || "-",
},
];
return (
<BaseBox>
<StackCustom>
{listData.map((item, i) => (
<GridSpan_4_8
key={i}
label={<TextCustom bold>{item.label}</TextCustom>}
value={<TextCustom>{item.value}</TextCustom>}
/>
))}
</StackCustom>
<Spacing />
</BaseBox>
);
}

View File

@@ -0,0 +1,37 @@
import { DrawerCustom, MenuDrawerDynamicGrid } from "@/components";
import { IconList } from "@/components/_Icon/IconComponent";
import { router } from "expo-router";
interface EventDetailDrawerProps {
isVisible: boolean;
onClose: () => void;
eventId: string;
}
export function EventDetailDrawer({
isVisible,
onClose,
eventId,
}: EventDetailDrawerProps) {
return (
<DrawerCustom
isVisible={isVisible}
closeDrawer={onClose}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
label: "Daftar Peserta",
icon: <IconList />,
path: `/admin/event/${eventId}/list-of-participants`,
},
]}
onPressItem={(item) => {
onClose();
router.push(item.path as any);
}}
/>
</DrawerCustom>
);
}

View File

@@ -0,0 +1,93 @@
import {
BaseBox,
ButtonCustom,
LoaderCustom,
Spacing,
StackCustom,
TextCustom,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { BASE_URL } from "@/service/api-config";
import { useLocalSearchParams } from "expo-router";
import { useState } from "react";
import { StyleSheet } from "react-native";
import QRCode from "react-native-qrcode-svg";
interface EventDetailQRCodeProps {
userId: string;
isLoading: boolean;
}
export function EventDetailQRCode({
userId,
isLoading,
}: EventDetailQRCodeProps) {
const { id } = useLocalSearchParams();
const [useHttpsLink, setUseHttpsLink] = useState(true);
// HTTPS link untuk Universal Links (iOS) dan App Links (Android)
// Ini akan membuka file .well-known di Next.js server Anda
const httpsLink = `https://cld-dkr-hipmi-stg.wibudev.com/event/${id}/confirmation?userId=${userId}`;
// Custom scheme link untuk fallback atau testing tanpa universal links
const deepLinkURL = `${BASE_URL}/event/${id}/confirmation?userId=${userId}`;
// Toggle antara HTTPS link dan custom scheme
const qrValue = useHttpsLink ? httpsLink : deepLinkURL;
return (
<BaseBox>
<StackCustom style={{ alignItems: "center" }}>
<TextCustom bold>QR Code Event</TextCustom>
{isLoading ? <LoaderCustom /> : <QRCode value={qrValue} size={200} />}
</StackCustom>
<Spacing />
<TextCustom color="gray" align="center" size="small">
{qrValue}
</TextCustom>
<Spacing />
<StackCustom direction="row" gap="sm">
<ButtonCustom
onPress={() => setUseHttpsLink(true)}
backgroundColor={useHttpsLink ? MainColor.yellow : "transparent"}
textColor={useHttpsLink ? MainColor.black : MainColor.yellow}
style={[
stylesButton.smallButton,
useHttpsLink && stylesButton.border,
]}
>
HTTPS
</ButtonCustom>
<ButtonCustom
onPress={() => setUseHttpsLink(false)}
backgroundColor={!useHttpsLink ? MainColor.yellow : "transparent"}
textColor={!useHttpsLink ? MainColor.black : MainColor.yellow}
style={[
stylesButton.smallButton,
!useHttpsLink && stylesButton.border,
]}
>
Custom Scheme
</ButtonCustom>
</StackCustom>
<Spacing />
<TextCustom color="gray" align="center" size={"small"}>
{useHttpsLink
? "✅ Testing Universal Links/App Links (butuh .well-known config)"
: "🔧 Testing langsung (tanpa domain verification)"}
</TextCustom>
</BaseBox>
);
}
const stylesButton = StyleSheet.create({
smallButton: {
paddingHorizontal: 12,
paddingVertical: 6,
minWidth: 140,
},
border: {
borderWidth: 1,
borderColor: MainColor.yellow,
},
});

View File

@@ -0,0 +1,165 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { ActionIcon, AlertDefaultSystem, Spacing } from "@/components";
import { IconDot } from "@/components/_Icon/IconComponent";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import AdminButtonReview from "@/components/_ShareComponent/Admin/ButtonReview";
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
import ReportBox from "@/components/Box/ReportBox";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { useAuth } from "@/hooks/use-auth";
import { funUpdateStatusEvent } from "@/screens/Admin/Event/funUpdateStatus";
import { apiAdminEventById } from "@/service/api-admin/api-admin-event";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useMemo, useState } from "react";
import Toast from "react-native-toast-message";
import { BoxEventDetail } from "./BoxEventDetail";
import { EventDetailDrawer } from "./EventDetailDrawer";
import { EventDetailQRCode } from "./EventDetailQRCode";
import { View } from "react-native";
export function Admin_ScreenEventDetail() {
const { user } = useAuth();
const { id, status } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false);
const [data, setData] = useState<any | null>(null);
const [loadData, setLoadData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id]),
);
const onLoadData = async () => {
try {
setLoadData(true);
const response = await apiAdminEventById({
id: id as string,
});
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadData(false);
}
};
const rightComponent = (
<ActionIcon
icon={<IconDot size={ICON_SIZE_BUTTON} />}
onPress={() => {
setOpenDrawer(true);
}}
/>
);
const handlerSubmit = async () => {
try {
const response = await funUpdateStatusEvent({
id: id as string,
changeStatus: "publish",
data: { catatan: "", senderId: user?.id as string },
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Gagal mempublikasikan event",
});
return;
}
Toast.show({
type: "success",
text1: "Event berhasil dipublikasikan",
});
router.back();
} catch (error) {
console.log("[ERROR]", error);
}
};
const headerComponent = useMemo(
() => (
<AdminBackButtonAntTitle
title={`Detail Data`}
rightComponent={
status === "publish" || status === "history"
? rightComponent
: undefined
}
/>
),
[status],
);
const footerComponent = useMemo(() => {
if (status === "review") {
return (
<AdminButtonReview
onPublish={() => {
AlertDefaultSystem({
title: "Publish",
message: "Apakah anda yakin ingin mempublikasikan data ini?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: () => handlerSubmit(),
});
}}
onReject={() => {
router.push(`/admin/event/${id}/reject-input?status=${status}`);
}}
/>
);
}
if (status === "reject") {
return (
<AdminButtonReject
title="Tambah Catatan"
onReject={() => {
router.push(`/admin/event/${id}/reject-input?status=${status}`);
}}
/>
);
}
return null;
}, [status, id]);
return (
<>
<NewWrapper
headerComponent={headerComponent}
// footerComponent={
// <View style={{ paddingInline: 8 }}>
// {footerComponent}
// </View>
// }
>
<BoxEventDetail data={data} status={status as string} />
{data?.catatan && (status === "reject" || status === "review") && (
<ReportBox text={data.catatan} />
)}
{(status === "publish" || status === "history") && (
<EventDetailQRCode userId={user?.id || ""} isLoading={loadData} />
)}
{footerComponent}
<Spacing />
</NewWrapper>
<EventDetailDrawer
isVisible={openDrawer}
onClose={() => setOpenDrawer(false)}
eventId={id as string}
/>
</>
);
}

View File

@@ -5,7 +5,6 @@ import { MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import { apiCheckCodeOtp } from "@/service/api-config";
import { GStyles } from "@/styles/global-styles";
import { registerForPushNotificationsAsync } from "@/utils/notifications";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { router, useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react";
@@ -17,17 +16,15 @@ import Toast from "react-native-toast-message";
export default function VerificationView() {
const { nomor } = useLocalSearchParams<{ nomor: string }>();
console.log("[NOMOR]", nomor);
const [inputOtp, setInputOtp] = useState<string>("");
const [userNumber, setUserNumber] = useState<string>("");
const [loading, setLoading] = useState<boolean>(false);
const [recodeOtp, setRecodeOtp] = useState<boolean>(false);
// 🔑 DETEKSI MODE REVIEW (HANYA UNTUK NOMOR DEMO & PRODUCTION)
// 🔑 DETEKSI MODE REVIEW (HANYA UNTUK NOMOR DEMO & DEVELOPMENT BUILD)
// Menggunakan Constants.expoConfig untuk mendeteksi development build
const isReviewMode =
typeof window !== "undefined" && // pastikan di browser/production
process.env.NODE_ENV === "production" &&
process.env.NODE_ENV === "development" &&
nomor === "6282340374412";
// --- Context ---
@@ -40,10 +37,6 @@ export default function VerificationView() {
// Hanya jalankan logika OTP normal jika BUKAN review mode
onLoadCheckCodeOtp();
}
console.log("[NODE_ENV]:", process.env.NODE_ENV);
console.log("[isReviewMode]:", isReviewMode);
console.log("[nomor]:", nomor);
}, [recodeOtp, isReviewMode]);
async function onLoadCheckCodeOtp() {
@@ -88,29 +81,30 @@ export default function VerificationView() {
const handleVerification = async () => {
if (isReviewMode) {
// ✅ VERIFIKASI OTOMATIS UNTUK APPLE REVIEW
if (inputOtp === "1234") {
try {
await validateOtp(nomor as string);
return;
} catch (error) {
console.log("Error verification", error);
Toast.show({ type: "error", text1: "Gagal verifikasi" });
}
} else {
// ✅ VERIFIKASI OTOMATIS UNTUK APPLE REVIEW (Development Only)
if (inputOtp !== "1234") {
Toast.show({ type: "error", text1: "Kode OTP tidak sesuai" });
return;
}
try {
await validateOtp(nomor as string, inputOtp);
return;
} catch (error) {
console.log("Error verification", error);
Toast.show({ type: "error", text1: "Gagal verifikasi" });
}
return;
}
// 🔁 VERIFIKASI NORMAL (untuk pengguna sungguhan)
try {
await validateOtp(nomor as string);
return
} catch (error) {
await validateOtp(nomor as string, inputOtp);
return;
} catch (error: any) {
console.log("Error verification", error);
Toast.show({ type: "error", text1: "Gagal verifikasi" });
Toast.show({
type: "error",
text1: error.response?.data?.message || "Gagal verifikasi",
});
}
};

View File

@@ -1,41 +1,15 @@
import { ClickableCustom, TextCustom } from "@/components";
import Spacing from "@/components/_ShareComponent/Spacing";
import React, { useCallback, useState } from "react";
import { router } from "expo-router";
import { View } from "react-native";
import Icon from "react-native-vector-icons/FontAwesome";
import { stylesHome } from "./homeViewStyle";
import { router, useFocusEffect } from "expo-router";
import { apiJobGetAll } from "@/service/api-client/api-job";
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
export default function Home_BottomFeatureSection() {
const [listData, setListData] = useState<any[] | null>(null);
const onLoadData = async () => {
try {
const response = await apiJobGetAll({
category: "beranda",
});
console.log("[DATA JOB]", JSON.stringify(response.data, null, 2));
const result = response.data
.sort(
(a: any, b: any) =>
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
)
.slice(0, 2);
setListData(result);
} catch (error) {
console.log("[ERROR]", error);
}
};
useFocusEffect(
useCallback(() => {
onLoadData();
}, [])
);
export default function Home_BottomFeatureSection({
listData,
}: {
listData: any[] | null;
}) {
return (
<>
<ClickableCustom onPress={() => router.push("/job")}>

View File

@@ -139,8 +139,8 @@ export default function UserSearchMainView_V2() {
searchQuery: search,
emptyMessage: "Tidak ada pengguna ditemukan",
emptySearchMessage: "Tidak ada hasil pencarian",
skeletonCount: 5,
skeletonHeight: 150,
skeletonCount: PAGINATION_DEFAULT_TAKE,
skeletonHeight: 100,
loadingFooterText: "Memuat lebih banyak pengguna...",
isInitialLoad,
});

View File

@@ -45,9 +45,16 @@ export async function apiCheckCodeOtp({ kodeId }: { kodeId: string }) {
return response.data;
}
export async function apiValidationCode({ nomor }: { nomor: string }) {
export async function apiValidationCode({
nomor,
code,
}: {
nomor: string;
code: string;
}) {
const response = await apiConfig.post(`/auth/mobile-validasi`, {
nomor: nomor,
code: code,
});
return response.data;
}