Compare commits

...

6 Commits

Author SHA1 Message Date
f68deab8c0 Fix App Header
Layouts & Navigation
- app/(application)/_layout.tsx
- app/(application)/(user)/_layout.tsx
- app/(application)/(user)/event/(tabs)/_layout.tsx
- app/(application)/(user)/job/(tabs)/_layout.tsx
- app/(application)/(user)/voting/(tabs)/_layout.tsx
- app/(application)/(user)/portofolio/_layout.tsx
- app/(application)/(user)/profile/_layout.tsx
- app/(application)/admin/_layout.tsx
- app/+not-found.tsx

User – File
- app/(application)/(file)/[id].tsx

User – Collaboration
- app/(application)/(user)/collaboration/[id]/index.tsx
- app/(application)/(user)/collaboration/[id]/detail-project-main.tsx
- app/(application)/(user)/collaboration/[id]/detail-participant.tsx
- app/(application)/(user)/collaboration/[id]/[detail]/info.tsx
- app/(application)/(user)/collaboration/[id]/[detail]/room-chat.tsx

User – Donation
- app/(application)/(user)/donation/[id]/index.tsx
- app/(application)/(user)/donation/[id]/[status]/detail.tsx
- app/(application)/(user)/donation/[id]/(news)/[news]/index.tsx

User – Event
- app/(application)/(user)/event/[id]/[status]/detail-event.tsx
- app/(application)/(user)/event/[id]/confirmation.tsx
- app/(application)/(user)/event/[id]/contribution.tsx
- app/(application)/(user)/event/[id]/history.tsx
- app/(application)/(user)/event/[id]/publish.tsx

User – Investment
- app/(application)/(user)/investment/[id]/index.tsx
- app/(application)/(user)/investment/[id]/[status]/detail.tsx
- app/(application)/(user)/investment/[id]/(my-holding)/[id].tsx
- app/(application)/(user)/investment/[id]/(news)/[news]/index.tsx

User – Job
- app/(application)/(user)/job/[id]/[status]/detail.tsx

User – Portofolio
- app/(application)/(user)/portofolio/[id]/index.tsx

User – Profile
- app/(application)/(user)/profile/[id]/index.tsx

User – Voting
- app/(application)/(user)/voting/[id]/index.tsx
- app/(application)/(user)/voting/[id]/[status]/detail.tsx
- app/(application)/(user)/voting/[id]/contribution.tsx
- app/(application)/(user)/voting/[id]/history.tsx

Components
- components/Button/BackButtonFromNotification.tsx
- components/_ShareComponent/AppHeader.tsx

Admin Screens
- screens/Admin/Notification-Admin/ScreenNotificationAdmin.tsx
- screens/Admin/Notification-Admin/ScreenNotificationAdmin2.tsx

Screens – Donation
- screens/Donation/ScreenListOfNews.tsx
- screens/Donation/ScreenRecapOfNews.tsx

Screens – Forum
- screens/Forum/ViewBeranda.tsx
- screens/Forum/ViewBeranda2.tsx
- screens/Forum/ViewBeranda3.tsx

Screens – Investment
- screens/Invesment/Document/ScreenRecapOfDocument.tsx
- screens/Invesment/News/ScreenListOfNews.tsx
- screens/Invesment/News/ScreenRecapOfNews.tsx

Screens – Notification
- screens/Notification/ScreenNotification_V1.tsx
- screens/Notification/ScreenNotification_V2.tsx

iOS
- ios/HIPMIBadungConnect.xcodeproj/project.pbxproj

Docs
- QWEN.md

### Issue: Tabs can't clicked
2026-03-13 16:41:34 +08:00
37d2fbe48a Fix code/
User Pages
- app/(application)/(user)/home.tsx
- app/(application)/(user)/portofolio/[id]/create.tsx

Components
- components/_ShareComponent/AppHeader.tsx

Screens
- screens/Portofolio/ScreenPortofolioCreate.tsx

Config & Dependencies
- app.config.js
- package.json
- bun.lock

iOS
- ios/HIPMIBadungConnect.xcodeproj/project.pbxproj
- ios/HIPMIBadungConnect/Info.plist

Docs
- docs/prompt-for-qwen-code.md

### No issue
2026-03-12 16:50:02 +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
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
68 changed files with 2150 additions and 1176 deletions

View File

@@ -387,7 +387,7 @@ apiConfig.interceptors.request.use(async (config) => {
### Deep Linking
- Scheme: `hipmimobile://`
- HTTPS: `cld-dkr-staging-hipmi.wibudev.com`
- HTTPS: `cld-dkr-hipmi-stg.wibudev.com`
- Configured for both platforms
### Camera

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: "5",
},
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

@@ -1,4 +1,5 @@
import { BackButton } from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import PdfViewer from "@/components/_ShareComponent/PdfViewer";
import API_STRORAGE from "@/constants/base-url-api-strorage";
import { Stack, useLocalSearchParams } from "expo-router";
@@ -7,13 +8,12 @@ import { SafeAreaView } from "react-native-safe-area-context";
export default function FileScreen() {
const { id } = useLocalSearchParams();
const url = API_STRORAGE.GET({ fileId: id as string });
return (
<>
<Stack.Screen
options={{
title: "File",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="File" left={<BackButton />} />,
}}
/>
<SafeAreaView style={{ flex: 1 }} edges={["bottom"]}>

View File

@@ -1,22 +1,21 @@
import { BackButton } from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { IconPlus } from "@/components/_Icon";
import { IconDot } from "@/components/_Icon/IconComponent";
import LeftButtonCustom from "@/components/Button/BackButton";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { HeaderStyles } from "@/styles/header-styles";
import { Ionicons } from "@expo/vector-icons";
import { router, Stack } from "expo-router";
export default function UserLayout() {
return (
<>
<Stack screenOptions={HeaderStyles}>
<Stack>
<Stack.Screen
name="delete-account"
options={{
title: "Hapus Akun",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Hapus Akun" />,
}}
/>
<Stack.Screen
@@ -47,8 +46,7 @@ export default function UserLayout() {
<Stack.Screen
name="user-search/index"
options={{
title: "Pencarian Pengguna",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Pencarian Pengguna" />,
}}
/>
@@ -71,10 +69,18 @@ export default function UserLayout() {
{/* ========== Event Section ========= */}
{/* <Stack.Screen
name="event/(tabs)"
options={{
header: () => <AppHeader title="Event" left={<BackButton path="/home" />} />,
}}
/> */}
<Stack.Screen
name="event/(tabs)"
options={{
title: "Event",
header: () => <AppHeader title="Event" left={<BackButton path="/home" />} />,
// NOTE: DIPINDAH DI FILE /Event/(Tabs)/_layout.tsx
// headerLeft: () => (
// <LeftButtonCustom path="/(application)/(user)/home" />
@@ -85,32 +91,28 @@ export default function UserLayout() {
<Stack.Screen
name="event/create"
options={{
title: "Tambah Event",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Tambah Event" />,
}}
/>
<Stack.Screen
name="event/detail/[id]"
options={{
title: "Event Detail",
headerLeft: () => <LeftButtonCustom />,
header: () => <AppHeader title="Event Detail" left={<LeftButtonCustom />} />,
}}
/>
<Stack.Screen
name="event/[id]/edit"
options={{
title: "Edit Event",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Edit Event" />,
}}
/>
<Stack.Screen
name="event/[id]/list-of-participants"
options={{
title: "Daftar peserta",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Daftar peserta" />,
}}
/>
{/* ========== End Event Section ========= */}
@@ -119,22 +121,19 @@ export default function UserLayout() {
<Stack.Screen
name="collaboration/(tabs)"
options={{
title: "Collaboration",
headerLeft: () => <BackButton path="/home" />,
header: () => <AppHeader title="Collaboration" left={<BackButton path="/home" />} />,
}}
/>
<Stack.Screen
name="collaboration/create"
options={{
title: "Tambah Proyek",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Tambah Proyek" />,
}}
/>
<Stack.Screen
name="collaboration/[id]/list-of-participants"
options={{
title: "Daftar Partisipan",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Daftar Partisipan" />,
}}
/>
{/* <Stack.Screen
@@ -147,22 +146,19 @@ export default function UserLayout() {
<Stack.Screen
name="collaboration/[id]/edit"
options={{
title: "Edit Proyek",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Edit Proyek" />,
}}
/>
<Stack.Screen
name="collaboration/[id]/create-pacticipants"
options={{
title: "Ajukan Partisipasi",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Ajukan Partisipasi" />,
}}
/>
<Stack.Screen
name="collaboration/[id]/select-of-participants"
options={{
title: "Pilih Partisipan",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Pilih Partisipan" />,
}}
/>
@@ -172,29 +168,25 @@ export default function UserLayout() {
<Stack.Screen
name="voting/create"
options={{
title: "Tambah Voting",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Tambah Voting" />,
}}
/>
<Stack.Screen
name="voting/(tabs)"
options={{
title: "Voting",
headerLeft: () => <BackButton path="/home" />,
header: () => <AppHeader title="Voting" left={<BackButton path="/home" />} />,
}}
/>
<Stack.Screen
name="voting/[id]/edit"
options={{
title: "Edit Voting",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Edit Voting" />,
}}
/>
<Stack.Screen
name="voting/[id]/list-of-contributor"
options={{
title: "Daftar Kontributor",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Daftar Kontributor" />,
}}
/>
@@ -204,8 +196,7 @@ export default function UserLayout() {
<Stack.Screen
name="crowdfunding/index"
options={{
title: "Crowdfunding",
headerLeft: () => <BackButton path="/home" />,
header: () => <AppHeader title="Crowdfunding" left={<BackButton path="/home" />} />,
}}
/>
@@ -215,103 +206,95 @@ export default function UserLayout() {
<Stack.Screen
name="investment/(tabs)"
options={{
title: "Investasi",
headerLeft: () => <BackButton path="/crowdfunding" />,
header: () => <AppHeader title="Investasi" left={<BackButton path="/crowdfunding" />} />,
}}
/>
<Stack.Screen
name="investment/create"
options={{
title: "Tambah Investasi",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Tambah Investasi" />,
}}
/>
<Stack.Screen
name="investment/[id]/index"
options={{
title: "Detail Investasi",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Detail Investasi" />,
}}
/>
<Stack.Screen
name="investment/[id]/edit"
options={{
title: "Edit Investasi",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Edit Investasi" />,
}}
/>
<Stack.Screen
name="investment/[id]/edit-prospectus"
options={{
title: "Edit Prospektus",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Edit Prospektus" />,
}}
/>
<Stack.Screen
name="investment/[id]/(document)/list-of-document"
options={{
title: "Daftar Dokumen",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Daftar Dokumen" />,
}}
/>
<Stack.Screen
name="investment/[id]/(document)/add-document"
options={{
title: "Tambah Dokumen",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Tambah Dokumen" />,
}}
/>
<Stack.Screen
name="investment/[id]/(document)/edit-document"
options={{
title: "Edit Dokumen",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Edit Dokumen" />,
}}
/>
<Stack.Screen
name="investment/[id]/(news)/add-news"
options={{
title: "Tambah Berita",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Tambah Berita" />,
}}
/>
<Stack.Screen
name="investment/[id]/investor"
options={{
title: "Investor",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Investor" />,
}}
/>
<Stack.Screen
name="investment/[id]/(transaction-flow)/index"
options={{
title: "Pembelian Saham",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Pembelian Saham" />,
}}
/>
<Stack.Screen
name="investment/[id]/(transaction-flow)/select-bank"
options={{
title: "Pilih Bank",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Pilih Bank" />,
}}
/>
<Stack.Screen
name="investment/[id]/(transaction-flow)/invoice"
options={{
title: "Invoice",
headerLeft: () => (
<Ionicons
name="close"
size={ICON_SIZE_SMALL}
color={MainColor.yellow}
onPress={() =>
router.navigate(`/investment/(tabs)/transaction`)
header: () => (
<AppHeader
title="Invoice"
left={
<Ionicons
name="close"
size={ICON_SIZE_SMALL}
color={MainColor.yellow}
onPress={() =>
router.navigate(`/investment/(tabs)/transaction`)
}
/>
}
/>
),
@@ -320,14 +303,18 @@ export default function UserLayout() {
<Stack.Screen
name="investment/[id]/(transaction-flow)/process"
options={{
title: "Proses",
headerLeft: () => (
<Ionicons
name="close"
size={ICON_SIZE_SMALL}
color={MainColor.yellow}
onPress={() =>
router.navigate(`/investment/(tabs)/transaction`)
header: () => (
<AppHeader
title="Proses"
left={
<Ionicons
name="close"
size={ICON_SIZE_SMALL}
color={MainColor.yellow}
onPress={() =>
router.navigate(`/investment/(tabs)/transaction`)
}
/>
}
/>
),
@@ -336,23 +323,20 @@ export default function UserLayout() {
<Stack.Screen
name="investment/[id]/(transaction-flow)/success"
options={{
title: "Transaksi Berhasil",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Transaksi Berhasil" />,
}}
/>
<Stack.Screen
name="investment/[id]/(transaction-flow)/failed"
options={{
title: "Transaksi Gagal",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Transaksi Gagal" />,
}}
/>
<Stack.Screen
name="investment/[id]/(my-holding)/[id]"
options={{
title: "Detail Saham Saya",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Detail Saham Saya" />,
}}
/>
{/* ========== End Investment Section ========= */}
@@ -361,122 +345,111 @@ export default function UserLayout() {
<Stack.Screen
name="donation/(tabs)"
options={{
title: "Donasi",
headerLeft: () => <BackButton path="/crowdfunding" />,
header: () => <AppHeader title="Donasi" left={<BackButton path="/crowdfunding" />} />,
}}
/>
<Stack.Screen
name="donation/create"
options={{
title: "Tambah Donasi",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Tambah Donasi" />,
}}
/>
<Stack.Screen
name="donation/create-story"
options={{
title: "Tambah Donasi",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Tambah Donasi" />,
}}
/>
<Stack.Screen
name="donation/[id]/edit"
options={{
title: "Edit Donasi",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Edit Donasi" />,
}}
/>
<Stack.Screen
name="donation/[id]/edit-story"
options={{
title: "Edit Donasi",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Edit Donasi" />,
}}
/>
<Stack.Screen
name="donation/[id]/edit-rekening"
options={{
title: "Edit Rekening",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Edit Rekening" />,
}}
/>
<Stack.Screen
name="donation/[id]/detail-story"
options={{
title: "Cerita Penggalang",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Cerita Penggalang" />,
}}
/>
<Stack.Screen
name="donation/[id]/infromation-fundrising"
options={{
title: "Informasi Penggalang Dana",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Informasi Penggalang Dana" />,
}}
/>
<Stack.Screen
name="donation/[id]/list-of-donatur"
options={{
title: "Daftar Donatur",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Daftar Donatur" />,
}}
/>
<Stack.Screen
name="donation/[id]/fund-disbursement"
options={{
title: "Pencairan Dana",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Pencairan Dana" />,
}}
/>
<Stack.Screen
name="donation/[id]/(news)/recap-of-news"
options={{
title: "Rekap Kabar",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Rekap Kabar" />,
}}
/>
<Stack.Screen
name="donation/[id]/(news)/add-news"
options={{
title: "Tambah Berita",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Tambah Berita" />,
}}
/>
<Stack.Screen
name="donation/[id]/(news)/[news]/edit-news"
options={{
title: "Edit Berita",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Edit Berita" />,
}}
/>
<Stack.Screen
name="donation/[id]/(transaction-flow)/index"
options={{
title: "Donasi",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Donasi" />,
}}
/>
<Stack.Screen
name="donation/[id]/(transaction-flow)/select-bank"
options={{
title: "Pilih Bank",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Pilih Bank" />,
}}
/>
<Stack.Screen
name="donation/[id]/(transaction-flow)/[invoiceId]/invoice"
options={{
title: "Invoice",
headerLeft: () => (
<Ionicons
name="close"
size={ICON_SIZE_SMALL}
color={MainColor.yellow}
onPress={() => router.navigate(`/donation/(tabs)/my-donation`)}
header: () => (
<AppHeader
title="Invoice"
left={
<Ionicons
name="close"
size={ICON_SIZE_SMALL}
color={MainColor.yellow}
onPress={() => router.navigate(`/donation/(tabs)/my-donation`)}
/>
}
/>
),
}}
@@ -484,13 +457,17 @@ export default function UserLayout() {
<Stack.Screen
name="donation/[id]/(transaction-flow)/[invoiceId]/process"
options={{
title: "Proses",
headerLeft: () => (
<Ionicons
name="close"
size={ICON_SIZE_SMALL}
color={MainColor.yellow}
onPress={() => router.navigate(`/donation/(tabs)/my-donation`)}
header: () => (
<AppHeader
title="Proses"
left={
<Ionicons
name="close"
size={ICON_SIZE_SMALL}
color={MainColor.yellow}
onPress={() => router.navigate(`/donation/(tabs)/my-donation`)}
/>
}
/>
),
}}
@@ -498,55 +475,51 @@ export default function UserLayout() {
<Stack.Screen
name="donation/[id]/(transaction-flow)/[invoiceId]/success"
options={{
title: "Donasi Berhasil",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Donasi Berhasil" />,
}}
/>
<Stack.Screen
name="donation/[id]/(transaction-flow)/[invoiceId]/failed"
options={{
title: "Donasi Gagal",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Donasi Gagal" />,
}}
/>
{/* ========== End Donation Section ========= */}
{/* ========== Job Section ========= */}
<Stack.Screen
name="job/create"
options={{
title: "Tambah Job",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Tambah Job" />,
}}
/>
<Stack.Screen
name="job/(tabs)"
options={{
title: "Job Vacancy",
// headerLeft: () => <BackButton path="/home" />,
// NOTE: headerLeft di pindahkan ke Tabs Layout
header: () => <AppHeader title="Job Vacancy" left={<BackButton path="/home" />} />,
}}
/>
<Stack.Screen
name="job/[id]/index"
options={{
title: "Detail Job",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Detail Job" />,
}}
/>
<Stack.Screen
name="job/[id]/edit"
options={{
title: "Edit Job",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Edit Job" />,
}}
/>
<Stack.Screen
name="job/[id]/archive"
options={{
title: "Arsip Job",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Arsip Job" />,
}}
/>
@@ -556,78 +529,67 @@ export default function UserLayout() {
<Stack.Screen
name="forum/create"
options={{
title: "Tambah Diskusi",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Tambah Diskusi" />,
}}
/>
<Stack.Screen
name="forum/[id]/edit"
options={{
title: "Edit Diskusi",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Edit Diskusi" />,
}}
/>
<Stack.Screen
name="forum/[id]/forumku"
options={{
title: "Forumku",
headerLeft: () => <BackButton icon={"close"} />,
header: () => <AppHeader title="Forumku" left={<BackButton icon={"close"} />} />,
}}
/>
<Stack.Screen
name="forum/[id]/index"
options={{
title: "Detail",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Detail" />,
}}
/>
<Stack.Screen
name="forum/[id]/report-commentar"
options={{
title: "Laporkan Komentar",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Laporkan Komentar" />,
}}
/>
<Stack.Screen
name="forum/[id]/other-report-commentar"
options={{
title: "Laporkan Komentar",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Laporkan Komentar" />,
}}
/>
<Stack.Screen
name="forum/[id]/report-posting"
options={{
title: "Laporkan Diskusi",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Laporkan Diskusi" />,
}}
/>
<Stack.Screen
name="forum/[id]/other-report-posting"
options={{
title: "Laporkan Diskusi",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Laporkan Diskusi" />,
}}
/>
<Stack.Screen
name="forum/terms"
options={{
title: "Syarat & Ketentuan Forum",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Syarat & Ketentuan Forum" />,
}}
/>
<Stack.Screen
name="forum/[id]/preview-report-posting"
options={{
title: "Laporan Postingan",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Laporan Postingan" />,
}}
/>
<Stack.Screen
name="forum/[id]/preview-report-comment"
options={{
title: "Laporan Komentar",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Laporan Komentar" />,
}}
/>
@@ -635,29 +597,25 @@ export default function UserLayout() {
<Stack.Screen
name="maps/index"
options={{
title: "Maps",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Maps" />,
}}
/>
<Stack.Screen
name="maps/create"
options={{
title: "Tambah Maps",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Tambah Maps" />,
}}
/>
<Stack.Screen
name="maps/[id]/edit"
options={{
title: "Edit Maps",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Edit Maps" />,
}}
/>
<Stack.Screen
name="maps/[id]/custom-pin"
options={{
title: "Custom Pin Maps",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Custom Pin Maps" />,
}}
/>
@@ -665,8 +623,7 @@ export default function UserLayout() {
<Stack.Screen
name="marketplace/index"
options={{
title: "Market Place",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Market Place" />,
}}
/>
</Stack>

View File

@@ -10,6 +10,7 @@ import {
TextCustom,
ViewWrapper,
} from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { apiCollaborationGroup } from "@/service/api-client/api-collaboration";
import { Stack, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useState, useCallback } from "react";
@@ -40,8 +41,7 @@ export default function CollaborationRoomInfo() {
<>
<Stack.Screen
options={{
title: `Info`,
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Info" left={<BackButton />} />,
}}
/>

View File

@@ -1,4 +1,5 @@
import { BackButton } from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import ChatScreen from "@/screens/Collaboration/GroupChatSection";
@@ -12,14 +13,18 @@ export default function CollaborationRoomChat() {
<>
<Stack.Screen
options={{
title: `Proyek ${detail}`,
headerLeft: () => <BackButton />,
headerRight: () => (
<Feather
name="info"
size={ICON_SIZE_SMALL}
color={MainColor.yellow}
onPress={() => router.push(`/collaboration/${id}/${detail}/info`)}
header: () => (
<AppHeader
title={`Proyek ${detail}`}
left={<BackButton />}
right={
<Feather
name="info"
size={ICON_SIZE_SMALL}
color={MainColor.yellow}
onPress={() => router.push(`/collaboration/${id}/${detail}/info`)}
/>
}
/>
),
}}

View File

@@ -6,6 +6,7 @@ import {
MenuDrawerDynamicGrid,
ViewWrapper
} from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection";
import { apiCollaborationGetOne } from "@/service/api-client/api-collaboration";
import { Ionicons } from "@expo/vector-icons";
@@ -38,10 +39,14 @@ export default function CollaborationDetailParticipant() {
<>
<Stack.Screen
options={{
title: "Detail Proyek",
headerLeft: () => <BackButton />,
headerRight: () => (
<DotButton onPress={() => setOpenDrawerParticipant(true)} />
header: () => (
<AppHeader
title="Detail Proyek"
left={<BackButton />}
right={
<DotButton onPress={() => setOpenDrawerParticipant(true)} />
}
/>
),
}}
/>

View File

@@ -8,6 +8,7 @@ import {
Spacing,
ViewWrapper
} from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { IconEdit } from "@/components/_Icon";
import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection";
import {
@@ -66,9 +67,13 @@ export default function CollaborationDetailProjectMain() {
<>
<Stack.Screen
options={{
title: "Proyek Saya",
headerLeft: () => <BackButton />,
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
header: () => (
<AppHeader
title="Proyek Saya"
left={<BackButton />}
right={<DotButton onPress={() => setOpenDrawer(true)} />}
/>
),
}}
/>
<ViewWrapper>

View File

@@ -9,6 +9,7 @@ import {
MenuDrawerDynamicGrid,
ViewWrapper,
} from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { useAuth } from "@/hooks/use-auth";
import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection";
import {
@@ -74,10 +75,14 @@ export default function CollaborationDetail() {
<>
<Stack.Screen
options={{
title: "Detail Proyek",
headerLeft: () => <BackButton />,
headerRight: () => (
<DotButton onPress={() => setOpenDrawerMenu(true)} />
header: () => (
<AppHeader
title="Detail Proyek"
left={<BackButton />}
right={
<DotButton onPress={() => setOpenDrawerMenu(true)} />
}
/>
),
}}
/>

View File

@@ -11,6 +11,7 @@ import {
TextCustom,
ViewWrapper,
} from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { IconEdit } from "@/components/_Icon";
import { IconTrash } from "@/components/_Icon/IconTrash";
import { useAuth } from "@/hooks/use-auth";
@@ -57,12 +58,17 @@ export default function DonationNews() {
<>
<Stack.Screen
options={{
title: "Detail Kabar",
headerLeft: () => <BackButton />,
headerRight: () =>
user?.id === data?.authorId && (
<DotButton onPress={() => setOpenDrawer(true)} />
),
header: () => (
<AppHeader
title="Detail Kabar"
left={<BackButton />}
right={
user?.id === data?.authorId && (
<DotButton onPress={() => setOpenDrawer(true)} />
)
}
/>
),
}}
/>
<ViewWrapper>

View File

@@ -7,6 +7,7 @@ import {
NewWrapper,
Spacing,
} from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { IconEdit, IconNews } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
@@ -97,14 +98,19 @@ export default function DonasiDetailStatus() {
<>
<Stack.Screen
options={{
title: `Detail ${_.startCase(status as string)}`,
headerLeft: () => <BackButton />,
headerRight: () =>
status === "draft" ? (
<DotButton onPress={() => setOpenDrawer(true)} />
) : status === "publish" ? (
<DotButton onPress={() => setOpenDrawerPublish(true)} />
) : null,
header: () => (
<AppHeader
title={`Detail ${_.startCase(status as string)}`}
left={<BackButton />}
right={
status === "draft" ? (
<DotButton onPress={() => setOpenDrawer(true)} />
) : status === "publish" ? (
<DotButton onPress={() => setOpenDrawerPublish(true)} />
) : null
}
/>
),
}}
/>
<NewWrapper

View File

@@ -10,6 +10,7 @@ import {
StackCustom,
ViewWrapper,
} from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { IconNews } from "@/components/_Icon";
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
import { useAuth } from "@/hooks/use-auth";
@@ -90,12 +91,17 @@ export default function DonasiDetailBeranda() {
<>
<Stack.Screen
options={{
title: `Detail Donasi`,
headerLeft: () => <BackButton />,
headerRight: () =>
user?.id === data?.Author?.id ? (
<DotButton onPress={() => setOpenDrawer(true)} />
) : null,
header: () => (
<AppHeader
title="Detail Donasi"
left={<BackButton />}
right={
user?.id === data?.Author?.id ? (
<DotButton onPress={() => setOpenDrawer(true)} />
) : null
}
/>
),
}}
/>
<NewWrapper footerComponent={buttonSection}>

View File

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

View File

@@ -10,6 +10,7 @@ import {
TextCustom,
ViewWrapper,
} from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import LeftButtonCustom from "@/components/Button/BackButton";
import Event_ButtonStatusSection from "@/screens/Event/ButtonStatusSection";
@@ -81,12 +82,17 @@ export default function EventDetailStatus() {
<>
<Stack.Screen
options={{
title: `Detail ${status === "publish" ? "" : status}`,
headerLeft: () => <LeftButtonCustom />,
headerRight: () =>
status === "draft" ? (
<DotButton onPress={() => setOpenDrawer(true)} />
) : null,
header: () => (
<AppHeader
title={`Detail ${status === "publish" ? "" : status}`}
left={<LeftButtonCustom />}
right={
status === "draft" ? (
<DotButton onPress={() => setOpenDrawer(true)} />
) : null
}
/>
),
}}
/>
<ViewWrapper>

View File

@@ -9,7 +9,8 @@ import {
TextCustom,
ViewWrapper,
} from "@/components";
import { AccentColor, MainColor } from "@/constants/color-palet";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import {
apiEventConfirmationAction,
@@ -60,7 +61,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 +114,7 @@ export default function UserEventConfirmation() {
confirmationStart,
confirmationEnd,
null,
"[]"
"[]",
);
// --- [4] Status waktu event (untuk pesan UI) ---
@@ -218,9 +219,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 (
@@ -260,18 +266,20 @@ 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")
// }
// />
// ),
}}
header: () => (
<AppHeader
title="Konfirmasi Event"
left={
<Ionicons
name="arrow-back"
size={20}
color={MainColor.yellow}
onPress={() => router.navigate("/")}
/>
}
/>
),
}}
/>
<ViewWrapper>{handlerReturn()}</ViewWrapper>
</>
@@ -497,7 +505,6 @@ const UserNotParticipan_And_DuringEvent = ({
);
};
// 🟡 ZONA ACARA BERLANGSUN
// User sudah terdaftar & Event sedang berlangsung & user harus konfirmasi
const UserParticipan_And_DuringEvent = ({

View File

@@ -7,6 +7,7 @@ import {
Spacing,
ViewWrapper,
} from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import LeftButtonCustom from "@/components/Button/BackButton";
import Event_BoxDetailPublishSection from "@/screens/Event/BoxDetailPublishSection";
@@ -49,9 +50,13 @@ export default function EventDetailContribution() {
<>
<Stack.Screen
options={{
title: `Detail kontribusi`,
headerLeft: () => <LeftButtonCustom />,
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
header: () => (
<AppHeader
title="Detail kontribusi"
left={<LeftButtonCustom />}
right={<DotButton onPress={() => setOpenDrawer(true)} />}
/>
),
}}
/>
<ViewWrapper>

View File

@@ -6,6 +6,7 @@ import {
ViewWrapper,
Spacing,
} from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import LeftButtonCustom from "@/components/Button/BackButton";
import Event_BoxDetailPublishSection from "@/screens/Event/BoxDetailPublishSection";
@@ -44,9 +45,13 @@ export default function EventDetailHistory() {
<>
<Stack.Screen
options={{
title: `Detail riwayat`,
headerLeft: () => <LeftButtonCustom />,
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
header: () => (
<AppHeader
title="Detail riwayat"
left={<LeftButtonCustom />}
right={<DotButton onPress={() => setOpenDrawer(true)} />}
/>
),
}}
/>
<ViewWrapper>

View File

@@ -8,6 +8,7 @@ import {
MenuDrawerDynamicGrid,
ViewWrapper,
} from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
import LeftButtonCustom from "@/components/Button/BackButton";
@@ -156,9 +157,13 @@ export default function EventDetailPublish() {
<>
<Stack.Screen
options={{
title: `Event Publish`,
headerLeft: () => <BackButton onPress={() => router.back()} />,
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
header: () => (
<AppHeader
title="Event Publish"
left={<BackButton onPress={() => router.back()} />}
right={<DotButton onPress={() => setOpenDrawer(true)} />}
/>
),
}}
/>
<ViewWrapper>

View File

@@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable react-hooks/exhaustive-deps */
import { BasicWrapper, StackCustom, ViewWrapper } from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
import { MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
@@ -117,28 +118,36 @@ export default function Application() {
<>
<Stack.Screen
options={{
title: `HIPMI`,
headerLeft: () =>
data ? (
<Ionicons
name="search"
size={20}
color={MainColor.yellow}
onPress={() => {
router.push("/user-search");
}}
/>
) : (
<CustomSkeleton height={30} width={30} radius={100} />
),
headerRight: () =>
data ? (
<HeaderBell />
) : (
<CustomSkeleton height={30} width={30} radius={100} />
),
header: () => (
<AppHeader
title="HIPMI"
showBack={false}
left={
data ? (
<Ionicons
name="search"
size={20}
color={MainColor.yellow}
onPress={() => {
router.push("/user-search");
}}
/>
) : (
<CustomSkeleton height={30} width={30} radius={100} />
)
}
right={
data ? (
<HeaderBell />
) : (
<CustomSkeleton height={30} width={30} radius={100} />
)
}
/>
),
}}
/>
<ViewWrapper
refreshControl={
<RefreshControl

View File

@@ -10,6 +10,7 @@ import {
TextCustom,
ViewWrapper,
} from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { IconDocument, IconEdit, IconNews } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import { MainColor } from "@/constants/color-palet";
@@ -30,13 +31,13 @@ export default function InvestmentDetailHolding() {
const [openDrawerDraft, setOpenDrawerDraft] = useState(false);
const [openDrawerPublish, setOpenDrawerPublish] = useState(false);
const [data, setData] = useState<any>(null);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id, status])
);
const onLoadData = async () => {
try {
const response = await apiInvestmentGetInvoice({
@@ -44,7 +45,7 @@ export default function InvestmentDetailHolding() {
authorId: user?.id,
category: "invoice",
});
console.log("[DATA]", JSON.stringify(response.data, null, 2));
setData(response.data);
} catch (error) {
@@ -76,14 +77,19 @@ export default function InvestmentDetailHolding() {
<>
<Stack.Screen
options={{
title: `Detail ${_.startCase(status as string)}`,
headerLeft: () => <BackButton />,
headerRight: () =>
status === "draft" ? (
<DotButton onPress={() => setOpenDrawerDraft(true)} />
) : status === "publish" ? (
<DotButton onPress={() => setOpenDrawerPublish(true)} />
) : null,
header: () => (
<AppHeader
title={`Detail ${_.startCase(status as string)}`}
left={<BackButton />}
right={
status === "draft" ? (
<DotButton onPress={() => setOpenDrawerDraft(true)} />
) : status === "publish" ? (
<DotButton onPress={() => setOpenDrawerPublish(true)} />
) : null
}
/>
),
}}
/>

View File

@@ -11,6 +11,7 @@ import {
TextCustom,
ViewWrapper,
} from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { IconTrash } from "@/components/_Icon/IconTrash";
import { useAuth } from "@/hooks/use-auth";
import {
@@ -56,12 +57,17 @@ export default function InvestmentNews() {
<>
<Stack.Screen
options={{
title: "Detail Berita",
headerLeft: () => <BackButton />,
headerRight: () =>
user?.id === data?.authorId && (
<DotButton onPress={() => setOpenDrawer(true)} />
),
header: () => (
<AppHeader
title="Detail Berita"
left={<BackButton />}
right={
user?.id === data?.authorId && (
<DotButton onPress={() => setOpenDrawer(true)} />
)
}
/>
),
}}
/>
<ViewWrapper>

View File

@@ -6,6 +6,7 @@ import {
MenuDrawerDynamicGrid,
ViewWrapper,
} from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { IconDocument, IconEdit, IconNews } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import { MainColor } from "@/constants/color-palet";
@@ -106,14 +107,19 @@ export default function InvestmentDetailStatus() {
<>
<Stack.Screen
options={{
title: `Detail ${_.startCase(status as string)}`,
headerLeft: () => <BackButton />,
headerRight: () =>
status === "draft" ? (
<DotButton onPress={() => setOpenDrawerDraft(true)} />
) : status === "publish" ? (
<DotButton onPress={() => setOpenDrawerPublish(true)} />
) : null,
header: () => (
<AppHeader
title={`Detail ${_.startCase(status as string)}`}
left={<BackButton />}
right={
status === "draft" ? (
<DotButton onPress={() => setOpenDrawerDraft(true)} />
) : status === "publish" ? (
<DotButton onPress={() => setOpenDrawerPublish(true)} />
) : null
}
/>
),
}}
/>

View File

@@ -6,6 +6,7 @@ import {
MenuDrawerDynamicGrid,
ViewWrapper,
} from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { IconDocument, IconEdit, IconNews } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import { MainColor } from "@/constants/color-palet";
@@ -105,14 +106,19 @@ export default function InvestmentDetail() {
<>
<Stack.Screen
options={{
title: `Detail ${_.startCase(status as string)}`,
headerLeft: () => <BackButton />,
headerRight: () =>
status === "draft" ? (
<DotButton onPress={() => setOpenDrawerDraft(true)} />
) : status === "publish" ? (
<DotButton onPress={() => setOpenDrawerPublish(true)} />
) : null,
header: () => (
<AppHeader
title={`Detail ${_.startCase(status as string)}`}
left={<BackButton />}
right={
status === "draft" ? (
<DotButton onPress={() => setOpenDrawerDraft(true)} />
) : status === "publish" ? (
<DotButton onPress={() => setOpenDrawerPublish(true)} />
) : null
}
/>
),
}}
/>

View File

@@ -1,5 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { BackButton } from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { IconHome, IconStatus } from "@/components/_Icon";
import BackButtonFromNotification from "@/components/Button/BackButtonFromNotification";
import { TabsStyles } from "@/styles/tabs-styles";
@@ -7,31 +8,30 @@ import { Ionicons } from "@expo/vector-icons";
import {
router,
Tabs,
useLocalSearchParams,
useNavigation
useLocalSearchParams
} from "expo-router";
import { useLayoutEffect } from "react";
export default function JobTabsLayout() {
const navigation = useNavigation();
const { from, category } = useLocalSearchParams<{
from?: string;
category?: string;
}>();
// Atur header secara dinamis
useLayoutEffect(() => {
navigation.setOptions({
headerLeft: () => (
<BackButtonFromNotification from={from as string} category={category as string} />
),
});
}, [from, router, navigation]);
return (
<>
<Tabs screenOptions={TabsStyles}>
<Tabs
screenOptions={{
...TabsStyles,
header: () => (
<AppHeader
title="Job Vacancy"
left={
<BackButtonFromNotification from={from as string} category={category as string} />
}
/>
),
}}
>
<Tabs.Screen
name="index"
options={{

View File

@@ -9,6 +9,7 @@ import {
StackCustom,
ViewWrapper,
} from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { IconEdit } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import ReportBox from "@/components/Box/ReportBox";
@@ -58,12 +59,17 @@ export default function JobDetailStatus() {
<>
<Stack.Screen
options={{
title: `Detail`,
headerLeft: () => <BackButton />,
headerRight: () =>
status === "draft" ? (
<DotButton onPress={() => setOpenDrawer(true)} />
) : null,
header: () => (
<AppHeader
title="Detail"
left={<BackButton />}
right={
status === "draft" ? (
<DotButton onPress={() => setOpenDrawer(true)} />
) : null
}
/>
),
}}
/>
<ViewWrapper>

View File

@@ -1,365 +1,5 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import {
ActionIcon,
AvatarComp,
BaseBox,
ButtonCenteredOnly,
CenterCustom,
Grid,
InformationBox,
NewWrapper,
SelectCustom,
Spacing,
StackCustom,
TextAreaCustom,
TextCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import { IconPlus } from "@/components/_Icon";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_XLARGE } from "@/constants/constans-value";
import DUMMY_IMAGE from "@/constants/dummy-image-value";
import Portofolio_ButtonCreate from "@/screens/Portofolio/ButtonCreatePortofolio";
import {
apiMasterBidangBisnis,
apiMasterSubBidangBisnis,
} from "@/service/api-client/api-master";
import {
IMasterBidangBisnis,
IMasterSubBidangBisnis,
} from "@/types/Type-Master";
import pickImage from "@/utils/pickImage";
import { Ionicons } from "@expo/vector-icons";
import { Image } from "expo-image";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useEffect, useState } from "react";
import { Text, TouchableOpacity, View } from "react-native";
import PhoneInput, { ICountry } from "react-native-international-phone-number";
import { Avatar } from "react-native-paper";
import { Admin_ScreenPortofolioCreate } from "@/screens/Portofolio/ScreenPortofolioCreate";
export default function PortofolioCreate() {
const { id } = useLocalSearchParams();
const [selectedCountry, setSelectedCountry] = useState<null | ICountry>(null);
const [inputValue, setInputValue] = useState<string>("");
const [data, setData] = useState({
namaBisnis: "",
masterBidangBisnisId: "",
alamatKantor: "",
tlpn: "",
deskripsi: "",
});
const [imageUri, setImageUri] = useState<string | null>(null);
const [bidangBisnis, setBidangBisnis] = useState<IMasterBidangBisnis[]>([]);
const [subBidangBisnis, setSubBidangBisnis] = useState<
IMasterSubBidangBisnis[]
>([]);
const [selectedSubBidang, setSelectedSubBidang] = useState<string[]>([]);
const [listSubBidangSelected, setListSubBidangSelected] = useState([
{
id: "",
},
]);
const [dataMedsos, setDataMedsos] = useState({
facebook: "",
twitter: "",
instagram: "",
youtube: "",
tiktok: "",
});
const [isLoadingCreate, setIsLoadingCreate] = useState(false);
function handleInputValue(phoneNumber: string) {
setInputValue(phoneNumber);
const callingCode = selectedCountry?.callingCode.replace(/^\+/, "") || "";
let fixNumber = inputValue.replace(/\s+/g, "").replace(/^0+/, "");
const realNumber = callingCode + fixNumber;
setData({ ...data, tlpn: realNumber });
}
function handleSelectedCountry(country: ICountry) {
setSelectedCountry(country);
}
useFocusEffect(
useCallback(() => {
onLoadMaster();
onLoadMasterSubBidangBisnis();
}, [])
);
const onLoadMaster = async () => {
try {
const response = await apiMasterBidangBisnis();
setBidangBisnis(response.data);
} catch (error) {
setBidangBisnis([]);
console.log("Error onLoadMasterBidangBisnis", error);
}
};
const onLoadMasterSubBidangBisnis = async () => {
try {
const response = await apiMasterSubBidangBisnis({});
setSubBidangBisnis(response.data);
} catch (error) {
setSubBidangBisnis([]);
console.log("Error onLoadMasterBidangBisnis", error);
}
};
const handlerSelectedSubBidang = ({ id }: { id: string }) => {
const selectedList = subBidangBisnis?.filter(
(item) => (item?.masterBidangBisnisId as any) === id
);
setSelectedSubBidang(selectedList as any[]);
};
return (
<NewWrapper
footerComponent={
<Portofolio_ButtonCreate
id={id as string}
data={data}
dataMedsos={dataMedsos}
imageUri={imageUri}
subBidangSelected={listSubBidangSelected}
isLoadingCreate={isLoadingCreate}
setIsLoadingCreate={setIsLoadingCreate}
/>
}
>
{/* <TextCustom>Portofolio Create {id}</TextCustom> */}
<StackCustom gap={"xs"}>
<InformationBox text="Lengkapi data bisnis anda." />
<TextInputCustom
required
label="Nama Bisnis"
placeholder="Masukkan nama bisnis"
onChangeText={(value: any) => setData({ ...data, namaBisnis: value })}
/>
<SelectCustom
label="Bidang Usaha"
required
data={bidangBisnis.map((item) => ({
label: item.name,
value: item.id,
}))}
value={data.masterBidangBisnisId}
onChange={(value) => {
const isSameBidang = data.masterBidangBisnisId === value;
if (!isSameBidang) {
setListSubBidangSelected([{ id: "" }]);
}
setData({ ...(data as any), masterBidangBisnisId: value });
handlerSelectedSubBidang({ id: value as string });
}}
/>
{listSubBidangSelected.map((item, index) => (
<SelectCustom
key={index}
disabled={data.masterBidangBisnisId === ""}
label="Sub Bidang Usaha"
required
data={_.map(selectedSubBidang as any)
.filter((option: any) => {
const selectedValues = listSubBidangSelected.map((s) => s.id);
return (
option.id === item.id || // biarkan tetap muncul kalau ini valuenya sendiri
!selectedValues.includes(option.id)
);
})
.map((e: any) => ({
value: e.id,
label: e.name,
}))}
value={item.id || null}
onChange={(value) => {
const list = _.clone(listSubBidangSelected);
list[index].id = value as any;
setListSubBidangSelected(list);
}}
/>
))}
<CenterCustom>
<View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
<ActionIcon
disabled={
selectedSubBidang.length === listSubBidangSelected.length
}
onPress={() => {
setListSubBidangSelected([
...listSubBidangSelected,
{ id: "" },
]);
}}
icon={
<Ionicons
name="add-circle-outline"
size={ICON_SIZE_XLARGE}
color={MainColor.black}
/>
}
size="xl"
/>
<ActionIcon
disabled={listSubBidangSelected.length <= 1}
onPress={() => {
const list = _.clone(listSubBidangSelected);
list.pop();
setListSubBidangSelected(list);
}}
icon={
<Ionicons
name="remove-circle-outline"
size={ICON_SIZE_XLARGE}
color={MainColor.black}
/>
}
size="xl"
/>
</View>
</CenterCustom>
<Spacing />
{/* <SelectCustom
label="Bidang Usaha"
required
data={bidangBisnis.map((item) => ({
label: item.name,
value: item.id,
}))}
value={null}
onChange={(value) => {
setData({ ...(data as any), masterBidangBisnisId: value });
handlerSelectedSubBidang({ id: value as string });
}}
/> */}
{/* <ButtonCenteredOnly
onPress={() => {
setListSubBidangSelected([...listSubBidangSelected, { id: "" }]);
}}
>
Tambah Pilihan
</ButtonCenteredOnly>
<Spacing /> */}
{/* <TextCustom>{JSON.stringify(bidangBisnis, null, 2)}</TextCustom> */}
<View>
<View style={{ flexDirection: "row", alignItems: "center" }}>
<TextCustom semiBold style={{ color: MainColor.white_gray }}>
Nomor Telepon
</TextCustom>
<Text style={{ color: "red" }}> *</Text>
</View>
<Spacing height={5} />
<PhoneInput
value={inputValue}
onChangePhoneNumber={handleInputValue}
selectedCountry={selectedCountry}
onChangeSelectedCountry={handleSelectedCountry}
defaultCountry="ID"
placeholder="xxx-xxx-xxx"
/>
</View>
<Spacing />
<TextInputCustom
required
label="Alamat Bisnis"
placeholder="Masukkan alamat bisnis"
onChangeText={(value: any) =>
setData({ ...data, alamatKantor: value })
}
/>
<TextAreaCustom
label="Deskripsi Bisnis"
placeholder="Masukkan deskripsi bisnis"
value={data.deskripsi}
onChangeText={(value: any) => setData({ ...data, deskripsi: value })}
autosize
minRows={2}
maxRows={5}
required
showCount
maxLength={1000}
/>
<Spacing />
{/* Logo */}
<InformationBox text="Upload logo bisnis anda untuk di tampilaka pada portofolio." />
<CenterCustom>
<Avatar.Image
source={imageUri ? { uri: imageUri } : DUMMY_IMAGE.dummy_image}
size={200}
/>
</CenterCustom>
<Spacing />
<ButtonCenteredOnly
icon="upload"
onPress={() => {
pickImage({
setImageUri,
});
}}
>
Upload
</ButtonCenteredOnly>
<Spacing height={40} />
{/* Social Media */}
<InformationBox text="Isi hanya pada sosial media yang anda miliki." />
<TextInputCustom
label="Tiktok"
placeholder="Masukkan username tiktok"
onChangeText={(value: any) =>
setDataMedsos({ ...dataMedsos, tiktok: value })
}
/>
<TextInputCustom
label="Facebook"
placeholder="Masukkan username facebook"
onChangeText={(value: any) =>
setDataMedsos({ ...dataMedsos, facebook: value })
}
/>
<TextInputCustom
label="Instagram"
placeholder="Masukkan username instagram"
onChangeText={(value: any) =>
setDataMedsos({ ...dataMedsos, instagram: value })
}
/>
<TextInputCustom
label="Twitter"
placeholder="Masukkan username twitter"
onChangeText={(value: any) =>
setDataMedsos({ ...dataMedsos, twitter: value })
}
/>
<TextInputCustom
label="Youtube"
placeholder="Masukkan username youtube"
onChangeText={(value: any) =>
setDataMedsos({ ...dataMedsos, youtube: value })
}
/>
{/* <Spacing /> */}
</StackCustom>
</NewWrapper>
);
return <Admin_ScreenPortofolioCreate />;
}

View File

@@ -8,6 +8,7 @@ import {
StackCustom,
TextCustom,
} from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import LeftButtonCustom from "@/components/Button/BackButton";
import GridTwoView from "@/components/_ShareComponent/GridTwoView";
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
@@ -72,20 +73,23 @@ export default function Portofolio() {
{/* Header */}
<Stack.Screen
options={{
title: "Portofolio",
headerLeft: () => <LeftButtonCustom />,
headerRight: () =>
data?.Profile?.id !== profileId ? null : (
<TouchableOpacity onPress={openDrawer}>
<Ionicons
name="ellipsis-vertical"
size={20}
color={MainColor.yellow}
/>
</TouchableOpacity>
),
headerStyle: GStyles.headerStyle,
headerTitleStyle: GStyles.headerTitleStyle,
header: () => (
<AppHeader
title="Portofolio"
left={<LeftButtonCustom />}
right={
data?.Profile?.id !== profileId ? null : (
<TouchableOpacity onPress={openDrawer}>
<Ionicons
name="ellipsis-vertical"
size={20}
color={MainColor.yellow}
/>
</TouchableOpacity>
)
}
/>
),
}}
/>
<ViewWrapper>

View File

@@ -1,5 +1,5 @@
import AppHeader from "@/components/_ShareComponent/AppHeader";
import LeftButtonCustom from "@/components/Button/BackButton";
import { HeaderStyles } from "@/styles/header-styles";
import { Stack } from "expo-router";
export default function PortofolioLayout() {
@@ -7,8 +7,9 @@ export default function PortofolioLayout() {
<>
<Stack
screenOptions={{
...HeaderStyles,
headerLeft: () => <LeftButtonCustom />,
header: () => (
<AppHeader title="Portofolio" left={<LeftButtonCustom />} />
),
}}
>
{/* <Stack.Screen name="[id]/index" options={{ title: "Portofolio" }} /> */}

View File

@@ -1,5 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { NewWrapper, StackCustom } from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
import LeftButtonCustom from "@/components/Button/BackButton";
import DrawerCustom from "@/components/Drawer/DrawerCustom";
@@ -101,18 +102,20 @@ export default function Profile() {
<>
<Stack.Screen
options={{
title: `Profile`,
headerLeft: () => <LeftButtonCustom />,
headerRight: () => (
<ButtonnDot
id={id as string}
openDrawer={openDrawer}
isUserCheck={isUserCheck()}
logout={logout}
header: () => (
<AppHeader
title="Profile"
left={<LeftButtonCustom />}
right={
<ButtonnDot
id={id as string}
openDrawer={openDrawer}
isUserCheck={isUserCheck()}
logout={logout}
/>
}
/>
),
headerStyle: GStyles.headerStyle,
headerTitleStyle: GStyles.headerTitleStyle,
}}
/>
{/* Main View */}

View File

@@ -1,47 +1,39 @@
import { BackButton } from "@/components";
import { GStyles } from "@/styles/global-styles";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { Stack } from "expo-router";
export default function ProfileLayout() {
return (
<>
<Stack
screenOptions={{
headerStyle: GStyles.headerStyle,
headerTitleStyle: GStyles.headerTitleStyle,
headerTitleAlign: "center",
headerBackButtonDisplayMode: "minimal",
}}
>
<Stack>
{/* <Stack.Screen name="[id]/index" options={{ headerShown: false }} /> */}
<Stack.Screen
name="[id]/edit"
options={{ title: "Edit Profile", headerLeft: () => <BackButton /> }}
options={{ header: () => <AppHeader title="Edit Profile" /> }}
/>
<Stack.Screen
name="[id]/update-photo"
options={{ title: "Update Foto", headerLeft: () => <BackButton /> }}
options={{ header: () => <AppHeader title="Update Foto" /> }}
/>
<Stack.Screen
name="[id]/update-background"
options={{
title: "Update Latar Belakang",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Update Latar Belakang" />,
}}
/>
<Stack.Screen
name="create"
options={{ title: "Buat Profile", headerBackVisible: false }}
options={{ headerBackVisible: false }}
/>
<Stack.Screen
name="[id]/blocked-list"
options={{ title: "Daftar Blokir", headerLeft: () => <BackButton /> }}
options={{ header: () => <AppHeader title="Daftar Blokir" /> }}
/>
<Stack.Screen
name="[id]/detail-blocked"
options={{ title: "Detail Blokir", headerLeft: () => <BackButton /> }}
options={{ header: () => <AppHeader title="Detail Blokir" /> }}
/>
</Stack>
</>

View File

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

View File

@@ -12,6 +12,7 @@ import {
TextCustom,
ViewWrapper,
} from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { IconArchive, IconContribution, IconEdit } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import ReportBox from "@/components/Box/ReportBox";
@@ -103,14 +104,19 @@ export default function VotingDetailStatus() {
<>
<Stack.Screen
options={{
title: `Detail`,
headerLeft: () => <BackButton />,
headerRight: () =>
status === "draft" ? (
<DotButton onPress={() => setOpenDrawerDraft(true)} />
) : status === "publish" ? (
<DotButton onPress={() => setOpenDrawerPublish(true)} />
) : null,
header: () => (
<AppHeader
title="Detail"
left={<BackButton />}
right={
status === "draft" ? (
<DotButton onPress={() => setOpenDrawerDraft(true)} />
) : status === "publish" ? (
<DotButton onPress={() => setOpenDrawerPublish(true)} />
) : null
}
/>
),
}}
/>
<ViewWrapper>

View File

@@ -9,6 +9,7 @@ import {
Spacing,
ViewWrapper,
} from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { IconContribution } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import { useAuth } from "@/hooks/use-auth";
@@ -81,10 +82,14 @@ export default function VotingDetailContribution() {
<>
<Stack.Screen
options={{
title: "Detail Kontribusi",
headerLeft: () => <BackButton />,
headerRight: () => (
<DotButton onPress={() => setOpenDrawerPublish(true)} />
header: () => (
<AppHeader
title="Detail Kontribusi"
left={<BackButton />}
right={
<DotButton onPress={() => setOpenDrawerPublish(true)} />
}
/>
),
}}
/>

View File

@@ -9,6 +9,7 @@ import {
Spacing,
ViewWrapper,
} from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { IconContribution } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import { useAuth } from "@/hooks/use-auth";
@@ -82,10 +83,14 @@ export default function VotingDetailHistory() {
<>
<Stack.Screen
options={{
title: "Riwayat Voting",
headerLeft: () => <BackButton />,
headerRight: () => (
<DotButton onPress={() => setOpenDrawerPublish(true)} />
header: () => (
<AppHeader
title="Riwayat Voting"
left={<BackButton />}
right={
<DotButton onPress={() => setOpenDrawerPublish(true)} />
}
/>
),
}}
/>

View File

@@ -11,6 +11,7 @@ import {
StackCustom,
ViewWrapper,
} from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { IconArchive, IconContribution } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
@@ -142,10 +143,14 @@ export default function VotingDetail() {
<>
<Stack.Screen
options={{
title: `Detail Voting`,
headerLeft: () => <BackButton />,
headerRight: () => (
<DotButton onPress={() => setOpenDrawerPublish(true)} />
header: () => (
<AppHeader
title="Detail Voting"
left={<BackButton />}
right={
<DotButton onPress={() => setOpenDrawerPublish(true)} />
}
/>
),
}}
/>

View File

@@ -1,8 +1,8 @@
import { BackButton } from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
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 { Stack } from "expo-router";
export default function ApplicationLayout() {
@@ -20,7 +20,7 @@ export default function ApplicationLayout() {
function ApplicationStack() {
return (
<>
<Stack screenOptions={HeaderStyles}>
<Stack>
<Stack.Screen name="(user)" options={{ headerShown: false }} />
<Stack.Screen name="admin" options={{ headerShown: false }} />
@@ -28,8 +28,7 @@ function ApplicationStack() {
<Stack.Screen
name="(image)/take-picture/[id]/index"
options={{
title: "Ambil Gambar",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Ambil Gambar" />,
}}
/>
@@ -37,8 +36,7 @@ function ApplicationStack() {
<Stack.Screen
name="(image)/preview-image/[id]/index"
options={{
title: "Preview Gambar",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Preview Gambar" />,
}}
/>
</Stack>

View File

@@ -6,6 +6,7 @@ import {
StackCustom,
TextCustom,
} from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import DrawerAdmin from "@/components/Drawer/DrawerAdmin";
import NavbarMenu from "@/components/Drawer/NavbarMenu";
import NavbarMenu_V2 from "@/components/Drawer/NavbarMenu_V2";
@@ -17,6 +18,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,
@@ -34,12 +36,28 @@ import { useState } from "react";
export default function AdminLayout() {
const [openDrawerNavbar, setOpenDrawerNavbar] = useState(false);
const [openDrawerUser, setOpenDrawerUser] = useState(false);
// const [user, setUser] = useState(null);
const { logout, user } = useAuth();
console.log("[USER LAYOUT]", JSON.stringify(user, null, 2));
const headerLeft = () => (
<Ionicons
name="menu"
size={ICON_SIZE_XLARGE}
color={MainColor.white}
onPress={() => setOpenDrawerNavbar(true)}
/>
);
const headerRight = () => (
<FontAwesome6
name="circle-user"
size={ICON_SIZE_MEDIUM}
color={MainColor.white}
onPress={() => setOpenDrawerUser(true)}
/>
);
return (
<>
<Stack
@@ -51,20 +69,33 @@ export default function AdminLayout() {
contentStyle: {
borderBottomColor: AccentColor.blue,
},
headerLeft: () => (
<Ionicons
name="menu"
size={ICON_SIZE_XLARGE}
color={MainColor.white}
onPress={() => setOpenDrawerNavbar(true)}
/>
),
headerRight: () => (
<FontAwesome6
name="circle-user"
size={ICON_SIZE_MEDIUM}
color={MainColor.white}
onPress={() => setOpenDrawerUser(true)}
// headerLeft: () => (
// <Ionicons
// name="menu"
// size={ICON_SIZE_XLARGE}
// color={MainColor.white}
// onPress={() => setOpenDrawerNavbar(true)}
// />
// ),
// headerRight: () => (
// <FontAwesome6
// name="circle-user"
// size={ICON_SIZE_MEDIUM}
// color={MainColor.white}
// onPress={() => setOpenDrawerUser(true)}
// />
// ),
header: () => (
<AppHeader
title="HIPMI DASHBOARD"
showBack={false}
left={headerLeft()}
right={headerRight()}
/>
),
}}

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

@@ -1,4 +1,5 @@
import { BackButton, StackCustom, TextCustom, ViewWrapper } from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { router, Stack } from "expo-router";
export default function NotFoundScreen() {
@@ -15,7 +16,7 @@ export default function NotFoundScreen() {
return (
<>
<Stack.Screen
options={{ headerShown: true, title: "", headerLeft: () => <BackButton onPress={() => handleBack()} /> }}
options={{ header: () => <AppHeader title="" left={<BackButton onPress={() => handleBack()} />} /> }}
/>
<ViewWrapper>
<StackCustom

View File

@@ -12,14 +12,15 @@ export default function BackButtonFromNotification({
return (
<>
<BackButton
onPress={() => {
if (from === "notifications") {
router.replace(`/notifications?category=${category}`);
router.push(`/notifications?category=${category}`);
} else {
if (from) {
router.replace(`/${from}` as any);
router.back();
} else {
router.navigate("/home");
router.back();
}
}
}}

View File

@@ -0,0 +1,114 @@
import { MainColor } from "@/constants/color-palet";
import { Platform, StyleSheet, Text, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { BackButton } from "..";
type Props = {
title: string;
right?: React.ReactNode;
showBack?: boolean;
onPressLeft?: () => void;
left?: React.ReactNode;
};
export default function AppHeader({
title,
right,
showBack = true,
onPressLeft,
left,
}: Props) {
const insets = useSafeAreaInsets();
// iOS 16+ detection (Dynamic Island) - insets.top > 47 indicates Dynamic Island
const isIOS26Plus =
Platform.OS === "ios" && insets.top > 47;
// Dynamic padding berdasarkan platform dan iOS version
const paddingTop =
Platform.OS === "ios"
? isIOS26Plus
? insets.top - 10
: insets.top
: 10;
const paddingBottom = Platform.OS === "ios" ? 8 : 13;
return (
<View
style={[
{
backgroundColor: MainColor.darkblue,
paddingTop,
paddingBottom,
},
]}
pointerEvents="box-none"
>
{/* Header Container dengan absolute positioning untuk title center */}
<View style={styles.headerApp} pointerEvents="box-none">
{/* Left Section - Absolute Left */}
<View style={styles.headerLeft}>
{showBack ? (
<BackButton onPress={onPressLeft} />
) : left ? (
left
) : (
<View style={styles.placeholder} />
)}
</View>
{/* Title - Absolute Center */}
<View style={styles.headerCenter}>
<Text
style={styles.headerTitle}
numberOfLines={1}
ellipsizeMode="tail"
>
{title ? title.charAt(0).toUpperCase() + title.slice(1) : ""}
</Text>
</View>
{/* Right Section - Absolute Right */}
<View style={styles.headerRight}>
{right}
</View>
</View>
</View>
);
}
const styles = StyleSheet.create({
headerApp: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
paddingHorizontal: 16,
height: 44, // Fixed height untuk consistency
},
headerLeft: {
position: "absolute",
left: 16,
zIndex: 1,
},
headerCenter: {
flex: 1,
alignItems: "center",
justifyContent: "center",
},
headerRight: {
position: "absolute",
right: 16,
zIndex: 1,
},
placeholder: {
width: 40,
height: 40,
},
headerTitle: {
color: MainColor.yellow,
fontSize: 18,
fontWeight: "600",
textAlign: "center",
},
});

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)/(user)/portofolio/[id]/create.tsx
Folder tujuan: screens/Portofolio
Nama file utama: ScreenPortofolioCreate.tsx
Nama function utama: Admin_ScreenPortofolioCreate
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

@@ -180,6 +180,23 @@
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) */,
7B5AE3770142492D84AEAAB3 /* Remove signature files (Xcode workaround) */,
0F1E3753571D42AB932C4F72 /* Remove signature files (Xcode workaround) */,
B020BDE42E304FBD99BD2279 /* Remove signature files (Xcode workaround) */,
6ECA5F81B0BC4C70A91BE265 /* Remove signature files (Xcode workaround) */,
44265583B67C48F2A24BA93E /* Remove signature files (Xcode workaround) */,
D5CA1D54CFF74AB4B8B5B583 /* Remove signature files (Xcode workaround) */,
97C01196E2194AF5A13C7773 /* Remove signature files (Xcode workaround) */,
EB19F4C53C8B434CBAD50897 /* Remove signature files (Xcode workaround) */,
);
buildRules = (
);
@@ -941,6 +958,295 @@
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\";
";
};
7B5AE3770142492D84AEAAB3 /* 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\";
";
};
0F1E3753571D42AB932C4F72 /* 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\";
";
};
B020BDE42E304FBD99BD2279 /* 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\";
";
};
6ECA5F81B0BC4C70A91BE265 /* 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\";
";
};
44265583B67C48F2A24BA93E /* 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\";
";
};
D5CA1D54CFF74AB4B8B5B583 /* 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\";
";
};
97C01196E2194AF5A13C7773 /* 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\";
";
};
EB19F4C53C8B434CBAD50897 /* 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>5</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

@@ -9,6 +9,7 @@ import {
StackCustom,
TextCustom,
} from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { IconPlus } from "@/components/_Icon";
import { IconDot } from "@/components/_Icon/IconComponent";
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
@@ -121,12 +122,16 @@ export default function Admin_ScreenNotification() {
<>
<Stack.Screen
options={{
title: "Admin Notifikasi",
headerLeft: () => <BackButton />,
headerRight: () => (
<IconDot
color={MainColor.yellow}
onPress={() => setOpenDrawer(true)}
header: () => (
<AppHeader
title="Admin Notifikasi"
left={<BackButton />}
right={
<IconDot
color={MainColor.yellow}
onPress={() => setOpenDrawer(true)}
/>
}
/>
),
}}

View File

@@ -11,6 +11,7 @@ import {
} from "@/components";
import { IconPlus } from "@/components/_Icon";
import { IconDot } from "@/components/_Icon/IconComponent";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL, PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
import { createPaginationComponents } from "@/helpers/paginationHelpers";
@@ -169,12 +170,16 @@ export default function Admin_ScreenNotification2() {
<>
<Stack.Screen
options={{
title: "Admin Notifikasi",
headerLeft: () => <BackButton />,
headerRight: () => (
<IconDot
color={MainColor.yellow}
onPress={() => setOpenDrawer(true)}
// title: "Admin Notifikasi",
header: () => (
<AppHeader
title="Admin Notifikasi"
right={
<IconDot
color={MainColor.yellow}
onPress={() => setOpenDrawer(true)}
/>
}
/>
),
}}

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,5 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { BackButton, DrawerCustom, MenuDrawerDynamicGrid } from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { IconPlus } from "@/components/_Icon";
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
import { MainColor } from "@/constants/color-palet";
@@ -52,8 +53,12 @@ export default function Donation_ScreenListOfNews({
<>
<Stack.Screen
options={{
title: "Daftar Kabar",
headerLeft: () => <BackButton />,
header: () => (
<AppHeader
title="Daftar Kabar"
left={<BackButton />}
/>
),
}}
/>
<NewWrapper

View File

@@ -5,6 +5,7 @@ import {
DrawerCustom,
MenuDrawerDynamicGrid,
} from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { IconPlus } from "@/components/_Icon";
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
@@ -61,9 +62,13 @@ export default function Donation_ScreenRecapOfNews({
<>
<Stack.Screen
options={{
title: "Rekap Kabar",
headerLeft: () => <BackButton />,
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
header: () => (
<AppHeader
title="Rekap Kabar"
left={<BackButton />}
right={<DotButton onPress={() => setOpenDrawer(true)} />}
/>
),
}}
/>
<NewWrapper

View File

@@ -7,6 +7,7 @@ import {
LoaderCustom,
TextCustom,
} from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { useAuth } from "@/hooks/use-auth";
import { apiForumGetAll } from "@/service/api-client/api-forum";
import { apiUser } from "@/service/api-client/api-user";
@@ -54,13 +55,17 @@ export default function Forum_ViewBeranda() {
<>
<Stack.Screen
options={{
title: "Forum",
headerLeft: () => <BackButton />,
headerRight: () => (
<AvatarComp
fileId={dataUser?.Profile?.imageId}
size="base"
href={`/forum/${user?.id}/forumku`}
header: () => (
<AppHeader
title="Forum"
left={<BackButton />}
right={
<AvatarComp
fileId={dataUser?.Profile?.imageId}
size="base"
href={`/forum/${user?.id}/forumku`}
/>
}
/>
),
}}

View File

@@ -8,6 +8,7 @@ import {
StackCustom,
TextCustom, // ← gunakan NewWrapper yang sudah diperbaiki
} from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import SkeletonCustom from "@/components/_ShareComponent/SkeletonCustom";
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
import { useAuth } from "@/hooks/use-auth";
@@ -155,13 +156,17 @@ export default function Forum_ViewBeranda2() {
{/* 🔹 Header Navigation */}
<Stack.Screen
options={{
title: "Forum",
headerLeft: () => <BackButton />,
headerRight: () => (
<AvatarComp
fileId={dataUser?.Profile?.imageId}
size="base"
href={`/forum/${user?.id}/forumku`}
header: () => (
<AppHeader
title="Forum"
left={<BackButton />}
right={
<AvatarComp
fileId={dataUser?.Profile?.imageId}
size="base"
href={`/forum/${user?.id}/forumku`}
/>
}
/>
),
}}

View File

@@ -4,6 +4,7 @@ import {
FloatingButton,
SearchInput,
} from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
import { MainColor } from "@/constants/color-palet";
import { createPaginationComponents } from "@/helpers/paginationHelpers";
@@ -84,18 +85,22 @@ export default function Forum_ViewBeranda3() {
<>
<Stack.Screen
options={{
title: "Forum",
headerLeft: () => <BackButton />,
headerRight: () => (
<TouchableOpacity
onPress={() => router.navigate(`/forum/${user?.id}/forumku`)}
>
<AvatarComp
fileId={dataUser?.Profile?.imageId}
size="base"
href={`/forum/${user?.id}/forumku`}
/>
</TouchableOpacity>
header: () => (
<AppHeader
title="Forum"
left={<BackButton />}
right={
<TouchableOpacity
onPress={() => router.navigate(`/forum/${user?.id}/forumku`)}
>
<AvatarComp
fileId={dataUser?.Profile?.imageId}
size="base"
href={`/forum/${user?.id}/forumku`}
/>
</TouchableOpacity>
}
/>
),
}}
/>

View File

@@ -6,6 +6,7 @@ import {
DrawerCustom,
MenuDrawerDynamicGrid
} from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { IconEdit } from "@/components/_Icon";
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
import { MainColor } from "@/constants/color-palet";
@@ -125,14 +126,18 @@ export default function Investment_ScreenRecapOfDocument() {
<>
<Stack.Screen
options={{
title: "Rekap Dokumen",
headerLeft: () => <BackButton />,
headerRight: () => (
<DotButton
onPress={() => {
setOpenDrawer(true);
setOpenDrawerBox(false);
}}
header: () => (
<AppHeader
title="Rekap Dokumen"
left={<BackButton />}
right={
<DotButton
onPress={() => {
setOpenDrawer(true);
setOpenDrawerBox(false);
}}
/>
}
/>
),
}}

View File

@@ -7,6 +7,7 @@ import {
MenuDrawerDynamicGrid,
TextCustom,
} from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { IconPlus } from "@/components/_Icon";
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
import { usePagination } from "@/hooks/use-pagination";
@@ -64,9 +65,13 @@ export default function Investment_ScreenListOfNews({
<>
<Stack.Screen
options={{
title: "Daftar Berita",
headerLeft: () => <BackButton />,
// headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
header: () => (
<AppHeader
title="Daftar Berita"
left={<BackButton />}
// headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
/>
),
}}
/>

View File

@@ -9,6 +9,7 @@ import {
Spacing,
TextCustom,
} from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { IconPlus } from "@/components/_Icon";
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
import { usePagination } from "@/hooks/use-pagination";
@@ -66,9 +67,13 @@ export default function Investment_ScreenRecapOfNews({
<>
<Stack.Screen
options={{
title: "Rekap Berita",
headerLeft: () => <BackButton />,
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
header: () => (
<AppHeader
title="Rekap Berita"
left={<BackButton />}
right={<DotButton onPress={() => setOpenDrawer(true)} />}
/>
),
}}
/>
<NewWrapper

View File

@@ -11,6 +11,7 @@ import {
StackCustom,
TextCustom,
} from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { IconDot } from "@/components/_Icon/IconComponent";
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
import NoDataText from "@/components/_ShareComponent/NoDataText";
@@ -217,12 +218,16 @@ export default function ScreenNotification_V1() {
<>
<Stack.Screen
options={{
title: "Notifikasi",
headerLeft: () => <BackButton />,
headerRight: () => (
<IconDot
color={MainColor.yellow}
onPress={() => setOpenDrawer(true)}
header: () => (
<AppHeader
title="Notifikasi"
left={<BackButton />}
right={
<IconDot
color={MainColor.yellow}
onPress={() => setOpenDrawer(true)}
/>
}
/>
),
}}

View File

@@ -10,6 +10,7 @@ import {
StackCustom,
TextCustom,
} from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { IconDot } from "@/components/_Icon/IconComponent";
import { AccentColor, MainColor } from "@/constants/color-palet";
import {
@@ -182,12 +183,16 @@ export default function ScreenNotification_V2() {
<>
<Stack.Screen
options={{
title: "Notifikasi",
headerLeft: () => <BackButton />,
headerRight: () => (
<IconDot
color={MainColor.yellow}
onPress={() => setOpenDrawer(true)}
header: () => (
<AppHeader
title="Notifikasi"
left={<BackButton />}
right={
<IconDot
color={MainColor.yellow}
onPress={() => setOpenDrawer(true)}
/>
}
/>
),
}}

View File

@@ -0,0 +1,340 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import {
ActionIcon,
ButtonCenteredOnly,
CenterCustom,
InformationBox,
NewWrapper,
SelectCustom,
Spacing,
StackCustom,
TextAreaCustom,
TextCustom,
TextInputCustom,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_XLARGE } from "@/constants/constans-value";
import DUMMY_IMAGE from "@/constants/dummy-image-value";
import Portofolio_ButtonCreate from "@/screens/Portofolio/ButtonCreatePortofolio";
import {
apiMasterBidangBisnis,
apiMasterSubBidangBisnis,
} from "@/service/api-client/api-master";
import {
IMasterBidangBisnis,
IMasterSubBidangBisnis,
} from "@/types/Type-Master";
import pickImage from "@/utils/pickImage";
import { Ionicons } from "@expo/vector-icons";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { Text, View } from "react-native";
import PhoneInput, { ICountry } from "react-native-international-phone-number";
import { Avatar } from "react-native-paper";
export function Admin_ScreenPortofolioCreate() {
const { id } = useLocalSearchParams();
const [selectedCountry, setSelectedCountry] = useState<null | ICountry>(null);
const [inputValue, setInputValue] = useState<string>("");
const [data, setData] = useState({
namaBisnis: "",
masterBidangBisnisId: "",
alamatKantor: "",
tlpn: "",
deskripsi: "",
});
const [imageUri, setImageUri] = useState<string | null>(null);
const [bidangBisnis, setBidangBisnis] = useState<IMasterBidangBisnis[]>([]);
const [subBidangBisnis, setSubBidangBisnis] = useState<
IMasterSubBidangBisnis[]
>([]);
const [selectedSubBidang, setSelectedSubBidang] = useState<string[]>([]);
const [listSubBidangSelected, setListSubBidangSelected] = useState([
{
id: "",
},
]);
const [dataMedsos, setDataMedsos] = useState({
facebook: "",
twitter: "",
instagram: "",
youtube: "",
tiktok: "",
});
const [isLoadingCreate, setIsLoadingCreate] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadMaster();
onLoadMasterSubBidangBisnis();
}, [])
);
const onLoadMaster = async () => {
try {
const response = await apiMasterBidangBisnis();
setBidangBisnis(response.data);
} catch (error) {
setBidangBisnis([]);
console.log("Error onLoadMasterBidangBisnis", error);
}
};
const onLoadMasterSubBidangBisnis = async () => {
try {
const response = await apiMasterSubBidangBisnis({});
setSubBidangBisnis(response.data);
} catch (error) {
setSubBidangBisnis([]);
console.log("Error onLoadMasterSubBidangBisnis", error);
}
};
const handlerSelectedSubBidang = ({ id }: { id: string }) => {
const selectedList = subBidangBisnis?.filter(
(item) => (item?.masterBidangBisnisId as any) === id
);
setSelectedSubBidang(selectedList as any[]);
};
const handleInputValue = (phoneNumber: string) => {
setInputValue(phoneNumber);
const callingCode = selectedCountry?.callingCode.replace(/^\+/, "") || "";
let fixNumber = inputValue.replace(/\s+/g, "").replace(/^0+/, "");
const realNumber = callingCode + fixNumber;
setData({ ...data, tlpn: realNumber });
};
const handleSelectedCountry = (country: ICountry) => {
setSelectedCountry(country);
};
return (
<NewWrapper
footerComponent={
<Portofolio_ButtonCreate
id={id as string}
data={data}
dataMedsos={dataMedsos}
imageUri={imageUri}
subBidangSelected={listSubBidangSelected}
isLoadingCreate={isLoadingCreate}
setIsLoadingCreate={setIsLoadingCreate}
/>
}
>
<StackCustom gap="xs">
<InformationBox text="Lengkapi data bisnis anda." />
<TextInputCustom
required
label="Nama Bisnis"
placeholder="Masukkan nama bisnis"
onChangeText={(value: any) => setData({ ...data, namaBisnis: value })}
/>
<SelectCustom
label="Bidang Usaha"
required
data={bidangBisnis.map((item) => ({
label: item.name,
value: item.id,
}))}
value={data.masterBidangBisnisId}
onChange={(value) => {
const isSameBidang = data.masterBidangBisnisId === value;
if (!isSameBidang) {
setListSubBidangSelected([{ id: "" }]);
}
setData({ ...(data as any), masterBidangBisnisId: value });
handlerSelectedSubBidang({ id: value as string });
}}
/>
{listSubBidangSelected.map((item, index) => (
<SelectCustom
key={index}
disabled={data.masterBidangBisnisId === ""}
label="Sub Bidang Usaha"
required
data={_.map(selectedSubBidang as any)
.filter((option: any) => {
const selectedValues = listSubBidangSelected.map((s) => s.id);
return (
option.id === item.id ||
!selectedValues.includes(option.id)
);
})
.map((e: any) => ({
value: e.id,
label: e.name,
}))}
value={item.id || null}
onChange={(value) => {
const list = _.clone(listSubBidangSelected);
list[index].id = value as any;
setListSubBidangSelected(list);
}}
/>
))}
<CenterCustom>
<View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
<ActionIcon
disabled={selectedSubBidang.length === listSubBidangSelected.length}
onPress={() => {
setListSubBidangSelected([
...listSubBidangSelected,
{ id: "" },
]);
}}
icon={
<Ionicons
name="add-circle-outline"
size={ICON_SIZE_XLARGE}
color={MainColor.black}
/>
}
size="xl"
/>
<ActionIcon
disabled={listSubBidangSelected.length <= 1}
onPress={() => {
const list = _.clone(listSubBidangSelected);
list.pop();
setListSubBidangSelected(list);
}}
icon={
<Ionicons
name="remove-circle-outline"
size={ICON_SIZE_XLARGE}
color={MainColor.black}
/>
}
size="xl"
/>
</View>
</CenterCustom>
<Spacing />
<View>
<View style={{ flexDirection: "row", alignItems: "center" }}>
<TextCustom semiBold style={{ color: MainColor.white_gray }}>
Nomor Telepon
</TextCustom>
<Text style={{ color: "red" }}> *</Text>
</View>
<Spacing height={5} />
<PhoneInput
value={inputValue}
onChangePhoneNumber={handleInputValue}
selectedCountry={selectedCountry}
onChangeSelectedCountry={handleSelectedCountry}
defaultCountry="ID"
placeholder="xxx-xxx-xxx"
/>
</View>
<Spacing />
<TextInputCustom
required
label="Alamat Bisnis"
placeholder="Masukkan alamat bisnis"
onChangeText={(value: any) =>
setData({ ...data, alamatKantor: value })
}
/>
<TextAreaCustom
label="Deskripsi Bisnis"
placeholder="Masukkan deskripsi bisnis"
value={data.deskripsi}
onChangeText={(value: any) => setData({ ...data, deskripsi: value })}
autosize
minRows={2}
maxRows={5}
required
showCount
maxLength={1000}
/>
<Spacing />
<InformationBox text="Upload logo bisnis anda untuk di tampilaka pada portofolio." />
<CenterCustom>
<Avatar.Image
source={imageUri ? { uri: imageUri } : DUMMY_IMAGE.dummy_image}
size={200}
/>
</CenterCustom>
<Spacing />
<ButtonCenteredOnly
icon="upload"
onPress={() => {
pickImage({
setImageUri,
});
}}
>
Upload
</ButtonCenteredOnly>
<Spacing height={40} />
<InformationBox text="Isi hanya pada sosial media yang anda miliki." />
<TextInputCustom
label="Tiktok"
placeholder="Masukkan username tiktok"
onChangeText={(value: any) =>
setDataMedsos({ ...dataMedsos, tiktok: value })
}
/>
<TextInputCustom
label="Facebook"
placeholder="Masukkan username facebook"
onChangeText={(value: any) =>
setDataMedsos({ ...dataMedsos, facebook: value })
}
/>
<TextInputCustom
label="Instagram"
placeholder="Masukkan username instagram"
onChangeText={(value: any) =>
setDataMedsos({ ...dataMedsos, instagram: value })
}
/>
<TextInputCustom
label="Twitter"
placeholder="Masukkan username twitter"
onChangeText={(value: any) =>
setDataMedsos({ ...dataMedsos, twitter: value })
}
/>
<TextInputCustom
label="Youtube"
placeholder="Masukkan username youtube"
onChangeText={(value: any) =>
setDataMedsos({ ...dataMedsos, youtube: value })
}
/>
</StackCustom>
</NewWrapper>
);
}

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;
}