Compare commits

...

12 Commits

Author SHA1 Message Date
6d545f2af9 Fix change yang tertinggal 2026-03-30 17:39:53 +08:00
eeb95336f2 Fix version & tabs android
### No Issue
2026-03-30 17:38:31 +08:00
6fb3b229c3 Fix andorid 2026-03-27 17:59:13 +08:00
76deec9c53 Fix tabs agar bisa di klik
### Issue: Di android batas atas kurang turun
2026-03-26 17:37:31 +08:00
31948f71db Fix: NewWrapper footer position dengan SafeAreaView
Problem:
- Footer component tertutup atau scroll melebihi layar
- Tabs tidak bisa diklik karena footer floating
- Height OS_HEIGHT tidak konsisten di berbagai device

Solution:
 Footer menggunakan position: absolute untuk stay di bawah
 Konten ScrollView/FlatList mendapat paddingBottom: OS_HEIGHT
 SafeAreaView hanya untuk area aman, bukan height control
 Footer tetap visible di semua ukuran layar

Changes:
- Add styles.footerContainer dengan position: absolute
- Add paddingBottom ke contentContainerStyle (ScrollView & FlatList)
- Remove height: OS_HEIGHT dari SafeAreaView
- Footer stay di bottom dengan proper safe area handling

Result:
- Footer selalu terlihat di bawah layar
- Konten tidak tertutup footer (ada padding)
- Tabs bisa diklik dengan baik
- Konsisten di iOS & Android
- Respect safe area insets

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-03-26 12:28:07 +08:00
16decd89c8 Refactor: apply PhoneInputCustom to PortofolioEdit
Changes:
- Replace react-native-international-phone-number with PhoneInputCustom
- Remove ICountry dependency, use CountryData from constants
- Add phone number state management
- Implement country detection from existing phone number
- Auto-detect country based on calling code on load
- Improve phone number formatting logic

Features Applied:
 NO emoji flags - only calling codes (+62, +65, etc)
 Clean, professional UI
 Modal country picker with search
 Auto-detect country from saved phone number
 Real-time phone number formatting
 Auto-update country code on change
 Consistent with LoginView & ScreenPortofolioCreate

Phone Detection Logic:
- Load existing phone number from API
- Detect country by matching calling code
- Extract phone number without country code for display
- Set detected country for country picker
- Re-format on country change

UI:
- Phone Input: [+62 ⌄ | xxx-xxx-xxx]
- Country Picker: Modal with search
- Display: Country name + calling code only

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-03-26 12:17:05 +08:00
ecbcc12abf Refactor: apply PhoneInputCustom to ScreenPortofolioCreate
Changes:
- Replace react-native-international-phone-number with PhoneInputCustom
- Remove ICountry dependency, use CountryData from constants
- Update state management (inputValue → phoneNumber)
- Improve phone number formatting logic
- Add handleCountryChange for better country switching

Features Applied:
 NO emoji flags - only calling codes (+62, +65, etc)
 Clean, professional UI
 Modal country picker with search
 Real-time phone number formatting
 Auto-update country code on change
 Consistent with LoginView implementation

Phone Input Logic:
- Format on every phone change
- Re-format when country changes
- Remove duplicate country codes
- Remove leading zeros
- Store E.164 format in API data

UI:
- Phone Input: [+62 ⌄ | xxx-xxx-xxx]
- Country Picker: Modal with search
- Display: Country name + calling code only

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-03-26 11:42:54 +08:00
0cb734e790 Refactor: apply PhoneInputCustom to LoginView
Changes:
- Replace react-native-international-phone-number with PhoneInputCustom
- Remove dependency on ICountry, use CountryData from constants
- Add KeyboardAvoidingView for better iOS keyboard handling
- Improve validation with libphonenumber-js
- Add proper phone number formatting
- Update state management (inputValue → phoneNumber)

Features Applied:
 NO emoji flags - only calling codes (+62, +65, etc)
 Clean, professional UI
 Modal country picker with search
 Better keyboard handling on iOS
 Real-time validation with libphonenumber-js
 Auto-formatting for international numbers
 Reusable component

UI:
- Phone Input: [+62 ⌄ | 812-3456-7890]
- Country Picker: Modal with search
- Display: Country name + calling code only

Validation:
- Check phone number length
- Validate with libphonenumber-js
- Format to E.164 on login
- Error handling with Toast

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-03-26 11:33:18 +08:00
0d2fef1878 Feat: add reusable PhoneInput component without flags
New Components:
- PhoneInputCustom: Reusable phone input without emoji flags
- constants/countries.ts: Country data with calling codes only

Features:
 NO emoji flags - only country name + calling code (+62, +65, etc)
 Clean, professional UI
 Modal country picker with search
 15 countries supported
 Helper functions: getCountryByCallingCode, getCountryByCode, searchCountries
 Fully typed with TypeScript
 Reusable across the app
 Maximum compatibility (no emoji rendering issues)

UI Design:
- Phone Input: [+62 ⌄ | 812-3456-7890]
- Country Picker: Modal with search
- Display: Country name + calling code only

Usage:
import { PhoneInputCustom } from '@/components';
import { DEFAULT_COUNTRY } from '@/constants/countries';

<PhoneInputCustom
  value={phoneNumber}
  onChangePhoneNumber={setPhoneNumber}
  selectedCountry={selectedCountry}
  onChangeCountry={setSelectedCountry}
/>

Benefits:
 Works on ALL iOS versions (no emoji issues)
 Consistent across all platforms
 Faster render (no emoji/image loading)
 Cleaner code structure
 Easy to maintain

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-03-26 11:27:59 +08:00
2e58f8c7b4 Docs: add task breakdown untuk fix phone input iOS 16+
- Add TASKS/fix-phone-input-ios-16.md dengan analisis lengkap
- 3 opsi solusi: KeyboardAvoidingView, Keyboard Controller, Custom Input
- Detailed implementation guidelines
- Testing checklist untuk iOS 16, 17, 18

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-03-25 16:39:48 +08:00
b6cd308b0b Refactor pagination implementation dan perbaikan UI
- Add default page parameter di apiAllUser
- Refactor MainView_V2.tsx dengan separate render functions
- Update pagination pageSize menjadi 10 di Forum
- Fix iOS height constant dan tab styling
- Rename Admin_ScreenPortofolioCreate ke ScreenPortofolioCreate
- Add TASKS documentation folder

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-03-25 16:22:54 +08:00
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
79 changed files with 2178 additions and 822 deletions

8
.qwen/settings.json Normal file
View File

@@ -0,0 +1,8 @@
{
"permissions": {
"allow": [
"Bash(git add *)"
]
},
"$version": 3
}

7
.qwen/settings.json.orig Normal file
View File

@@ -0,0 +1,7 @@
{
"permissions": {
"allow": [
"Bash(git add *)"
]
}
}

View File

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

View File

@@ -0,0 +1,369 @@
# Fix Phone Number Input - iOS 16+ Compatibility
## 📋 Ringkasan Task
Memperbaiki masalah phone number input pada `screens/Authentication/LoginView.tsx` yang tidak berfungsi dengan baik pada iOS versi 16 ke atas.
---
## 🎯 Tujuan
1. Fix keyboard overlay issues pada iOS 16+
2. Perbaiki layout measurement dan safe area
3. Pastikan input phone number responsive di semua device
4. Maintain UX yang konsisten dengan Android
---
## 📁 File yang Terlibat
### File Utama
- **Target**: `screens/Authentication/LoginView.tsx`
### File Pendukung
- `components/TextInput/TextInputCustom.tsx` - Alternatif custom input
- `styles/global-styles.ts` - Styling adjustments
- `package.json` - Update dependencies (jika perlu)
### Dependencies Terkait
- `react-native-international-phone-number`: ^0.9.3
- `react-native-keyboard-controller`: ^1.18.6
---
## 🔍 Analisis Masalah
### Issues pada iOS 16+
#### 1. **Keyboard Overlay Problem**
**Symptom**: Input field tertutup keyboard saat aktif
**Cause**:
- Safe area insets tidak terhitung dengan benar
- Keyboard animation tidak sinkron dengan layout
- `react-native-keyboard-controller` tidak terintegrasi dengan baik
#### 2. **Layout Measurement Issues**
**Symptom**: Input field berubah ukuran secara tidak terduga
**Cause**:
- Dynamic Type settings mempengaruhi layout
- Font scaling pada iOS 16+ berbeda
- Container tidak memiliki fixed height
#### 3. **Focus Behavior**
**Symptom**: Input tidak auto-scroll saat di-focus
**Cause**:
- ScrollView/KeyboardAvoidingView configuration salah
- Keyboard dismissing behavior tidak konsisten
#### 4. **Visual Glitches**
**Symptom**: Country flag dropdown tidak muncul atau terpotong
**Cause**:
- Z-index issues pada iOS
- Modal/Popover rendering problems
---
## 🎨 Solusi yang Direkomendasikan
### Option 1: **KeyboardAvoidingView Enhancement** (RECOMMENDED)
**Effort**: Medium
**Impact**: High
```typescript
import { KeyboardAvoidingView, Platform } from "react-native";
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
keyboardVerticalOffset={Platform.OS === "ios" ? 90 : 50}
style={{ flex: 1 }}
>
<ViewWrapper>
{/* Content */}
</ViewWrapper>
</KeyboardAvoidingView>
```
**Keuntungan**:
- Native solution dari React Native
- Tidak perlu tambahan library
- Stabil untuk iOS 16+
**Kekurangan**:
- Perlu tuning offset untuk setiap device
- Tidak se-smooth keyboard controller
### Option 2: **React Native Keyboard Controller** (BETTER UX)
**Effort**: Medium-High
**Impact**: High
```typescript
import { KeyboardProvider } from "react-native-keyboard-controller";
// Wrap di root app (sudah ada di _layout.tsx)
<KeyboardProvider>
<App />
</KeyboardProvider>
// Di LoginView
import { KeyboardAwareScrollView } from "react-native-keyboard-controller";
<KeyboardAwareScrollView
bottomOffset={20}
keyboardOffset={10}
>
{/* Content */}
</KeyboardAwareScrollView>
```
**Keuntungan**:
- Smooth keyboard animations
- Better control over keyboard behavior
- Cross-platform consistency
**Kekurangan**:
- Perlu verify konfigurasi yang sudah ada
- Mungkin perlu update library
### Option 3: **Custom Phone Input** (FALLBACK)
**Effort**: High
**Impact**: Medium
Membuat custom phone input dengan:
- TextInputCustom component
- Country code picker modal
- Validation logic
**Keuntungan**:
- Full control atas behavior
- Tidak depend on third-party issues
**Kekurangan**:
- Development time lebih lama
- Perlu testing ekstensif
- Maintain code sendiri
---
## 📝 Breakdown Task
### Task 1: Research & Setup ✅
- [x] Identifikasi masalah pada iOS 16+
- [x] Cek dokumentasi library
- [x] Review existing implementation
- [ ] Test di iOS simulator (iOS 16+)
- [ ] Test di device fisik (jika ada)
### Task 2: Implement KeyboardAvoidingView Fix
- [ ] Wrap ViewWrapper dengan KeyboardAvoidingView
- [ ] Set behavior berdasarkan Platform
- [ ] Adjust keyboardVerticalOffset
- [ ] Test di berbagai ukuran layar
- [ ] Test landscape mode (jika applicable)
### Task 3: Adjust Layout & Styling
- [ ] Fix container height/width
- [ ] Adjust safe area insets
- [ ] Test dengan Dynamic Type settings
- [ ] Ensure consistent padding/margin
### Task 4: Test Focus Behavior
- [ ] Auto-scroll saat focus
- [ ] Keyboard dismiss saat tap outside
- [ ] Next/previous field navigation (jika ada)
- [ ] Input validation on blur
### Task 5: Country Picker Fix
- [ ] Verify dropdown z-index
- [ ] Test modal presentation
- [ ] Ensure flag icons visible
- [ ] Test search functionality
### Task 6: Testing & QA
- [ ] Test iOS 16, 17, 18
- [ ] Test Android (regression)
- [ ] Test dengan berbagai device sizes
- [ ] Test accessibility (VoiceOver)
- [ ] Performance test (no lag)
### Task 7: Documentation
- [ ] Update code comments
- [ ] Document iOS-specific workarounds
- [ ] Add troubleshooting notes
---
## 💻 Implementation Guidelines
### Recommended Implementation (Option 1 + 2 Hybrid)
```typescript
import { KeyboardAvoidingView, Platform } from "react-native";
import { KeyboardProvider } from "react-native-keyboard-controller";
// Di LoginView.tsx
export default function LoginView() {
return (
<ViewWrapper
withBackground
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />
}
>
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
keyboardVerticalOffset={Platform.OS === "ios" ? 100 : 50}
style={{ flex: 1 }}
>
<View style={[GStyles.authContainer, { paddingBottom: 40 }]}>
{/* Title Section */}
<View style={GStyles.authContainerTitle}>
<Text style={GStyles.authSubTitle}>WELCOME TO</Text>
<Spacing height={5} />
<Text style={GStyles.authTitle}>HIPMI BADUNG APPS</Text>
<Spacing height={5} />
</View>
<Spacing height={50} />
{/* Phone Input - Wrap dengan View untuk stability */}
<View style={{ marginBottom: 20 }}>
<PhoneInput
value={inputValue}
onChangePhoneNumber={handleInputValue}
selectedCountry={selectedCountry}
onChangeSelectedCountry={handleSelectedCountry}
defaultCountry="ID"
placeholder="Masukkan nomor"
// Add iOS-specific props
textInputProps={{
keyboardType: "phone-pad",
autoComplete: "tel",
importantForAutofill: "yes",
}}
/>
</View>
<Spacing />
{/* Login Button */}
<ButtonCustom
onPress={handleLogin}
disabled={loadingTerm}
isLoading={loading || loadingTerm}
>
Login
</ButtonCustom>
<Spacing height={50} />
{/* Terms Text */}
<Text style={{ ...GStyles.textLabel, textAlign: "center", fontSize: 12 }}>
Dengan menggunakan aplikasi ini, Anda telah menyetujui{" "}
<Text
style={{
color: MainColor.yellow,
textDecorationLine: "underline",
}}
onPress={() => {
const toUrl = `${url}/terms-of-service.html`;
openBrowser(toUrl);
}}
>
Syarat & Ketentuan
</Text>{" "}
dan seluruh kebijakan privasi yang berlaku.
</Text>
{/* Version Info */}
<Text
style={{
position: "absolute",
bottom: 35,
right: 50,
fontSize: 10,
fontWeight: "thin",
fontStyle: "italic",
color: MainColor.white_gray,
}}
>
{version} | powered by muku.id
</Text>
</View>
</KeyboardAvoidingView>
</ViewWrapper>
);
}
```
### Styling Adjustments
```typescript
// styles/global-styles.ts
export const GStyles = StyleSheet.create({
authContainer: {
flex: 1,
justifyContent: "center",
paddingHorizontal: 24,
// Add padding bottom untuk keyboard space
paddingBottom: Platform.OS === "ios" ? 40 : 20,
},
// ... existing styles
});
```
---
## ✅ Acceptance Criteria
### Functional
- [ ] Input field tidak tertutup keyboard saat focus
- [ ] Country picker dropdown berfungsi dengan baik
- [ ] Auto-scroll bekerja smooth saat focus
- [ ] Keyboard dismiss saat tap outside
- [ ] Input validation berjalan normal
### Visual
- [ ] Layout tidak berubah saat keyboard muncul
- [ ] No visual glitches atau flickering
- [ ] Country flag icons visible
- [ ] Consistent spacing dan padding
### Compatibility
- [ ] iOS 16, 17, 18 - Tested ✅
- [ ] Android - No regression ✅
- [ ] iPad - Responsive ✅
- [ ] Landscape mode - Usable ✅
### Performance
- [ ] No lag saat typing
- [ ] Smooth keyboard animations
- [ ] No memory leaks
- [ ] Fast input response
---
## 🔗 References
- [React Native KeyboardAvoidingView Docs](https://reactnative.dev/docs/keyboardavoidingview)
- [react-native-keyboard-controller](https://github.com/kirillzyusko/react-native-keyboard-controller)
- [react-native-international-phone-number Issues](https://github.com/bluesky01/react-native-international-phone-number/issues)
- [iOS 16+ Keyboard Changes](https://developer.apple.com/documentation/uikit/uikit)
---
## 📊 Estimated Effort
- **Complexity**: Medium
- **Time Estimate**: 2-4 jam
- **Risk Level**: Medium (perlu testing ekstensif di device)
---
## 📝 Notes
- **Priority**: High (login adalah critical path)
- **Testing**: Wajib test di device fisik jika memungkinkan
- **Fallback**: Jika Option 1 & 2 gagal, siap untuk implement Option 3 (custom input)
- **Monitoring**: Add analytics untuk track input completion rate
---
**Created**: 2026-03-25
**Status**: Pending
**Priority**: High
**Related Issue**: iOS 16+ keyboard compatibility

View File

@@ -101,7 +101,7 @@ packagingOptions {
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1 versionCode 1
versionName "1.0.2" versionName "1.0.3"
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\"" buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
} }

View File

@@ -15,7 +15,7 @@ require("dotenv").config();
export default { export default {
name: "HIPMI Badung Connect", name: "HIPMI Badung Connect",
slug: "hipmi-mobile", slug: "hipmi-mobile",
version: "1.0.2", version: "1.0.3",
orientation: "portrait", orientation: "portrait",
icon: "./assets/images/icon.png", icon: "./assets/images/icon.png",
scheme: "hipmimobile", scheme: "hipmimobile",
@@ -34,7 +34,7 @@ export default {
associatedDomains: [ associatedDomains: [
"applinks:cld-dkr-hipmi-stg.wibudev.com", "applinks:cld-dkr-hipmi-stg.wibudev.com",
], ],
buildNumber: "5", buildNumber: "7",
}, },
android: { android: {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,6 +9,7 @@ import {
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth"; import { useAuth } from "@/hooks/use-auth";
import { import {
@@ -265,13 +266,17 @@ export default function UserEventConfirmation() {
<> <>
<Stack.Screen <Stack.Screen
options={{ options={{
title: "Konfirmasi Event", header: () => (
headerLeft: () => ( <AppHeader
<Ionicons title="Konfirmasi Event"
name="arrow-back" left={
size={20} <Ionicons
color={MainColor.yellow} name="arrow-back"
onPress={() => router.navigate("/")} size={20}
color={MainColor.yellow}
onPress={() => router.navigate("/")}
/>
}
/> />
), ),
}} }}

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import { BasicWrapper, StackCustom, ViewWrapper } from "@/components"; import { BasicWrapper, NewWrapper, StackCustom, ViewWrapper } from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader"; import AppHeader from "@/components/_ShareComponent/AppHeader";
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom"; import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
@@ -148,7 +148,7 @@ export default function Application() {
}} }}
/> />
<ViewWrapper <NewWrapper
refreshControl={ refreshControl={
<RefreshControl <RefreshControl
refreshing={refreshing} refreshing={refreshing}
@@ -166,18 +166,19 @@ export default function Application() {
})} })}
/> />
) : ( ) : (
<View style={GStyles.tabBar}> null
<View style={[GStyles.tabContainer, { paddingTop: 10 }]}> // <View style={GStyles.tabBar}>
{Array.from({ length: 4 }).map((e, index) => ( // <View style={[GStyles.tabContainer, { paddingTop: 10 }]}>
<CustomSkeleton // {Array.from({ length: 4 }).map((e, index) => (
key={index} // <CustomSkeleton
height={40} // key={index}
width={40} // height={40}
radius={100} // width={40}
/> // radius={100}
))} // />
</View> // ))}
</View> // </View>
// </View>
) )
} }
> >
@@ -201,10 +202,10 @@ export default function Application() {
{data ? ( {data ? (
<Home_BottomFeatureSection listData={listData} /> <Home_BottomFeatureSection listData={listData} />
) : ( ) : (
<CustomSkeleton height={200} /> <CustomSkeleton height={150} />
)} )}
</StackCustom> </StackCustom>
</ViewWrapper> </NewWrapper>
</> </>
); );
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
import { Admin_ScreenPortofolioCreate } from "@/screens/Portofolio/ScreenPortofolioCreate"; import { ScreenPortofolioCreate } from "@/screens/Portofolio/ScreenPortofolioCreate";
export default function PortofolioCreate() { export default function PortofolioCreate() {
return <Admin_ScreenPortofolioCreate />; return <ScreenPortofolioCreate />;
} }

View File

@@ -5,6 +5,7 @@ import {
ButtonCustom, ButtonCustom,
CenterCustom, CenterCustom,
NewWrapper, NewWrapper,
PhoneInputCustom,
SelectCustom, SelectCustom,
Spacing, Spacing,
StackCustom, StackCustom,
@@ -15,6 +16,7 @@ import {
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent"; import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_XLARGE } from "@/constants/constans-value"; import { ICON_SIZE_XLARGE } from "@/constants/constans-value";
import { DEFAULT_COUNTRY, type CountryData, COUNTRIES } from "@/constants/countries";
import { import {
apiMasterBidangBisnis, apiMasterBidangBisnis,
apiMasterSubBidangBisnis, apiMasterSubBidangBisnis,
@@ -32,7 +34,6 @@ import { router, useLocalSearchParams } from "expo-router";
import _ from "lodash"; import _ from "lodash";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Text, View } from "react-native"; import { Text, View } from "react-native";
import PhoneInput, { ICountry } from "react-native-international-phone-number";
import { ActivityIndicator } from "react-native-paper"; import { ActivityIndicator } from "react-native-paper";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
@@ -59,8 +60,8 @@ export default function PortofolioEdit() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [data, setData] = useState<any>({}); const [data, setData] = useState<any>({});
const [phoneNumber, setPhoneNumber] = useState<string>("");
const [selectedCountry, setSelectedCountry] = useState<null | ICountry>(null); const [selectedCountry, setSelectedCountry] = useState<CountryData>(DEFAULT_COUNTRY);
const [bidangBisnis, setBidangBisnis] = useState< const [bidangBisnis, setBidangBisnis] = useState<
IMasterBidangBisnis[] | null IMasterBidangBisnis[] | null
>(null); >(null);
@@ -72,12 +73,42 @@ export default function PortofolioEdit() {
IListSubBidangSelected[] IListSubBidangSelected[]
>([]); >([]);
function handleInputValue(phoneNumber: string) { function handlePhoneChange(phone: string) {
setData({ ...data, tlpn: phoneNumber }); setPhoneNumber(phone);
// Format phone number for API
const callingCode = selectedCountry.callingCode;
let fixNumber = phone.replace(/\s+/g, "").replace(/^0+/, "");
// Remove country code if already present
if (fixNumber.startsWith(callingCode)) {
fixNumber = fixNumber.substring(callingCode.length);
}
// Remove leading zero
fixNumber = fixNumber.replace(/^0+/, "");
const realNumber = callingCode + fixNumber;
setData({ ...data, tlpn: realNumber });
} }
function handleSelectedCountry(country: ICountry) { function handleCountryChange(country: CountryData) {
setSelectedCountry(country); setSelectedCountry(country);
// Re-format with new country code
const callingCode = country.callingCode;
let fixNumber = phoneNumber.replace(/\s+/g, "").replace(/^0+/, "");
// Remove country code if already present
if (fixNumber.startsWith(callingCode)) {
fixNumber = fixNumber.substring(callingCode.length);
}
// Remove leading zero
fixNumber = fixNumber.replace(/^0+/, "");
const realNumber = callingCode + fixNumber;
setData({ ...data, tlpn: realNumber });
} }
const onLoadMasterBidang = async () => { const onLoadMasterBidang = async () => {
@@ -122,8 +153,27 @@ export default function PortofolioEdit() {
const response = await apiGetOnePortofolio({ id: id }); const response = await apiGetOnePortofolio({ id: id });
if (response.success) { if (response.success) {
const fixNumber = response.data.tlpn.replace("62", ""); // Extract phone number without country code for display
setData({ ...response.data, tlpn: fixNumber }); const fullNumber = response.data.tlpn;
let displayNumber = fullNumber;
let detectedCountry = DEFAULT_COUNTRY;
// Try to detect country from calling code
for (const country of COUNTRIES) {
if (fullNumber.startsWith(country.callingCode)) {
detectedCountry = country;
displayNumber = fullNumber.substring(country.callingCode.length);
break;
}
}
setSelectedCountry(detectedCountry);
// Remove leading zero if present
displayNumber = displayNumber.replace(/^0+/, "");
setPhoneNumber(displayNumber);
setData({ ...response.data, tlpn: displayNumber });
// Cek apakah ada sub bidang bisnis yang terpilih // Cek apakah ada sub bidang bisnis yang terpilih
const prevSubBidang = response.data.Portofolio_BidangDanSubBidangBisnis; const prevSubBidang = response.data.Portofolio_BidangDanSubBidangBisnis;
@@ -244,15 +294,11 @@ export default function PortofolioEdit() {
} }
const handleSubmitUpdate = async () => { const handleSubmitUpdate = async () => {
const callingCode = selectedCountry?.callingCode.replace(/^\+/, "") || "";
let fixNumber = data.tlpn.replace(/\s+/g, "").replace(/^0+/, "");
const realNumber = callingCode + fixNumber;
const newData: IFormData = { const newData: IFormData = {
id_Portofolio: data.id_Portofolio, id_Portofolio: data.id_Portofolio,
namaBisnis: data.namaBisnis, namaBisnis: data.namaBisnis,
alamatKantor: data.alamatKantor, alamatKantor: data.alamatKantor,
tlpn: realNumber, tlpn: data.tlpn, // Already formatted by PhoneInputCustom
deskripsi: data.deskripsi, deskripsi: data.deskripsi,
masterBidangBisnisId: data.masterBidangBisnisId, masterBidangBisnisId: data.masterBidangBisnisId,
subBidang: listSubBidangSelected, subBidang: listSubBidangSelected,
@@ -435,12 +481,11 @@ export default function PortofolioEdit() {
<Text style={{ color: "red" }}> *</Text> <Text style={{ color: "red" }}> *</Text>
</View> </View>
<Spacing height={5} /> <Spacing height={5} />
<PhoneInput <PhoneInputCustom
value={data.tlpn} value={phoneNumber}
onChangePhoneNumber={handleInputValue} onChangePhoneNumber={handlePhoneChange}
selectedCountry={selectedCountry} selectedCountry={selectedCountry}
onChangeSelectedCountry={handleSelectedCountry} onChangeCountry={handleCountryChange}
defaultCountry="ID"
placeholder="xxx-xxx-xxx" placeholder="xxx-xxx-xxx"
/> />
</View> </View>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -41,6 +41,7 @@
"expo-symbols": "~1.0.7", "expo-symbols": "~1.0.7",
"expo-system-ui": "~6.0.7", "expo-system-ui": "~6.0.7",
"expo-web-browser": "~15.0.9", "expo-web-browser": "~15.0.9",
"libphonenumber-js": "^1.12.40",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"moti": "^0.30.0", "moti": "^0.30.0",
"react": "19.1.0", "react": "19.1.0",
@@ -1772,6 +1773,8 @@
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
"libphonenumber-js": ["libphonenumber-js@1.12.40", "", {}, "sha512-HKGs7GowShNls3Zh+7DTr6wYpPk5jC78l508yQQY3e8ZgJChM3A9JZghmMJZuK+5bogSfuTafpjksGSR3aMIEg=="],
"lighthouse-logger": ["lighthouse-logger@1.4.2", "", { "dependencies": { "debug": "^2.6.9", "marky": "^1.2.2" } }, "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g=="], "lighthouse-logger": ["lighthouse-logger@1.4.2", "", { "dependencies": { "debug": "^2.6.9", "marky": "^1.2.2" } }, "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g=="],
"lightningcss": ["lightningcss@1.31.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.31.1", "lightningcss-darwin-arm64": "1.31.1", "lightningcss-darwin-x64": "1.31.1", "lightningcss-freebsd-x64": "1.31.1", "lightningcss-linux-arm-gnueabihf": "1.31.1", "lightningcss-linux-arm64-gnu": "1.31.1", "lightningcss-linux-arm64-musl": "1.31.1", "lightningcss-linux-x64-gnu": "1.31.1", "lightningcss-linux-x64-musl": "1.31.1", "lightningcss-win32-arm64-msvc": "1.31.1", "lightningcss-win32-x64-msvc": "1.31.1" } }, "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ=="], "lightningcss": ["lightningcss@1.31.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.31.1", "lightningcss-darwin-arm64": "1.31.1", "lightningcss-darwin-x64": "1.31.1", "lightningcss-freebsd-x64": "1.31.1", "lightningcss-linux-arm-gnueabihf": "1.31.1", "lightningcss-linux-arm64-gnu": "1.31.1", "lightningcss-linux-arm64-musl": "1.31.1", "lightningcss-linux-x64-gnu": "1.31.1", "lightningcss-linux-x64-musl": "1.31.1", "lightningcss-win32-arm64-msvc": "1.31.1", "lightningcss-win32-x64-msvc": "1.31.1" } }, "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ=="],

View File

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

View File

@@ -0,0 +1,256 @@
import { MainColor } from "@/constants/color-palet";
import {
DEFAULT_COUNTRY,
searchCountries,
type CountryData,
} from "@/constants/countries";
import { useState } from "react";
import {
Modal,
ScrollView,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View,
} from "react-native";
interface PhoneInputProps {
value: string;
onChangePhoneNumber: (phone: string) => void;
selectedCountry?: CountryData;
onChangeCountry: (country: CountryData) => void;
placeholder?: string;
disabled?: boolean;
}
export default function PhoneInputCustom({
value,
onChangePhoneNumber,
selectedCountry = DEFAULT_COUNTRY,
onChangeCountry,
placeholder = "Masukkan nomor",
disabled = false,
}: PhoneInputProps) {
const [countryPickerVisible, setCountryPickerVisible] = useState(false);
const [searchQuery, setSearchQuery] = useState("");
const filteredCountries = searchCountries(searchQuery);
const handleSelectCountry = (country: CountryData) => {
onChangeCountry(country);
setCountryPickerVisible(false);
setSearchQuery("");
};
const handlePhoneChange = (text: string) => {
// Only allow numbers and spaces
const cleaned = text.replace(/[^\d\s]/g, "");
onChangePhoneNumber(cleaned);
};
return (
<>
{/* Phone Input Field */}
<View style={styles.container}>
<TouchableOpacity
style={styles.countryPickerButton}
onPress={() => setCountryPickerVisible(true)}
disabled={disabled}
activeOpacity={0.7}
>
<Text style={styles.countryCodeText}>+{selectedCountry.callingCode}</Text>
</TouchableOpacity>
<View style={styles.divider} />
<TextInput
style={[styles.phoneInput, disabled && styles.disabledInput]}
placeholder={placeholder}
placeholderTextColor={MainColor.placeholder}
value={value}
onChangeText={handlePhoneChange}
keyboardType="phone-pad"
autoComplete="tel"
importantForAutofill="yes"
editable={!disabled}
/>
</View>
{/* Country Picker Modal */}
<Modal
visible={countryPickerVisible}
transparent
animationType="slide"
onRequestClose={() => setCountryPickerVisible(false)}
>
<View style={styles.modalOverlay}>
<View style={styles.modalContent}>
<View style={styles.modalHeader}>
<Text style={styles.modalTitle}>Pilih Negara</Text>
<TouchableOpacity onPress={() => setCountryPickerVisible(false)}>
<Text style={styles.modalClose}></Text>
</TouchableOpacity>
</View>
<TextInput
style={styles.searchInput}
placeholder="Cari negara atau kode..."
placeholderTextColor={MainColor.placeholder}
value={searchQuery}
onChangeText={setSearchQuery}
autoFocus
/>
<ScrollView style={styles.countryList}>
{filteredCountries.map((country) => (
<TouchableOpacity
key={country.code}
style={[
styles.countryItem,
selectedCountry.code === country.code &&
styles.countryItemSelected,
]}
onPress={() => handleSelectCountry(country)}
activeOpacity={0.7}
>
<View style={styles.countryInfo}>
<Text style={styles.countryName}>{country.name}</Text>
<Text style={styles.countryCode}>+{country.callingCode}</Text>
</View>
{selectedCountry.code === country.code && (
<Text style={styles.checkmark}></Text>
)}
</TouchableOpacity>
))}
</ScrollView>
</View>
</View>
</Modal>
</>
);
}
const styles = StyleSheet.create({
// Container
container: {
flexDirection: "row",
backgroundColor: MainColor.white,
borderRadius: 8,
borderWidth: 1,
borderColor: MainColor.white_gray,
marginBottom: 16,
overflow: "hidden",
},
// Country Picker Button
countryPickerButton: {
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 16,
paddingVertical: 14,
backgroundColor: MainColor.text_input,
borderRightWidth: 1,
borderRightColor: MainColor.white_gray,
},
countryCodeText: {
fontSize: 16,
color: MainColor.black,
fontWeight: "600",
},
dropdownIcon: {
fontSize: 18,
color: MainColor.placeholder,
marginLeft: 4,
},
// Divider
divider: {
width: 1,
backgroundColor: MainColor.white_gray,
},
// Phone Input
phoneInput: {
flex: 1,
paddingVertical: 14,
paddingHorizontal: 12,
fontSize: 16,
color: MainColor.black,
},
disabledInput: {
backgroundColor: MainColor.text_input,
color: MainColor.placeholder,
},
// Modal
modalOverlay: {
flex: 1,
backgroundColor: "rgba(0, 0, 0, 0.5)",
justifyContent: "flex-end",
},
modalContent: {
backgroundColor: MainColor.white,
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
maxHeight: "80%",
paddingBottom: 34,
},
modalHeader: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
padding: 20,
borderBottomWidth: 1,
borderBottomColor: MainColor.white_gray,
},
modalTitle: {
fontSize: 18,
fontWeight: "bold",
color: MainColor.black,
},
modalClose: {
fontSize: 24,
color: MainColor.placeholder,
padding: 5,
},
// Search Input
searchInput: {
backgroundColor: MainColor.text_input,
margin: 16,
padding: 12,
borderRadius: 8,
fontSize: 16,
color: MainColor.black,
},
// Country List
countryList: {
paddingHorizontal: 16,
},
countryItem: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingVertical: 12,
paddingHorizontal: 12,
borderBottomWidth: 1,
borderBottomColor: MainColor.white_gray,
},
countryItemSelected: {
backgroundColor: MainColor.soft_darkblue + "15",
},
countryInfo: {
flex: 1,
},
countryName: {
fontSize: 16,
color: MainColor.black,
fontWeight: "500",
},
countryCode: {
fontSize: 14,
color: MainColor.placeholder,
marginTop: 2,
},
checkmark: {
fontSize: 20,
color: MainColor.green,
fontWeight: "bold",
},
});

View File

@@ -30,7 +30,7 @@ export default function AppHeader({
? isIOS26Plus ? isIOS26Plus
? insets.top - 10 ? insets.top - 10
: insets.top : insets.top
: 10; : 40;
const paddingBottom = Platform.OS === "ios" ? 8 : 13; const paddingBottom = Platform.OS === "ios" ? 8 : 13;
@@ -43,9 +43,10 @@ export default function AppHeader({
paddingBottom, paddingBottom,
}, },
]} ]}
pointerEvents="box-none"
> >
{/* Header Container dengan absolute positioning untuk title center */} {/* Header Container dengan absolute positioning untuk title center */}
<View style={styles.headerApp}> <View style={styles.headerApp} pointerEvents="box-none">
{/* Left Section - Absolute Left */} {/* Left Section - Absolute Left */}
<View style={styles.headerLeft}> <View style={styles.headerLeft}>
{showBack ? ( {showBack ? (

View File

@@ -19,6 +19,7 @@ import {
SafeAreaView, SafeAreaView,
} from "react-native-safe-area-context"; } from "react-native-safe-area-context";
import type { ScrollViewProps, FlatListProps } from "react-native"; import type { ScrollViewProps, FlatListProps } from "react-native";
import Spacing from "./Spacing";
// --- ✅ Tambahkan refreshControl ke BaseProps --- // --- ✅ Tambahkan refreshControl ke BaseProps ---
interface BaseProps { interface BaseProps {
@@ -83,7 +84,7 @@ const NewWrapper = (props: NewWrapperProps) => {
return <View style={[GStyles.container, style]}>{content}</View>; return <View style={[GStyles.container, style]}>{content}</View>;
}; };
// 🔹 Mode Dinamis // 🔹 Mode Dinamis (FlatList)
if ("listData" in props) { if ("listData" in props) {
const listProps = props as ListModeProps; const listProps = props as ListModeProps;
@@ -95,7 +96,7 @@ const NewWrapper = (props: NewWrapperProps) => {
{headerComponent && ( {headerComponent && (
<View style={GStyles.stickyHeader}>{headerComponent}</View> <View style={GStyles.stickyHeader}>{headerComponent}</View>
)} )}
<View style={[GStyles.container, style]}> <View style={[GStyles.container, style, { flex: 1 }]}>
<FlatList <FlatList
data={listProps.listData} data={listProps.listData}
renderItem={listProps.renderItem} renderItem={listProps.renderItem}
@@ -107,30 +108,36 @@ const NewWrapper = (props: NewWrapperProps) => {
return `fallback-${index}-${JSON.stringify(item)}`; return `fallback-${index}-${JSON.stringify(item)}`;
} }
// Gabungkan ID dengan indeks untuk mencegah duplikasi
return `${String(item.id)}-${index}`; return `${String(item.id)}-${index}`;
}) })
} }
refreshControl={refreshControl}
refreshControl={refreshControl} // ✅ dari BaseProps
onEndReached={listProps.onEndReached} onEndReached={listProps.onEndReached}
onEndReachedThreshold={0.5} onEndReachedThreshold={0.5}
ListHeaderComponent={listProps.ListHeaderComponent} ListHeaderComponent={listProps.ListHeaderComponent}
ListFooterComponent={listProps.ListFooterComponent} ListFooterComponent={listProps.ListFooterComponent}
ListEmptyComponent={listProps.ListEmptyComponent} ListEmptyComponent={listProps.ListEmptyComponent}
contentContainerStyle={{ flexGrow: 1 }} contentContainerStyle={{
flexGrow: 1,
paddingBottom: footerComponent && !hideFooter ? OS_HEIGHT : 0
}}
keyboardShouldPersistTaps="handled" keyboardShouldPersistTaps="handled"
/> />
</View> </View>
{footerComponent ? ( {/* Footer dengan position absolute untuk stay di bawah */}
<SafeAreaView {footerComponent && !hideFooter && (
edges={Platform.OS === "ios" ? edgesFooter : ["bottom"]} <View style={styles.footerContainer}>
style={{ backgroundColor: MainColor.darkblue, height: OS_HEIGHT }} <SafeAreaView
> edges={Platform.OS === "ios" ? edgesFooter : ["bottom"]}
{footerComponent} style={{ backgroundColor: MainColor.darkblue }}
</SafeAreaView> >
) : hideFooter ? null : ( {footerComponent}
</SafeAreaView>
</View>
)}
{!footerComponent && !hideFooter && (
<SafeAreaView <SafeAreaView
edges={["bottom"]} edges={["bottom"]}
style={{ backgroundColor: MainColor.darkblue }} style={{ backgroundColor: MainColor.darkblue }}
@@ -144,7 +151,7 @@ const NewWrapper = (props: NewWrapperProps) => {
); );
} }
// 🔹 Mode Statis // 🔹 Mode Statis (ScrollView)
const staticProps = props as StaticModeProps; const staticProps = props as StaticModeProps;
return ( return (
@@ -156,24 +163,34 @@ const NewWrapper = (props: NewWrapperProps) => {
<View style={GStyles.stickyHeader}>{headerComponent}</View> <View style={GStyles.stickyHeader}>{headerComponent}</View>
)} )}
<ScrollView <View style={{ flex: 0 }} collapsable={false}>
contentContainerStyle={{ flexGrow: 1 }} <ScrollView
keyboardShouldPersistTaps="handled" contentContainerStyle={{
refreshControl={refreshControl} // ✅ sekarang valid flexGrow: 1,
> paddingBottom: footerComponent && !hideFooter ? OS_HEIGHT : 0
<TouchableWithoutFeedback onPress={Keyboard.dismiss}> }}
{renderContainer(staticProps.children)} keyboardShouldPersistTaps="handled"
</TouchableWithoutFeedback> refreshControl={refreshControl}
</ScrollView>
{footerComponent ? (
<SafeAreaView
edges={Platform.OS === "ios" ? edgesFooter : ["bottom"]}
style={{ backgroundColor: MainColor.darkblue, height: OS_HEIGHT }}
> >
{footerComponent} <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
</SafeAreaView> {renderContainer(staticProps.children)}
) : hideFooter ? null : ( </TouchableWithoutFeedback>
</ScrollView>
</View>
{/* Footer dengan position absolute untuk stay di bawah */}
{footerComponent && !hideFooter && (
<View style={styles.footerContainer}>
<SafeAreaView
edges={Platform.OS === "ios" ? edgesFooter : ["bottom"]}
style={{ backgroundColor: MainColor.darkblue }}
>
{footerComponent}
</SafeAreaView>
</View>
)}
{!footerComponent && !hideFooter && (
<SafeAreaView <SafeAreaView
edges={["bottom"]} edges={["bottom"]}
style={{ backgroundColor: MainColor.darkblue }} style={{ backgroundColor: MainColor.darkblue }}
@@ -187,4 +204,15 @@ const NewWrapper = (props: NewWrapperProps) => {
); );
}; };
// Styles untuk footer dengan position absolute
const styles = {
footerContainer: {
position: "absolute" as const,
bottom: 0,
left: 0,
right: 0,
backgroundColor: MainColor.darkblue,
},
};
export default NewWrapper; export default NewWrapper;

View File

@@ -49,6 +49,8 @@ import MapCustom from "./Map/MapCustom";
import CenterCustom from "./Center/CenterCustom"; import CenterCustom from "./Center/CenterCustom";
// Clickable // Clickable
import ClickableCustom from "./Clickable/ClickableCustom"; import ClickableCustom from "./Clickable/ClickableCustom";
// PhoneInput
import PhoneInputCustom from "./PhoneInput/PhoneInputCustom";
// Scroll // Scroll
import ScrollableCustom from "./Scroll/ScrollCustom"; import ScrollableCustom from "./Scroll/ScrollCustom";
// ShareComponent // ShareComponent
@@ -95,6 +97,8 @@ export {
CheckboxGroup, CheckboxGroup,
// Clickable // Clickable
ClickableCustom, ClickableCustom,
// PhoneInput
PhoneInputCustom,
// Container // Container
CircleContainer, CircleContainer,
// Divider // Divider

View File

@@ -23,8 +23,8 @@ export {
}; };
// OS Height // OS Height
const OS_ANDROID_HEIGHT = 115 const OS_ANDROID_HEIGHT = 70
const OS_IOS_HEIGHT = 90 const OS_IOS_HEIGHT = 80
const OS_HEIGHT = Platform.OS === "ios" ? OS_IOS_HEIGHT : OS_ANDROID_HEIGHT const OS_HEIGHT = Platform.OS === "ios" ? OS_IOS_HEIGHT : OS_ANDROID_HEIGHT
// Text Size // Text Size

89
constants/countries.ts Normal file
View File

@@ -0,0 +1,89 @@
import { type CountryCode } from "libphonenumber-js";
/**
* Country data for phone number input
* Contains only country name and calling code (NO flags for maximum compatibility)
*/
export interface CountryData {
code: CountryCode;
name: string;
callingCode: string;
}
/**
* List of supported countries for phone number input
*
* @description
* This list includes major countries across different regions.
* Countries are ordered by likelihood of use (Indonesia first as default).
*
* @note
* NO emoji flags used - only text-based country name and calling code
* This ensures maximum compatibility across all platforms and iOS versions
*/
export const COUNTRIES: CountryData[] = [
// Asia Pacific (Primary markets)
{ code: "ID", name: "Indonesia", callingCode: "62" },
{ code: "SG", name: "Singapore", callingCode: "65" },
{ code: "MY", name: "Malaysia", callingCode: "60" },
{ code: "AU", name: "Australia", callingCode: "61" },
// Asia (Other)
{ code: "CN", name: "China", callingCode: "86" },
{ code: "JP", name: "Japan", callingCode: "81" },
{ code: "KR", name: "South Korea", callingCode: "82" },
{ code: "IN", name: "India", callingCode: "91" },
// Middle East
{ code: "AE", name: "United Arab Emirates", callingCode: "971" },
{ code: "SA", name: "Saudi Arabia", callingCode: "966" },
// Europe
{ code: "GB", name: "United Kingdom", callingCode: "44" },
{ code: "DE", name: "Germany", callingCode: "49" },
{ code: "FR", name: "France", callingCode: "33" },
{ code: "NL", name: "Netherlands", callingCode: "31" },
// Americas
{ code: "US", name: "United States", callingCode: "1" },
];
/**
* Default country for phone number input
* Used when no country is selected (Indonesia by default)
*/
export const DEFAULT_COUNTRY: CountryData = COUNTRIES[0];
/**
* Get country by calling code
* @param callingCode - The calling code to search for (e.g., "62", "1")
* @returns The matching country data or undefined if not found
*/
export function getCountryByCallingCode(callingCode: string): CountryData | undefined {
return COUNTRIES.find((country) => country.callingCode === callingCode);
}
/**
* Get country by country code (ISO 3166-1 alpha-2)
* @param code - The country code to search for (e.g., "ID", "US")
* @returns The matching country data or undefined if not found
*/
export function getCountryByCode(code: CountryCode): CountryData | undefined {
return COUNTRIES.find((country) => country.code === code);
}
/**
* Search countries by name or calling code
* @param query - The search query (case-insensitive)
* @returns Array of matching countries
*/
export function searchCountries(query: string): CountryData[] {
const normalizedQuery = query.toLowerCase().trim();
return COUNTRIES.filter(
(country) =>
country.name.toLowerCase().includes(normalizedQuery) ||
country.code.toLowerCase().includes(normalizedQuery) ||
country.callingCode.includes(normalizedQuery)
);
}

View File

@@ -120,4 +120,9 @@ Buatkan file baru pada "Folder tujuan" dengan nama "Nama file utama" dan ubah na
<!-- END Create Box --> <!-- END Create Box -->
<!-- Random Prompt -->
Diskusi pada file screens/Authentication/LoginView.tsx , tentang penggunaan phone number input. Karena tidak berfungsi dengan baik pada versi ios 26 keatas
<!-- END Random Prompt -->
<!-- END Use Prompt Now --> <!-- END Use Prompt Now -->

View File

@@ -195,6 +195,24 @@
6ECA5F81B0BC4C70A91BE265 /* Remove signature files (Xcode workaround) */, 6ECA5F81B0BC4C70A91BE265 /* Remove signature files (Xcode workaround) */,
44265583B67C48F2A24BA93E /* Remove signature files (Xcode workaround) */, 44265583B67C48F2A24BA93E /* Remove signature files (Xcode workaround) */,
D5CA1D54CFF74AB4B8B5B583 /* Remove signature files (Xcode workaround) */, D5CA1D54CFF74AB4B8B5B583 /* Remove signature files (Xcode workaround) */,
97C01196E2194AF5A13C7773 /* Remove signature files (Xcode workaround) */,
EB19F4C53C8B434CBAD50897 /* Remove signature files (Xcode workaround) */,
95ABFC1FE48F4F2ABAF407D8 /* Remove signature files (Xcode workaround) */,
A8B22025FD1A438684F239A7 /* Remove signature files (Xcode workaround) */,
1041145746B64EBB994D41F1 /* Remove signature files (Xcode workaround) */,
A6E85C04CEFE4EB1BEC42A8F /* Remove signature files (Xcode workaround) */,
90185735FF1241C998928089 /* Remove signature files (Xcode workaround) */,
7FC9CE1604F2440EBA9E61D6 /* Remove signature files (Xcode workaround) */,
3D825348A9EE47AEB9FEB9FC /* Remove signature files (Xcode workaround) */,
F18AFF842FD14072824B885B /* Remove signature files (Xcode workaround) */,
CEB288FB251945499C04360F /* Remove signature files (Xcode workaround) */,
EE74CCF78BB449818616D056 /* Remove signature files (Xcode workaround) */,
F1E1D92E19934D0BA39896EB /* Remove signature files (Xcode workaround) */,
A6A8CCDC2FE54E96B87BA30E /* Remove signature files (Xcode workaround) */,
13FFB811A4C84DC3A1CECF5E /* Remove signature files (Xcode workaround) */,
706EFB5693F44588A1644A80 /* Remove signature files (Xcode workaround) */,
04F90533DBC3454B881643D4 /* Remove signature files (Xcode workaround) */,
45B8C246D763424E956A3E6F /* Remove signature files (Xcode workaround) */,
); );
buildRules = ( buildRules = (
); );
@@ -1211,6 +1229,312 @@
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\"; 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\";
";
};
95ABFC1FE48F4F2ABAF407D8 /* 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\";
";
};
A8B22025FD1A438684F239A7 /* 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\";
";
};
1041145746B64EBB994D41F1 /* 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\";
";
};
A6E85C04CEFE4EB1BEC42A8F /* 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\";
";
};
90185735FF1241C998928089 /* 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\";
";
};
7FC9CE1604F2440EBA9E61D6 /* 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\";
";
};
3D825348A9EE47AEB9FEB9FC /* 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\";
";
};
F18AFF842FD14072824B885B /* 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\";
";
};
CEB288FB251945499C04360F /* 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\";
";
};
EE74CCF78BB449818616D056 /* 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\";
";
};
F1E1D92E19934D0BA39896EB /* 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\";
";
};
A6A8CCDC2FE54E96B87BA30E /* 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\";
";
};
13FFB811A4C84DC3A1CECF5E /* 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\";
";
};
706EFB5693F44588A1644A80 /* 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\";
";
};
04F90533DBC3454B881643D4 /* 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\";
";
};
45B8C246D763424E956A3E6F /* 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 */ /* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */

View File

@@ -19,7 +19,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string> <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.0.2</string> <string>1.0.3</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleURLTypes</key> <key>CFBundleURLTypes</key>
@@ -39,7 +39,7 @@
</dict> </dict>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>5</string> <string>7</string>
<key>ITSAppUsesNonExemptEncryption</key> <key>ITSAppUsesNonExemptEncryption</key>
<false/> <false/>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>

View File

@@ -48,6 +48,7 @@
"expo-symbols": "~1.0.7", "expo-symbols": "~1.0.7",
"expo-system-ui": "~6.0.7", "expo-system-ui": "~6.0.7",
"expo-web-browser": "~15.0.9", "expo-web-browser": "~15.0.9",
"libphonenumber-js": "^1.12.40",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"moti": "^0.30.0", "moti": "^0.30.0",
"react": "19.1.0", "react": "19.1.0",

View File

@@ -33,7 +33,10 @@ export function EventDetailQRCode({
const deepLinkURL = `${BASE_URL}/event/${id}/confirmation?userId=${userId}`; const deepLinkURL = `${BASE_URL}/event/${id}/confirmation?userId=${userId}`;
// Toggle antara HTTPS link dan custom scheme // Toggle antara HTTPS link dan custom scheme
const qrValue = useHttpsLink ? httpsLink : deepLinkURL; // const qrValue = useHttpsLink ? httpsLink : deepLinkURL;
const qrValue = deepLinkURL;
return ( return (
<BaseBox> <BaseBox>
@@ -46,7 +49,7 @@ export function EventDetailQRCode({
{qrValue} {qrValue}
</TextCustom> </TextCustom>
<Spacing /> <Spacing />
<StackCustom direction="row" gap="sm"> {/* <StackCustom direction="row" gap="sm">
<ButtonCustom <ButtonCustom
onPress={() => setUseHttpsLink(true)} onPress={() => setUseHttpsLink(true)}
backgroundColor={useHttpsLink ? MainColor.yellow : "transparent"} backgroundColor={useHttpsLink ? MainColor.yellow : "transparent"}
@@ -69,13 +72,13 @@ export function EventDetailQRCode({
> >
Custom Scheme Custom Scheme
</ButtonCustom> </ButtonCustom>
</StackCustom> </StackCustom> */}
<Spacing /> {/* <Spacing />
<TextCustom color="gray" align="center" size={"small"}> <TextCustom color="gray" align="center" size={"small"}>
{useHttpsLink {useHttpsLink
? "✅ Testing Universal Links/App Links (butuh .well-known config)" ? "✅ Testing Universal Links/App Links (butuh .well-known config)"
: "🔧 Testing langsung (tanpa domain verification)"} : "🔧 Testing langsung (tanpa domain verification)"}
</TextCustom> </TextCustom> */}
</BaseBox> </BaseBox>
); );
} }

View File

@@ -128,12 +128,13 @@ export function Admin_ScreenEventDetail() {
); );
} }
return null; return <Spacing height={100} />;
}, [status, id]); }, [status, id]);
return ( return (
<> <>
<NewWrapper <NewWrapper
hideFooter
headerComponent={headerComponent} headerComponent={headerComponent}
// footerComponent={ // footerComponent={
// <View style={{ paddingInline: 8 }}> // <View style={{ paddingInline: 8 }}>

View File

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

View File

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

View File

@@ -1,8 +1,9 @@
import { NewWrapper } from "@/components"; import { NewWrapper, PhoneInputCustom, ViewWrapper } from "@/components";
import ButtonCustom from "@/components/Button/ButtonCustom"; import ButtonCustom from "@/components/Button/ButtonCustom";
import ModalReactNative from "@/components/Modal/ModalReactNative"; import ModalReactNative from "@/components/Modal/ModalReactNative";
import Spacing from "@/components/_ShareComponent/Spacing"; import Spacing from "@/components/_ShareComponent/Spacing";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { DEFAULT_COUNTRY, type CountryData } from "@/constants/countries";
import { useAuth } from "@/hooks/use-auth"; import { useAuth } from "@/hooks/use-auth";
import { apiVersion, BASE_URL } from "@/service/api-config"; import { apiVersion, BASE_URL } from "@/service/api-config";
import { GStyles } from "@/styles/global-styles"; import { GStyles } from "@/styles/global-styles";
@@ -10,16 +11,23 @@ import { openBrowser } from "@/utils/openBrower";
import versionBadge from "@/utils/viersionBadge"; import versionBadge from "@/utils/viersionBadge";
import { Redirect } from "expo-router"; import { Redirect } from "expo-router";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { RefreshControl, Text, View } from "react-native"; import {
import PhoneInput, { ICountry } from "react-native-international-phone-number"; KeyboardAvoidingView,
Platform,
RefreshControl,
Text,
View,
} from "react-native";
import { parsePhoneNumber } from "libphonenumber-js";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
import EULASection from "./EULASection"; import EULASection from "./EULASection";
export default function LoginView() { export default function LoginView() {
const url = BASE_URL; const url = BASE_URL;
const [version, setVersion] = useState<string>(""); const [version, setVersion] = useState<string>("");
const [selectedCountry, setSelectedCountry] = useState<null | ICountry>(null); const [selectedCountry, setSelectedCountry] =
const [inputValue, setInputValue] = useState<string>(""); useState<CountryData>(DEFAULT_COUNTRY);
const [phoneNumber, setPhoneNumber] = useState<string>("");
const [loading, setLoading] = useState<boolean>(false); const [loading, setLoading] = useState<boolean>(false);
const [refreshing, setRefreshing] = useState<boolean>(false); const [refreshing, setRefreshing] = useState<boolean>(false);
const [modalVisible, setModalVisible] = useState(false); const [modalVisible, setModalVisible] = useState(false);
@@ -43,41 +51,43 @@ export default function LoginView() {
async function handleRefresh() { async function handleRefresh() {
setRefreshing(true); setRefreshing(true);
await onLoadVersion(); await onLoadVersion();
setInputValue(""); setPhoneNumber("");
setSelectedCountry(DEFAULT_COUNTRY);
setLoading(false); setLoading(false);
setRefreshing(false); setRefreshing(false);
} }
function handleInputValue(phoneNumber: string) {
setInputValue(phoneNumber);
}
function handleSelectedCountry(country: ICountry) {
setSelectedCountry(country);
}
async function validateData() { async function validateData() {
if (inputValue.length === 0) { if (phoneNumber.length === 0) {
return Toast.show({ return Toast.show({
type: "error", type: "error",
text1: "Masukan nomor anda", text1: "Masukan nomor anda",
}); });
} }
if (selectedCountry === null) { if (phoneNumber.length < 9) {
return Toast.show({
type: "error",
text1: "Pilih negara",
});
}
if (inputValue.length < 9) {
return Toast.show({ return Toast.show({
type: "error", type: "error",
text1: "Nomor tidak valid", text1: "Nomor tidak valid",
}); });
} }
// Validate with libphonenumber-js
try {
const parsedNumber = parsePhoneNumber(phoneNumber, selectedCountry.code);
if (!parsedNumber || !parsedNumber.isValid()) {
return Toast.show({
type: "error",
text1: "Nomor tidak valid",
});
}
} catch (error) {
return Toast.show({
type: "error",
text1: "Format nomor tidak valid",
});
}
return true; return true;
} }
@@ -85,8 +95,17 @@ export default function LoginView() {
const isValid = await validateData(); const isValid = await validateData();
if (!isValid) return; if (!isValid) return;
const callingCode = selectedCountry?.callingCode.replace(/^\+/, "") || ""; // Format phone number with country code
let fixNumber = inputValue.replace(/\s+/g, "").replace(/^0+/, ""); const callingCode = selectedCountry.callingCode;
let fixNumber = phoneNumber.replace(/\s+/g, "").replace(/^0+/, "");
// Remove country code if already present
if (fixNumber.startsWith(callingCode)) {
fixNumber = fixNumber.substring(callingCode.length);
}
// Remove leading zero
fixNumber = fixNumber.replace(/^0+/, "");
const realNumber = callingCode + fixNumber; const realNumber = callingCode + fixNumber;
@@ -128,75 +147,84 @@ export default function LoginView() {
} }
return ( return (
<NewWrapper <KeyboardAvoidingView
withBackground behavior={Platform.OS === "ios" ? "padding" : "height"}
refreshControl={ keyboardVerticalOffset={Platform.OS === "ios" ? 100 : 50}
<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} /> style={{ flex: 1 }}
}
> >
<View style={GStyles.authContainer}> <ViewWrapper
<View> withBackground
<View style={GStyles.authContainerTitle}> refreshControl={
<Text style={GStyles.authSubTitle}>WELCOME TO</Text> <RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />
<Spacing height={5} /> }
<Text style={GStyles.authTitle}>HIPMI BADUNG APPS</Text> >
<Spacing height={5} /> <View style={[GStyles.authContainer, { paddingBottom: 40 }]}>
<View>
<View style={GStyles.authContainerTitle}>
<Text style={GStyles.authSubTitle}>WELCOME TO</Text>
<Spacing height={5} />
<Text style={GStyles.authTitle}>HIPMI BADUNG APPS</Text>
<Spacing height={5} />
</View>
<Spacing height={50} />
{version && (
<Text
style={{
position: "absolute",
bottom: 35,
right: 50,
fontSize: 10,
fontWeight: "thin",
fontStyle: "italic",
color: MainColor.white_gray,
}}
>
powered by muku.id
</Text>
)}
</View> </View>
<Spacing height={50} />
<Text <Spacing height={20} />
style={{
position: "absolute", <PhoneInputCustom
bottom: 35, value={phoneNumber}
right: 50, onChangePhoneNumber={setPhoneNumber}
fontSize: 10, selectedCountry={selectedCountry}
fontWeight: "thin", onChangeCountry={setSelectedCountry}
fontStyle: "italic", placeholder="Masukkan nomor"
color: MainColor.white_gray, />
}}
<Spacing />
<ButtonCustom
onPress={handleLogin}
disabled={loadingTerm}
isLoading={loading || loadingTerm}
> >
{version} | powered by muku.id Login
</ButtonCustom>
<Spacing height={50} />
<Text
style={{ ...GStyles.textLabel, textAlign: "center", fontSize: 12 }}
>
Dengan menggunakan aplikasi ini, Anda telah menyetujui{" "}
<Text
style={{
color: MainColor.yellow,
textDecorationLine: "underline",
}}
onPress={() => {
const toUrl = `${url}/terms-of-service.html`;
openBrowser(toUrl);
}}
>
Syarat & Ketentuan
</Text>{" "}
dan seluruh kebijakan privasi yang berlaku.
</Text> </Text>
</View> </View>
</ViewWrapper>
<PhoneInput
value={inputValue}
onChangePhoneNumber={handleInputValue}
selectedCountry={selectedCountry}
onChangeSelectedCountry={handleSelectedCountry}
defaultCountry="ID"
placeholder="Masukkan nomor"
/>
<Spacing />
<ButtonCustom
onPress={handleLogin}
disabled={loadingTerm}
isLoading={loading || loadingTerm}
>
Login
</ButtonCustom>
<Spacing height={50} />
<Text
style={{ ...GStyles.textLabel, textAlign: "center", fontSize: 12 }}
>
Dengan menggunakan aplikasi ini, Anda telah menyetujui{" "}
<Text
style={{
color: MainColor.yellow,
textDecorationLine: "underline",
}}
onPress={() => {
const toUrl = `${url}/terms-of-service.html`;
openBrowser(toUrl);
}}
>
Syarat & Ketentuan
</Text>{" "}
dan seluruh kebijakan privasi yang berlaku.
</Text>
</View>
<ModalReactNative isVisible={modalVisible}> <ModalReactNative isVisible={modalVisible}>
<EULASection <EULASection
@@ -205,6 +233,6 @@ export default function LoginView() {
setLoadingTerm={setLoadingTerm} setLoadingTerm={setLoadingTerm}
/> />
</ModalReactNative> </ModalReactNative>
</NewWrapper> </KeyboardAvoidingView>
); );
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,15 +1,17 @@
import { ClickableCustom, TextCustom } from "@/components"; import { CenterCustom, ClickableCustom, TextCustom } from "@/components";
import Spacing from "@/components/_ShareComponent/Spacing"; import Spacing from "@/components/_ShareComponent/Spacing";
import { router } from "expo-router"; import { router } from "expo-router";
import { View } from "react-native"; import { View } from "react-native";
import Icon from "react-native-vector-icons/FontAwesome"; import Icon from "react-native-vector-icons/FontAwesome";
import { stylesHome } from "./homeViewStyle"; import { stylesHome } from "./homeViewStyle";
import _ from "lodash";
export default function Home_BottomFeatureSection({ export default function Home_BottomFeatureSection({
listData, listData,
}: { }: {
listData: any[] | null; listData: any[] | null;
}) { }) {
console.log("listData", JSON.stringify(listData, null, 2));
return ( return (
<> <>
<ClickableCustom onPress={() => router.push("/job")}> <ClickableCustom onPress={() => router.push("/job")}>
@@ -24,17 +26,23 @@ export default function Home_BottomFeatureSection({
<View style={stylesHome.vacancyList}> <View style={stylesHome.vacancyList}>
{/* Vacancy Item 1 */} {/* Vacancy Item 1 */}
{listData?.map((item: any, index: number) => ( {_.isEmpty(listData) ? (
<View style={stylesHome.vacancyItem} key={index}> <CenterCustom style={{ paddingBlock: 50 }}>
<View style={stylesHome.vacancyDetails}> <TextCustom color="gray">Lowongan pekerjaan belum tersedia</TextCustom>
<TextCustom bold color="yellow" truncate size="large"> </CenterCustom>
{item.title} ) : (
</TextCustom> listData?.map((item: any, index: number) => (
<Spacing height={5} /> <View style={stylesHome.vacancyItem} key={index}>
<TextCustom truncate={2}>{item.deskripsi}</TextCustom> <View style={stylesHome.vacancyDetails}>
<TextCustom bold color="yellow" truncate size="large">
{item.title}
</TextCustom>
<Spacing height={5} />
<TextCustom truncate={2}>{item.deskripsi}</TextCustom>
</View>
</View> </View>
</View> ))
))} )}
</View> </View>
</View> </View>
</ClickableCustom> </ClickableCustom>

View File

@@ -94,7 +94,7 @@ export const stylesHome = StyleSheet.create({
jobVacancyHeader: { jobVacancyHeader: {
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
marginBottom: 16, marginBottom: 20,
}, },
jobVacancyTitle: { jobVacancyTitle: {
fontSize: 18, fontSize: 18,

View File

@@ -17,7 +17,7 @@ export default function Home_ImageSection() {
transition={1000} transition={1000}
style={{ style={{
width: "100%", width: "100%",
height: 120, height: 150,
borderRadius: 10, borderRadius: 10,
}} }}
/> />

View File

@@ -5,7 +5,6 @@ import { router } from "expo-router";
import React from "react"; import React from "react";
import { Text, TouchableOpacity, View } from "react-native"; import { Text, TouchableOpacity, View } from "react-native";
const CustomTab = ({ icon, label, isActive, onPress }: ICustomTab) => ( const CustomTab = ({ icon, label, isActive, onPress }: ICustomTab) => (
<TouchableOpacity <TouchableOpacity
style={[GStyles.tabItem, isActive && GStyles.activeTab]} style={[GStyles.tabItem, isActive && GStyles.activeTab]}
@@ -17,7 +16,7 @@ const CustomTab = ({ icon, label, isActive, onPress }: ICustomTab) => (
> >
<Ionicons <Ionicons
name={icon as any} name={icon as any}
size={20} size={18}
color={isActive ? "#fff" : "#666"} color={isActive ? "#fff" : "#666"}
/> />
</View> </View>
@@ -30,8 +29,8 @@ const CustomTab = ({ icon, label, isActive, onPress }: ICustomTab) => (
export default function TabSection({ tabs }: { tabs: ITabs[] }) { export default function TabSection({ tabs }: { tabs: ITabs[] }) {
return ( return (
<> <>
<View style={GStyles.tabBar}> <View style={GStyles.tabBar} pointerEvents="box-none">
<View style={GStyles.tabContainer}> <View style={GStyles.tabContainer} pointerEvents="box-none">
{tabs.map((e) => ( {tabs.map((e) => (
<CustomTab <CustomTab
key={e.id} key={e.id}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,6 +5,7 @@ import {
CenterCustom, CenterCustom,
InformationBox, InformationBox,
NewWrapper, NewWrapper,
PhoneInputCustom,
SelectCustom, SelectCustom,
Spacing, Spacing,
StackCustom, StackCustom,
@@ -15,6 +16,7 @@ import {
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_XLARGE } from "@/constants/constans-value"; import { ICON_SIZE_XLARGE } from "@/constants/constans-value";
import DUMMY_IMAGE from "@/constants/dummy-image-value"; import DUMMY_IMAGE from "@/constants/dummy-image-value";
import { DEFAULT_COUNTRY, type CountryData } from "@/constants/countries";
import Portofolio_ButtonCreate from "@/screens/Portofolio/ButtonCreatePortofolio"; import Portofolio_ButtonCreate from "@/screens/Portofolio/ButtonCreatePortofolio";
import { import {
apiMasterBidangBisnis, apiMasterBidangBisnis,
@@ -30,13 +32,12 @@ import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash"; import _ from "lodash";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { Text, View } from "react-native"; import { Text, View } from "react-native";
import PhoneInput, { ICountry } from "react-native-international-phone-number";
import { Avatar } from "react-native-paper"; import { Avatar } from "react-native-paper";
export function Admin_ScreenPortofolioCreate() { export function ScreenPortofolioCreate() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [selectedCountry, setSelectedCountry] = useState<null | ICountry>(null); const [selectedCountry, setSelectedCountry] = useState<CountryData>(DEFAULT_COUNTRY);
const [inputValue, setInputValue] = useState<string>(""); const [phoneNumber, setPhoneNumber] = useState<string>("");
const [data, setData] = useState({ const [data, setData] = useState({
namaBisnis: "", namaBisnis: "",
masterBidangBisnisId: "", masterBidangBisnisId: "",
@@ -72,7 +73,7 @@ export function Admin_ScreenPortofolioCreate() {
useCallback(() => { useCallback(() => {
onLoadMaster(); onLoadMaster();
onLoadMasterSubBidangBisnis(); onLoadMasterSubBidangBisnis();
}, []) }, []),
); );
const onLoadMaster = async () => { const onLoadMaster = async () => {
@@ -97,21 +98,47 @@ export function Admin_ScreenPortofolioCreate() {
const handlerSelectedSubBidang = ({ id }: { id: string }) => { const handlerSelectedSubBidang = ({ id }: { id: string }) => {
const selectedList = subBidangBisnis?.filter( const selectedList = subBidangBisnis?.filter(
(item) => (item?.masterBidangBisnisId as any) === id (item) => (item?.masterBidangBisnisId as any) === id,
); );
setSelectedSubBidang(selectedList as any[]); setSelectedSubBidang(selectedList as any[]);
}; };
const handleInputValue = (phoneNumber: string) => { const handlePhoneChange = (phone: string) => {
setInputValue(phoneNumber); setPhoneNumber(phone);
const callingCode = selectedCountry?.callingCode.replace(/^\+/, "") || "";
let fixNumber = inputValue.replace(/\s+/g, "").replace(/^0+/, ""); // Format phone number for API
const callingCode = selectedCountry.callingCode;
let fixNumber = phone.replace(/\s+/g, "").replace(/^0+/, "");
// Remove country code if already present
if (fixNumber.startsWith(callingCode)) {
fixNumber = fixNumber.substring(callingCode.length);
}
// Remove leading zero
fixNumber = fixNumber.replace(/^0+/, "");
const realNumber = callingCode + fixNumber; const realNumber = callingCode + fixNumber;
setData({ ...data, tlpn: realNumber }); setData({ ...data, tlpn: realNumber });
}; };
const handleSelectedCountry = (country: ICountry) => { const handleCountryChange = (country: CountryData) => {
setSelectedCountry(country); setSelectedCountry(country);
// Re-format with new country code
const callingCode = country.callingCode;
let fixNumber = phoneNumber.replace(/\s+/g, "").replace(/^0+/, "");
// Remove country code if already present
if (fixNumber.startsWith(callingCode)) {
fixNumber = fixNumber.substring(callingCode.length);
}
// Remove leading zero
fixNumber = fixNumber.replace(/^0+/, "");
const realNumber = callingCode + fixNumber;
setData({ ...data, tlpn: realNumber });
}; };
return ( return (
@@ -168,8 +195,7 @@ export function Admin_ScreenPortofolioCreate() {
.filter((option: any) => { .filter((option: any) => {
const selectedValues = listSubBidangSelected.map((s) => s.id); const selectedValues = listSubBidangSelected.map((s) => s.id);
return ( return (
option.id === item.id || option.id === item.id || !selectedValues.includes(option.id)
!selectedValues.includes(option.id)
); );
}) })
.map((e: any) => ({ .map((e: any) => ({
@@ -188,7 +214,9 @@ export function Admin_ScreenPortofolioCreate() {
<CenterCustom> <CenterCustom>
<View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}> <View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
<ActionIcon <ActionIcon
disabled={selectedSubBidang.length === listSubBidangSelected.length} disabled={
selectedSubBidang.length === listSubBidangSelected.length
}
onPress={() => { onPress={() => {
setListSubBidangSelected([ setListSubBidangSelected([
...listSubBidangSelected, ...listSubBidangSelected,
@@ -233,12 +261,11 @@ export function Admin_ScreenPortofolioCreate() {
<Text style={{ color: "red" }}> *</Text> <Text style={{ color: "red" }}> *</Text>
</View> </View>
<Spacing height={5} /> <Spacing height={5} />
<PhoneInput <PhoneInputCustom
value={inputValue} value={phoneNumber}
onChangePhoneNumber={handleInputValue} onChangePhoneNumber={handlePhoneChange}
selectedCountry={selectedCountry} selectedCountry={selectedCountry}
onChangeSelectedCountry={handleSelectedCountry} onChangeCountry={handleCountryChange}
defaultCountry="ID"
placeholder="xxx-xxx-xxx" placeholder="xxx-xxx-xxx"
/> />
</View> </View>

View File

@@ -12,6 +12,7 @@ import {
ICON_SIZE_SMALL, ICON_SIZE_SMALL,
PAGINATION_DEFAULT_TAKE, PAGINATION_DEFAULT_TAKE,
} from "@/constants/constans-value"; } from "@/constants/constans-value";
import { createPaginationComponents } from "@/helpers/paginationHelpers";
import { usePagination } from "@/hooks/use-pagination"; import { usePagination } from "@/hooks/use-pagination";
import { apiAllUser } from "@/service/api-client/api-user"; import { apiAllUser } from "@/service/api-client/api-user";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
@@ -19,21 +20,89 @@ import { router, useFocusEffect } from "expo-router";
import _ from "lodash"; import _ from "lodash";
import { useCallback, useRef, useState } from "react"; import { useCallback, useRef, useState } from "react";
import { RefreshControl, View } from "react-native"; import { RefreshControl, View } from "react-native";
import { createPaginationComponents } from "@/helpers/paginationHelpers";
const PAGE_SIZE = PAGINATION_DEFAULT_TAKE;
/**
* Render header dengan search input
*/
const renderHeader = (search: string, setSearch: (text: string) => void) => (
<TextInputCustom
value={search}
onChangeText={setSearch}
iconLeft={
<Ionicons
name="search"
size={ICON_SIZE_SMALL}
color={MainColor.placeholder}
/>
}
placeholder="Cari Pengguna"
borderRadius={50}
containerStyle={{ marginBottom: 0 }}
/>
);
/**
* Render item user
*/
const renderItem = ({ item }: { item: any }) => (
<View
style={{
backgroundColor: MainColor.soft_darkblue,
borderRadius: 8,
padding: 12,
marginBottom: 10,
elevation: 2,
shadowColor: "#000",
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.2,
shadowRadius: 2,
}}
>
<ClickableCustom
onPress={() => {
router.push(`/profile/${item?.Profile?.id}`);
}}
>
<Grid>
<Grid.Col span={2}>
<AvatarComp fileId={item?.Profile?.imageId} size="base" />
</Grid.Col>
<Grid.Col span={9}>
<StackCustom gap={"sm"}>
<TextCustom size="large">{item?.username}</TextCustom>
<TextCustom size="small">+{item?.nomor}</TextCustom>
{item?.Profile?.businessField && (
<TextCustom size="small">
{item?.Profile?.businessField}
</TextCustom>
)}
</StackCustom>
</Grid.Col>
<Grid.Col
span={1}
style={{
justifyContent: "center",
alignItems: "flex-end",
}}
>
<Ionicons
name="chevron-forward"
size={ICON_SIZE_SMALL}
color={MainColor.placeholder}
/>
</Grid.Col>
</Grid>
</ClickableCustom>
</View>
);
export default function UserSearchMainView_V2() { export default function UserSearchMainView_V2() {
const isInitialMount = useRef(true); const isInitialMount = useRef(true);
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const { const pagination = usePagination({
listData,
loading,
refreshing,
hasMore,
onRefresh,
loadMore,
isInitialLoad,
} = usePagination({
fetchFunction: async (page, searchQuery) => { fetchFunction: async (page, searchQuery) => {
const response = await apiAllUser({ const response = await apiAllUser({
page: String(page), page: String(page),
@@ -41,127 +110,50 @@ export default function UserSearchMainView_V2() {
}); });
return response; return response;
}, },
pageSize: PAGINATION_DEFAULT_TAKE, pageSize: PAGE_SIZE,
searchQuery: search, searchQuery: search,
}); });
// 🔁 Refresh otomatis saat kembali ke halaman ini // 🔁 Refresh otomatis saat kembali ke halaman ini
useFocusEffect( // useFocusEffect(
useCallback(() => { // useCallback(() => {
if (isInitialMount.current) { // if (isInitialMount.current) {
// Skip saat pertama kali mount // isInitialMount.current = false;
isInitialMount.current = false; // return;
return; // }
} // pagination.onRefresh();
// Hanya refresh saat kembali dari screen lain // }, [pagination.onRefresh]),
onRefresh(); // );
}, [onRefresh]),
);
const renderHeader = () => (
<>
<TextInputCustom
value={search}
onChangeText={setSearch}
iconLeft={
<Ionicons
name="search"
size={ICON_SIZE_SMALL}
color={MainColor.placeholder}
/>
}
placeholder="Cari Pengguna"
borderRadius={50}
containerStyle={{ marginBottom: 0 }}
/>
</>
);
const renderItem = ({ item }: { item: any }) => (
<View
style={{
backgroundColor: MainColor.soft_darkblue,
borderRadius: 8,
padding: 12,
marginBottom: 10,
elevation: 2,
shadowColor: "#000",
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.2,
shadowRadius: 2,
// height: 100
}}
>
<ClickableCustom
onPress={() => {
console.log("Ke Profile");
router.push(`/profile/${item?.Profile?.id}`);
}}
>
<Grid>
<Grid.Col span={2}>
<AvatarComp fileId={item?.Profile?.imageId} size="base" />
</Grid.Col>
<Grid.Col span={9}>
<StackCustom gap={"sm"}>
<TextCustom size="large">{item?.username}</TextCustom>
<TextCustom size="small">+{item?.nomor}</TextCustom>
{item?.Profile?.businessField && (
<TextCustom size="small">
{item?.Profile?.businessField}
</TextCustom>
)}
</StackCustom>
</Grid.Col>
<Grid.Col
span={1}
style={{
justifyContent: "center",
alignItems: "flex-end",
}}
>
<Ionicons
name="chevron-forward"
size={ICON_SIZE_SMALL}
color={MainColor.placeholder}
/>
</Grid.Col>
</Grid>
</ClickableCustom>
</View>
);
const { ListEmptyComponent, ListFooterComponent } = const { ListEmptyComponent, ListFooterComponent } =
createPaginationComponents({ createPaginationComponents({
loading, loading: pagination.loading,
refreshing, refreshing: pagination.refreshing,
listData, listData: pagination.listData,
searchQuery: search, searchQuery: search,
emptyMessage: "Tidak ada pengguna ditemukan", emptyMessage: "Tidak ada pengguna ditemukan",
emptySearchMessage: "Tidak ada hasil pencarian", emptySearchMessage: "Tidak ada hasil pencarian",
skeletonCount: PAGINATION_DEFAULT_TAKE, skeletonCount: PAGINATION_DEFAULT_TAKE,
skeletonHeight: 100, skeletonHeight: 100,
loadingFooterText: "Memuat lebih banyak pengguna...", loadingFooterText: "Memuat lebih banyak pengguna...",
isInitialLoad, isInitialLoad: pagination.isInitialLoad,
}); });
return ( return (
<> <NewWrapper
<NewWrapper headerComponent={renderHeader(search, setSearch)}
headerComponent={renderHeader()} listData={pagination.listData}
listData={listData} renderItem={renderItem}
renderItem={renderItem} onEndReached={pagination.loadMore}
onEndReached={loadMore} refreshControl={
refreshControl={ <RefreshControl
<RefreshControl progressBackgroundColor={MainColor.yellow}
progressBackgroundColor={MainColor.yellow} refreshing={pagination.refreshing}
refreshing={refreshing} onRefresh={pagination.onRefresh}
onRefresh={onRefresh} />
/> }
} ListFooterComponent={ListFooterComponent}
ListFooterComponent={ListFooterComponent} ListEmptyComponent={ListEmptyComponent}
ListEmptyComponent={ListEmptyComponent} />
/>
</>
); );
} }

View File

@@ -6,13 +6,13 @@ export async function apiUser(id: string) {
} }
export async function apiAllUser({ export async function apiAllUser({
page, page = "1",
search, search,
}: { }: {
page?: string; page?: string;
search?: string; search?: string;
}) { }) {
const pageQuery = page ? `?page=${page}` : ""; const pageQuery = `?page=${page}`;
const searchQuery = search ? `&search=${search}` : ""; const searchQuery = search ? `&search=${search}` : "";
try { try {

View File

@@ -159,7 +159,7 @@ export const GStyles = StyleSheet.create({
transform: [{ scale: 1.05 }], transform: [{ scale: 1.05 }],
}, },
iconContainer: { iconContainer: {
padding: 8, padding: 5,
borderRadius: 20, borderRadius: 20,
// marginBottom: 4, // marginBottom: 4,
}, },
@@ -207,7 +207,7 @@ export const GStyles = StyleSheet.create({
elevation: 8, // untuk Android elevation: 8, // untuk Android
}, },
bottomBarContainer: { bottomBarContainer: {
paddingHorizontal: 15, paddingHorizontal: 25,
paddingVertical: 10, paddingVertical: 10,
}, },
// =============== BOTTOM BAR =============== // // =============== BOTTOM BAR =============== //

View File

@@ -11,7 +11,7 @@ export const TabsStyles: BottomTabNavigationOptions = {
tabBarStyle: Platform.select({ tabBarStyle: Platform.select({
ios: { ios: {
borderTopWidth: 0, borderTopWidth: 0,
paddingTop: 5, paddingTop: 12,
height: OS_IOS_HEIGHT, height: OS_IOS_HEIGHT,
}, },
android: { android: {
@@ -19,7 +19,6 @@ export const TabsStyles: BottomTabNavigationOptions = {
paddingTop: 5, paddingTop: 5,
height: OS_ANDROID_HEIGHT, height: OS_ANDROID_HEIGHT,
}, },
default: {},
}), }),
tabBarBackground: TabBarBackground, tabBarBackground: TabBarBackground,
}; };