Compare commits
7 Commits
clean-code
...
tabs/25-ma
| Author | SHA1 | Date | |
|---|---|---|---|
| 2e58f8c7b4 | |||
| b6cd308b0b | |||
| f68deab8c0 | |||
| 37d2fbe48a | |||
| 57c9215771 | |||
| 4efdbd3c7b | |||
| ad32eb6fe6 |
2
QWEN.md
2
QWEN.md
@@ -387,7 +387,7 @@ apiConfig.interceptors.request.use(async (config) => {
|
||||
|
||||
### Deep Linking
|
||||
- Scheme: `hipmimobile://`
|
||||
- HTTPS: `cld-dkr-staging-hipmi.wibudev.com`
|
||||
- HTTPS: `cld-dkr-hipmi-stg.wibudev.com`
|
||||
- Configured for both platforms
|
||||
|
||||
### Camera
|
||||
|
||||
369
TASKS/fix-phone-input-ios-16.md
Normal file
369
TASKS/fix-phone-input-ios-16.md
Normal 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
|
||||
@@ -37,7 +37,7 @@
|
||||
</intent-filter>
|
||||
<intent-filter android:autoVerify="true" data-generated="true">
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<data android:scheme="https" android:host="cld-dkr-staging-hipmi.wibudev.com" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="cld-dkr-hipmi-stg.wibudev.com" android:pathPrefix="/"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
</intent-filter>
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
// app.config.js
|
||||
require("dotenv").config();
|
||||
|
||||
// const isDev = process.env.NODE_ENV === "development";
|
||||
// const isStaging = process.env.NEXT_PUBLIC_ENV === "staging";
|
||||
// const isProd = process.env.NEXT_PUBLIC_ENV === "production";
|
||||
|
||||
// Domain berdasarkan environment
|
||||
// const domain = isDev
|
||||
// ? "localhost:3000"
|
||||
// : isStaging
|
||||
// ? "cld-dkr-hipmi-stg.wibudev.com"
|
||||
// : "hipmi.muku.id"; // Production domain
|
||||
|
||||
export default {
|
||||
name: "HIPMI Badung Connect",
|
||||
slug: "hipmi-mobile",
|
||||
@@ -20,8 +31,10 @@ export default {
|
||||
NSLocationWhenInUseUsageDescription:
|
||||
"Aplikasi membutuhkan akses lokasi untuk menampilkan peta.",
|
||||
},
|
||||
associatedDomains: ["applinks:cld-dkr-staging-hipmi.wibudev.com"],
|
||||
buildNumber: "3",
|
||||
associatedDomains: [
|
||||
"applinks:cld-dkr-hipmi-stg.wibudev.com",
|
||||
],
|
||||
buildNumber: "5",
|
||||
},
|
||||
|
||||
android: {
|
||||
@@ -41,7 +54,7 @@ export default {
|
||||
data: [
|
||||
{
|
||||
scheme: "https",
|
||||
host: "cld-dkr-staging-hipmi.wibudev.com",
|
||||
host: "cld-dkr-hipmi-stg.wibudev.com",
|
||||
pathPrefix: "/",
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { BackButton } from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import PdfViewer from "@/components/_ShareComponent/PdfViewer";
|
||||
import API_STRORAGE from "@/constants/base-url-api-strorage";
|
||||
import { Stack, useLocalSearchParams } from "expo-router";
|
||||
@@ -7,13 +8,12 @@ import { SafeAreaView } from "react-native-safe-area-context";
|
||||
export default function FileScreen() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const url = API_STRORAGE.GET({ fileId: id as string });
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "File",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="File" left={<BackButton />} />,
|
||||
}}
|
||||
/>
|
||||
<SafeAreaView style={{ flex: 1 }} edges={["bottom"]}>
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
import { BackButton } from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IconPlus } from "@/components/_Icon";
|
||||
import { IconDot } from "@/components/_Icon/IconComponent";
|
||||
import LeftButtonCustom from "@/components/Button/BackButton";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
|
||||
import { HeaderStyles } from "@/styles/header-styles";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { router, Stack } from "expo-router";
|
||||
|
||||
export default function UserLayout() {
|
||||
return (
|
||||
<>
|
||||
<Stack screenOptions={HeaderStyles}>
|
||||
<Stack>
|
||||
<Stack.Screen
|
||||
name="delete-account"
|
||||
options={{
|
||||
title: "Hapus Akun",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Hapus Akun" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
@@ -47,8 +46,7 @@ export default function UserLayout() {
|
||||
<Stack.Screen
|
||||
name="user-search/index"
|
||||
options={{
|
||||
title: "Pencarian Pengguna",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Pencarian Pengguna" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -71,10 +69,18 @@ export default function UserLayout() {
|
||||
|
||||
{/* ========== Event Section ========= */}
|
||||
|
||||
{/* <Stack.Screen
|
||||
name="event/(tabs)"
|
||||
options={{
|
||||
header: () => <AppHeader title="Event" left={<BackButton path="/home" />} />,
|
||||
}}
|
||||
/> */}
|
||||
|
||||
<Stack.Screen
|
||||
name="event/(tabs)"
|
||||
options={{
|
||||
title: "Event",
|
||||
header: () => <AppHeader title="Event" left={<BackButton path="/home" />} />,
|
||||
// NOTE: DIPINDAH DI FILE /Event/(Tabs)/_layout.tsx
|
||||
// headerLeft: () => (
|
||||
// <LeftButtonCustom path="/(application)/(user)/home" />
|
||||
@@ -85,32 +91,28 @@ export default function UserLayout() {
|
||||
<Stack.Screen
|
||||
name="event/create"
|
||||
options={{
|
||||
title: "Tambah Event",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Tambah Event" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="event/detail/[id]"
|
||||
options={{
|
||||
title: "Event Detail",
|
||||
headerLeft: () => <LeftButtonCustom />,
|
||||
header: () => <AppHeader title="Event Detail" left={<LeftButtonCustom />} />,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="event/[id]/edit"
|
||||
options={{
|
||||
title: "Edit Event",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Edit Event" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="event/[id]/list-of-participants"
|
||||
options={{
|
||||
title: "Daftar peserta",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Daftar peserta" />,
|
||||
}}
|
||||
/>
|
||||
{/* ========== End Event Section ========= */}
|
||||
@@ -119,22 +121,19 @@ export default function UserLayout() {
|
||||
<Stack.Screen
|
||||
name="collaboration/(tabs)"
|
||||
options={{
|
||||
title: "Collaboration",
|
||||
headerLeft: () => <BackButton path="/home" />,
|
||||
header: () => <AppHeader title="Collaboration" left={<BackButton path="/home" />} />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="collaboration/create"
|
||||
options={{
|
||||
title: "Tambah Proyek",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Tambah Proyek" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="collaboration/[id]/list-of-participants"
|
||||
options={{
|
||||
title: "Daftar Partisipan",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Daftar Partisipan" />,
|
||||
}}
|
||||
/>
|
||||
{/* <Stack.Screen
|
||||
@@ -147,22 +146,19 @@ export default function UserLayout() {
|
||||
<Stack.Screen
|
||||
name="collaboration/[id]/edit"
|
||||
options={{
|
||||
title: "Edit Proyek",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Edit Proyek" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="collaboration/[id]/create-pacticipants"
|
||||
options={{
|
||||
title: "Ajukan Partisipasi",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Ajukan Partisipasi" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="collaboration/[id]/select-of-participants"
|
||||
options={{
|
||||
title: "Pilih Partisipan",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Pilih Partisipan" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -172,29 +168,25 @@ export default function UserLayout() {
|
||||
<Stack.Screen
|
||||
name="voting/create"
|
||||
options={{
|
||||
title: "Tambah Voting",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Tambah Voting" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="voting/(tabs)"
|
||||
options={{
|
||||
title: "Voting",
|
||||
headerLeft: () => <BackButton path="/home" />,
|
||||
header: () => <AppHeader title="Voting" left={<BackButton path="/home" />} />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="voting/[id]/edit"
|
||||
options={{
|
||||
title: "Edit Voting",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Edit Voting" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="voting/[id]/list-of-contributor"
|
||||
options={{
|
||||
title: "Daftar Kontributor",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Daftar Kontributor" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -204,8 +196,7 @@ export default function UserLayout() {
|
||||
<Stack.Screen
|
||||
name="crowdfunding/index"
|
||||
options={{
|
||||
title: "Crowdfunding",
|
||||
headerLeft: () => <BackButton path="/home" />,
|
||||
header: () => <AppHeader title="Crowdfunding" left={<BackButton path="/home" />} />,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -215,103 +206,95 @@ export default function UserLayout() {
|
||||
<Stack.Screen
|
||||
name="investment/(tabs)"
|
||||
options={{
|
||||
title: "Investasi",
|
||||
headerLeft: () => <BackButton path="/crowdfunding" />,
|
||||
header: () => <AppHeader title="Investasi" left={<BackButton path="/crowdfunding" />} />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="investment/create"
|
||||
options={{
|
||||
title: "Tambah Investasi",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Tambah Investasi" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="investment/[id]/index"
|
||||
options={{
|
||||
title: "Detail Investasi",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Detail Investasi" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="investment/[id]/edit"
|
||||
options={{
|
||||
title: "Edit Investasi",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Edit Investasi" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="investment/[id]/edit-prospectus"
|
||||
options={{
|
||||
title: "Edit Prospektus",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Edit Prospektus" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="investment/[id]/(document)/list-of-document"
|
||||
options={{
|
||||
title: "Daftar Dokumen",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Daftar Dokumen" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="investment/[id]/(document)/add-document"
|
||||
options={{
|
||||
title: "Tambah Dokumen",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Tambah Dokumen" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="investment/[id]/(document)/edit-document"
|
||||
options={{
|
||||
title: "Edit Dokumen",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Edit Dokumen" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="investment/[id]/(news)/add-news"
|
||||
options={{
|
||||
title: "Tambah Berita",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Tambah Berita" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="investment/[id]/investor"
|
||||
options={{
|
||||
title: "Investor",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Investor" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="investment/[id]/(transaction-flow)/index"
|
||||
options={{
|
||||
title: "Pembelian Saham",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Pembelian Saham" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="investment/[id]/(transaction-flow)/select-bank"
|
||||
options={{
|
||||
title: "Pilih Bank",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Pilih Bank" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="investment/[id]/(transaction-flow)/invoice"
|
||||
options={{
|
||||
title: "Invoice",
|
||||
headerLeft: () => (
|
||||
<Ionicons
|
||||
name="close"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color={MainColor.yellow}
|
||||
onPress={() =>
|
||||
router.navigate(`/investment/(tabs)/transaction`)
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Invoice"
|
||||
left={
|
||||
<Ionicons
|
||||
name="close"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color={MainColor.yellow}
|
||||
onPress={() =>
|
||||
router.navigate(`/investment/(tabs)/transaction`)
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
),
|
||||
@@ -320,14 +303,18 @@ export default function UserLayout() {
|
||||
<Stack.Screen
|
||||
name="investment/[id]/(transaction-flow)/process"
|
||||
options={{
|
||||
title: "Proses",
|
||||
headerLeft: () => (
|
||||
<Ionicons
|
||||
name="close"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color={MainColor.yellow}
|
||||
onPress={() =>
|
||||
router.navigate(`/investment/(tabs)/transaction`)
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Proses"
|
||||
left={
|
||||
<Ionicons
|
||||
name="close"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color={MainColor.yellow}
|
||||
onPress={() =>
|
||||
router.navigate(`/investment/(tabs)/transaction`)
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
),
|
||||
@@ -336,23 +323,20 @@ export default function UserLayout() {
|
||||
<Stack.Screen
|
||||
name="investment/[id]/(transaction-flow)/success"
|
||||
options={{
|
||||
title: "Transaksi Berhasil",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Transaksi Berhasil" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="investment/[id]/(transaction-flow)/failed"
|
||||
options={{
|
||||
title: "Transaksi Gagal",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Transaksi Gagal" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="investment/[id]/(my-holding)/[id]"
|
||||
options={{
|
||||
title: "Detail Saham Saya",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Detail Saham Saya" />,
|
||||
}}
|
||||
/>
|
||||
{/* ========== End Investment Section ========= */}
|
||||
@@ -361,122 +345,111 @@ export default function UserLayout() {
|
||||
<Stack.Screen
|
||||
name="donation/(tabs)"
|
||||
options={{
|
||||
title: "Donasi",
|
||||
headerLeft: () => <BackButton path="/crowdfunding" />,
|
||||
header: () => <AppHeader title="Donasi" left={<BackButton path="/crowdfunding" />} />,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="donation/create"
|
||||
options={{
|
||||
title: "Tambah Donasi",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Tambah Donasi" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="donation/create-story"
|
||||
options={{
|
||||
title: "Tambah Donasi",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Tambah Donasi" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="donation/[id]/edit"
|
||||
options={{
|
||||
title: "Edit Donasi",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Edit Donasi" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="donation/[id]/edit-story"
|
||||
options={{
|
||||
title: "Edit Donasi",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Edit Donasi" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="donation/[id]/edit-rekening"
|
||||
options={{
|
||||
title: "Edit Rekening",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Edit Rekening" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="donation/[id]/detail-story"
|
||||
options={{
|
||||
title: "Cerita Penggalang",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Cerita Penggalang" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="donation/[id]/infromation-fundrising"
|
||||
options={{
|
||||
title: "Informasi Penggalang Dana",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Informasi Penggalang Dana" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="donation/[id]/list-of-donatur"
|
||||
options={{
|
||||
title: "Daftar Donatur",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Daftar Donatur" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="donation/[id]/fund-disbursement"
|
||||
options={{
|
||||
title: "Pencairan Dana",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Pencairan Dana" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="donation/[id]/(news)/recap-of-news"
|
||||
options={{
|
||||
title: "Rekap Kabar",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Rekap Kabar" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="donation/[id]/(news)/add-news"
|
||||
options={{
|
||||
title: "Tambah Berita",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Tambah Berita" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="donation/[id]/(news)/[news]/edit-news"
|
||||
options={{
|
||||
title: "Edit Berita",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Edit Berita" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="donation/[id]/(transaction-flow)/index"
|
||||
options={{
|
||||
title: "Donasi",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Donasi" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="donation/[id]/(transaction-flow)/select-bank"
|
||||
options={{
|
||||
title: "Pilih Bank",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Pilih Bank" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="donation/[id]/(transaction-flow)/[invoiceId]/invoice"
|
||||
options={{
|
||||
title: "Invoice",
|
||||
headerLeft: () => (
|
||||
<Ionicons
|
||||
name="close"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color={MainColor.yellow}
|
||||
onPress={() => router.navigate(`/donation/(tabs)/my-donation`)}
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Invoice"
|
||||
left={
|
||||
<Ionicons
|
||||
name="close"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color={MainColor.yellow}
|
||||
onPress={() => router.navigate(`/donation/(tabs)/my-donation`)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
@@ -484,13 +457,17 @@ export default function UserLayout() {
|
||||
<Stack.Screen
|
||||
name="donation/[id]/(transaction-flow)/[invoiceId]/process"
|
||||
options={{
|
||||
title: "Proses",
|
||||
headerLeft: () => (
|
||||
<Ionicons
|
||||
name="close"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color={MainColor.yellow}
|
||||
onPress={() => router.navigate(`/donation/(tabs)/my-donation`)}
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Proses"
|
||||
left={
|
||||
<Ionicons
|
||||
name="close"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color={MainColor.yellow}
|
||||
onPress={() => router.navigate(`/donation/(tabs)/my-donation`)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
@@ -498,55 +475,51 @@ export default function UserLayout() {
|
||||
<Stack.Screen
|
||||
name="donation/[id]/(transaction-flow)/[invoiceId]/success"
|
||||
options={{
|
||||
title: "Donasi Berhasil",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Donasi Berhasil" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="donation/[id]/(transaction-flow)/[invoiceId]/failed"
|
||||
options={{
|
||||
title: "Donasi Gagal",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Donasi Gagal" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* ========== End Donation Section ========= */}
|
||||
|
||||
{/* ========== Job Section ========= */}
|
||||
|
||||
|
||||
<Stack.Screen
|
||||
name="job/create"
|
||||
options={{
|
||||
title: "Tambah Job",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Tambah Job" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="job/(tabs)"
|
||||
options={{
|
||||
title: "Job Vacancy",
|
||||
// headerLeft: () => <BackButton path="/home" />,
|
||||
// NOTE: headerLeft di pindahkan ke Tabs Layout
|
||||
header: () => <AppHeader title="Job Vacancy" left={<BackButton path="/home" />} />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="job/[id]/index"
|
||||
options={{
|
||||
title: "Detail Job",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Detail Job" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="job/[id]/edit"
|
||||
options={{
|
||||
title: "Edit Job",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Edit Job" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="job/[id]/archive"
|
||||
options={{
|
||||
title: "Arsip Job",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Arsip Job" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -556,78 +529,67 @@ export default function UserLayout() {
|
||||
<Stack.Screen
|
||||
name="forum/create"
|
||||
options={{
|
||||
title: "Tambah Diskusi",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Tambah Diskusi" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="forum/[id]/edit"
|
||||
options={{
|
||||
title: "Edit Diskusi",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Edit Diskusi" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="forum/[id]/forumku"
|
||||
options={{
|
||||
title: "Forumku",
|
||||
headerLeft: () => <BackButton icon={"close"} />,
|
||||
header: () => <AppHeader title="Forumku" left={<BackButton icon={"close"} />} />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="forum/[id]/index"
|
||||
options={{
|
||||
title: "Detail",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Detail" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="forum/[id]/report-commentar"
|
||||
options={{
|
||||
title: "Laporkan Komentar",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Laporkan Komentar" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="forum/[id]/other-report-commentar"
|
||||
options={{
|
||||
title: "Laporkan Komentar",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Laporkan Komentar" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="forum/[id]/report-posting"
|
||||
options={{
|
||||
title: "Laporkan Diskusi",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Laporkan Diskusi" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="forum/[id]/other-report-posting"
|
||||
options={{
|
||||
title: "Laporkan Diskusi",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Laporkan Diskusi" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="forum/terms"
|
||||
options={{
|
||||
title: "Syarat & Ketentuan Forum",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Syarat & Ketentuan Forum" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="forum/[id]/preview-report-posting"
|
||||
options={{
|
||||
title: "Laporan Postingan",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Laporan Postingan" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="forum/[id]/preview-report-comment"
|
||||
options={{
|
||||
title: "Laporan Komentar",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Laporan Komentar" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -635,29 +597,25 @@ export default function UserLayout() {
|
||||
<Stack.Screen
|
||||
name="maps/index"
|
||||
options={{
|
||||
title: "Maps",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Maps" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="maps/create"
|
||||
options={{
|
||||
title: "Tambah Maps",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Tambah Maps" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="maps/[id]/edit"
|
||||
options={{
|
||||
title: "Edit Maps",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Edit Maps" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="maps/[id]/custom-pin"
|
||||
options={{
|
||||
title: "Custom Pin Maps",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Custom Pin Maps" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -665,8 +623,7 @@ export default function UserLayout() {
|
||||
<Stack.Screen
|
||||
name="marketplace/index"
|
||||
options={{
|
||||
title: "Market Place",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Market Place" />,
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { apiCollaborationGroup } from "@/service/api-client/api-collaboration";
|
||||
import { Stack, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { useState, useCallback } from "react";
|
||||
@@ -40,8 +41,7 @@ export default function CollaborationRoomInfo() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: `Info`,
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Info" left={<BackButton />} />,
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { BackButton } from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
|
||||
import ChatScreen from "@/screens/Collaboration/GroupChatSection";
|
||||
@@ -12,14 +13,18 @@ export default function CollaborationRoomChat() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: `Proyek ${detail}`,
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => (
|
||||
<Feather
|
||||
name="info"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color={MainColor.yellow}
|
||||
onPress={() => router.push(`/collaboration/${id}/${detail}/info`)}
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title={`Proyek ${detail}`}
|
||||
left={<BackButton />}
|
||||
right={
|
||||
<Feather
|
||||
name="info"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color={MainColor.yellow}
|
||||
onPress={() => router.push(`/collaboration/${id}/${detail}/info`)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
MenuDrawerDynamicGrid,
|
||||
ViewWrapper
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection";
|
||||
import { apiCollaborationGetOne } from "@/service/api-client/api-collaboration";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
@@ -38,10 +39,14 @@ export default function CollaborationDetailParticipant() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Detail Proyek",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => (
|
||||
<DotButton onPress={() => setOpenDrawerParticipant(true)} />
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Detail Proyek"
|
||||
left={<BackButton />}
|
||||
right={
|
||||
<DotButton onPress={() => setOpenDrawerParticipant(true)} />
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
Spacing,
|
||||
ViewWrapper
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IconEdit } from "@/components/_Icon";
|
||||
import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection";
|
||||
import {
|
||||
@@ -66,9 +67,13 @@ export default function CollaborationDetailProjectMain() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Proyek Saya",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Proyek Saya"
|
||||
left={<BackButton />}
|
||||
right={<DotButton onPress={() => setOpenDrawer(true)} />}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
MenuDrawerDynamicGrid,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection";
|
||||
import {
|
||||
@@ -74,10 +75,14 @@ export default function CollaborationDetail() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Detail Proyek",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => (
|
||||
<DotButton onPress={() => setOpenDrawerMenu(true)} />
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Detail Proyek"
|
||||
left={<BackButton />}
|
||||
right={
|
||||
<DotButton onPress={() => setOpenDrawerMenu(true)} />
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IconEdit } from "@/components/_Icon";
|
||||
import { IconTrash } from "@/components/_Icon/IconTrash";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
@@ -57,12 +58,17 @@ export default function DonationNews() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Detail Kabar",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () =>
|
||||
user?.id === data?.authorId && (
|
||||
<DotButton onPress={() => setOpenDrawer(true)} />
|
||||
),
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Detail Kabar"
|
||||
left={<BackButton />}
|
||||
right={
|
||||
user?.id === data?.authorId && (
|
||||
<DotButton onPress={() => setOpenDrawer(true)} />
|
||||
)
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
NewWrapper,
|
||||
Spacing,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IconEdit, IconNews } from "@/components/_Icon";
|
||||
import { IMenuDrawerItem } from "@/components/_Interface/types";
|
||||
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
|
||||
@@ -97,14 +98,19 @@ export default function DonasiDetailStatus() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: `Detail ${_.startCase(status as string)}`,
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () =>
|
||||
status === "draft" ? (
|
||||
<DotButton onPress={() => setOpenDrawer(true)} />
|
||||
) : status === "publish" ? (
|
||||
<DotButton onPress={() => setOpenDrawerPublish(true)} />
|
||||
) : null,
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title={`Detail ${_.startCase(status as string)}`}
|
||||
left={<BackButton />}
|
||||
right={
|
||||
status === "draft" ? (
|
||||
<DotButton onPress={() => setOpenDrawer(true)} />
|
||||
) : status === "publish" ? (
|
||||
<DotButton onPress={() => setOpenDrawerPublish(true)} />
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<NewWrapper
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
StackCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IconNews } from "@/components/_Icon";
|
||||
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
@@ -90,12 +91,17 @@ export default function DonasiDetailBeranda() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: `Detail Donasi`,
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () =>
|
||||
user?.id === data?.Author?.id ? (
|
||||
<DotButton onPress={() => setOpenDrawer(true)} />
|
||||
) : null,
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Detail Donasi"
|
||||
left={<BackButton />}
|
||||
right={
|
||||
user?.id === data?.Author?.id ? (
|
||||
<DotButton onPress={() => setOpenDrawer(true)} />
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<NewWrapper footerComponent={buttonSection}>
|
||||
|
||||
@@ -4,36 +4,34 @@ import {
|
||||
IconHome,
|
||||
IconStatus,
|
||||
} from "@/components/_Icon";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import BackButtonFromNotification from "@/components/Button/BackButtonFromNotification";
|
||||
import { TabsStyles } from "@/styles/tabs-styles";
|
||||
import { router, Tabs, useLocalSearchParams, useNavigation } from "expo-router";
|
||||
import { useLayoutEffect } from "react";
|
||||
import { router, Tabs, useLocalSearchParams } from "expo-router";
|
||||
|
||||
export default function EventTabsLayout() {
|
||||
const navigation = useNavigation();
|
||||
|
||||
const { from, category } = useLocalSearchParams<{
|
||||
from?: string;
|
||||
category?: string;
|
||||
}>();
|
||||
|
||||
console.log("from", from);
|
||||
console.log("category", category);
|
||||
|
||||
// Atur header secara dinamis
|
||||
useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerLeft: () => (
|
||||
<BackButtonFromNotification
|
||||
from={from as string}
|
||||
category={category as string}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}, [from, router, navigation]);
|
||||
|
||||
return (
|
||||
<Tabs screenOptions={TabsStyles}>
|
||||
<Tabs
|
||||
screenOptions={{
|
||||
...TabsStyles,
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Event"
|
||||
left={
|
||||
<BackButtonFromNotification
|
||||
from={from as string}
|
||||
category={category as string}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
>
|
||||
<Tabs.Screen
|
||||
name="index"
|
||||
options={{
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IMenuDrawerItem } from "@/components/_Interface/types";
|
||||
import LeftButtonCustom from "@/components/Button/BackButton";
|
||||
import Event_ButtonStatusSection from "@/screens/Event/ButtonStatusSection";
|
||||
@@ -81,12 +82,17 @@ export default function EventDetailStatus() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: `Detail ${status === "publish" ? "" : status}`,
|
||||
headerLeft: () => <LeftButtonCustom />,
|
||||
headerRight: () =>
|
||||
status === "draft" ? (
|
||||
<DotButton onPress={() => setOpenDrawer(true)} />
|
||||
) : null,
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title={`Detail ${status === "publish" ? "" : status}`}
|
||||
left={<LeftButtonCustom />}
|
||||
right={
|
||||
status === "draft" ? (
|
||||
<DotButton onPress={() => setOpenDrawer(true)} />
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
|
||||
@@ -9,7 +9,8 @@ import {
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { AccentColor, MainColor } from "@/constants/color-palet";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import {
|
||||
apiEventConfirmationAction,
|
||||
@@ -60,7 +61,7 @@ export default function UserEventConfirmation() {
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
checkTokenAndDataParticipants() || console.log("Token is null");
|
||||
}, [token, id, user?.id])
|
||||
}, [token, id, user?.id]),
|
||||
);
|
||||
|
||||
const checkTokenAndDataParticipants = async () => {
|
||||
@@ -113,7 +114,7 @@ export default function UserEventConfirmation() {
|
||||
confirmationStart,
|
||||
confirmationEnd,
|
||||
null,
|
||||
"[]"
|
||||
"[]",
|
||||
);
|
||||
|
||||
// --- [4] Status waktu event (untuk pesan UI) ---
|
||||
@@ -218,9 +219,14 @@ export default function UserEventConfirmation() {
|
||||
if (isWithinConfirmationWindow) {
|
||||
if (konfirmasi === false) {
|
||||
return (
|
||||
<TamplateBox data={data}>
|
||||
<TamplateText text="Konfirmasi Kehadiran" />
|
||||
</TamplateBox>
|
||||
// <TamplateBox data={data}>
|
||||
// <TamplateText text="Konfirmasi Kehadiran" />
|
||||
// </TamplateBox>
|
||||
<UserParticipan_And_DuringEvent
|
||||
id={data.id}
|
||||
userId={user?.id as string}
|
||||
data={data}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
@@ -260,18 +266,20 @@ export default function UserEventConfirmation() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Konfirmasi Event",
|
||||
// headerLeft: () => (
|
||||
// <Ionicons
|
||||
// name="arrow-back"
|
||||
// size={20}
|
||||
// color={MainColor.yellow}
|
||||
// onPress={() =>
|
||||
// router.navigate("/(application)/(user)/event/create")
|
||||
// }
|
||||
// />
|
||||
// ),
|
||||
}}
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Konfirmasi Event"
|
||||
left={
|
||||
<Ionicons
|
||||
name="arrow-back"
|
||||
size={20}
|
||||
color={MainColor.yellow}
|
||||
onPress={() => router.navigate("/")}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>{handlerReturn()}</ViewWrapper>
|
||||
</>
|
||||
@@ -497,7 +505,6 @@ const UserNotParticipan_And_DuringEvent = ({
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
// 🟡 ZONA ACARA BERLANGSUN
|
||||
// User sudah terdaftar & Event sedang berlangsung & user harus konfirmasi
|
||||
const UserParticipan_And_DuringEvent = ({
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
Spacing,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IMenuDrawerItem } from "@/components/_Interface/types";
|
||||
import LeftButtonCustom from "@/components/Button/BackButton";
|
||||
import Event_BoxDetailPublishSection from "@/screens/Event/BoxDetailPublishSection";
|
||||
@@ -49,9 +50,13 @@ export default function EventDetailContribution() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: `Detail kontribusi`,
|
||||
headerLeft: () => <LeftButtonCustom />,
|
||||
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Detail kontribusi"
|
||||
left={<LeftButtonCustom />}
|
||||
right={<DotButton onPress={() => setOpenDrawer(true)} />}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
ViewWrapper,
|
||||
Spacing,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IMenuDrawerItem } from "@/components/_Interface/types";
|
||||
import LeftButtonCustom from "@/components/Button/BackButton";
|
||||
import Event_BoxDetailPublishSection from "@/screens/Event/BoxDetailPublishSection";
|
||||
@@ -44,9 +45,13 @@ export default function EventDetailHistory() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: `Detail riwayat`,
|
||||
headerLeft: () => <LeftButtonCustom />,
|
||||
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Detail riwayat"
|
||||
left={<LeftButtonCustom />}
|
||||
right={<DotButton onPress={() => setOpenDrawer(true)} />}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
MenuDrawerDynamicGrid,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IMenuDrawerItem } from "@/components/_Interface/types";
|
||||
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
|
||||
import LeftButtonCustom from "@/components/Button/BackButton";
|
||||
@@ -156,9 +157,13 @@ export default function EventDetailPublish() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: `Event Publish`,
|
||||
headerLeft: () => <BackButton onPress={() => router.back()} />,
|
||||
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Event Publish"
|
||||
left={<BackButton onPress={() => router.back()} />}
|
||||
right={<DotButton onPress={() => setOpenDrawer(true)} />}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* 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 CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
@@ -117,29 +118,37 @@ export default function Application() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: `HIPMI`,
|
||||
headerLeft: () =>
|
||||
data ? (
|
||||
<Ionicons
|
||||
name="search"
|
||||
size={20}
|
||||
color={MainColor.yellow}
|
||||
onPress={() => {
|
||||
router.push("/user-search");
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<CustomSkeleton height={30} width={30} radius={100} />
|
||||
),
|
||||
headerRight: () =>
|
||||
data ? (
|
||||
<HeaderBell />
|
||||
) : (
|
||||
<CustomSkeleton height={30} width={30} radius={100} />
|
||||
),
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="HIPMI"
|
||||
showBack={false}
|
||||
left={
|
||||
data ? (
|
||||
<Ionicons
|
||||
name="search"
|
||||
size={20}
|
||||
color={MainColor.yellow}
|
||||
onPress={() => {
|
||||
router.push("/user-search");
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<CustomSkeleton height={30} width={30} radius={100} />
|
||||
)
|
||||
}
|
||||
right={
|
||||
data ? (
|
||||
<HeaderBell />
|
||||
) : (
|
||||
<CustomSkeleton height={30} width={30} radius={100} />
|
||||
)
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper
|
||||
|
||||
<NewWrapper
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
@@ -157,18 +166,19 @@ export default function Application() {
|
||||
})}
|
||||
/>
|
||||
) : (
|
||||
<View style={GStyles.tabBar}>
|
||||
<View style={[GStyles.tabContainer, { paddingTop: 10 }]}>
|
||||
{Array.from({ length: 4 }).map((e, index) => (
|
||||
<CustomSkeleton
|
||||
key={index}
|
||||
height={40}
|
||||
width={40}
|
||||
radius={100}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
null
|
||||
// <View style={GStyles.tabBar}>
|
||||
// <View style={[GStyles.tabContainer, { paddingTop: 10 }]}>
|
||||
// {Array.from({ length: 4 }).map((e, index) => (
|
||||
// <CustomSkeleton
|
||||
// key={index}
|
||||
// height={40}
|
||||
// width={40}
|
||||
// radius={100}
|
||||
// />
|
||||
// ))}
|
||||
// </View>
|
||||
// </View>
|
||||
)
|
||||
}
|
||||
>
|
||||
@@ -192,10 +202,10 @@ export default function Application() {
|
||||
{data ? (
|
||||
<Home_BottomFeatureSection listData={listData} />
|
||||
) : (
|
||||
<CustomSkeleton height={200} />
|
||||
<CustomSkeleton height={150} />
|
||||
)}
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
</NewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IconDocument, IconEdit, IconNews } from "@/components/_Icon";
|
||||
import { IMenuDrawerItem } from "@/components/_Interface/types";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
@@ -30,13 +31,13 @@ export default function InvestmentDetailHolding() {
|
||||
const [openDrawerDraft, setOpenDrawerDraft] = useState(false);
|
||||
const [openDrawerPublish, setOpenDrawerPublish] = useState(false);
|
||||
const [data, setData] = useState<any>(null);
|
||||
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id, status])
|
||||
);
|
||||
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiInvestmentGetInvoice({
|
||||
@@ -44,7 +45,7 @@ export default function InvestmentDetailHolding() {
|
||||
authorId: user?.id,
|
||||
category: "invoice",
|
||||
});
|
||||
|
||||
|
||||
console.log("[DATA]", JSON.stringify(response.data, null, 2));
|
||||
setData(response.data);
|
||||
} catch (error) {
|
||||
@@ -76,14 +77,19 @@ export default function InvestmentDetailHolding() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: `Detail ${_.startCase(status as string)}`,
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () =>
|
||||
status === "draft" ? (
|
||||
<DotButton onPress={() => setOpenDrawerDraft(true)} />
|
||||
) : status === "publish" ? (
|
||||
<DotButton onPress={() => setOpenDrawerPublish(true)} />
|
||||
) : null,
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title={`Detail ${_.startCase(status as string)}`}
|
||||
left={<BackButton />}
|
||||
right={
|
||||
status === "draft" ? (
|
||||
<DotButton onPress={() => setOpenDrawerDraft(true)} />
|
||||
) : status === "publish" ? (
|
||||
<DotButton onPress={() => setOpenDrawerPublish(true)} />
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IconTrash } from "@/components/_Icon/IconTrash";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import {
|
||||
@@ -56,12 +57,17 @@ export default function InvestmentNews() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Detail Berita",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () =>
|
||||
user?.id === data?.authorId && (
|
||||
<DotButton onPress={() => setOpenDrawer(true)} />
|
||||
),
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Detail Berita"
|
||||
left={<BackButton />}
|
||||
right={
|
||||
user?.id === data?.authorId && (
|
||||
<DotButton onPress={() => setOpenDrawer(true)} />
|
||||
)
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
MenuDrawerDynamicGrid,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IconDocument, IconEdit, IconNews } from "@/components/_Icon";
|
||||
import { IMenuDrawerItem } from "@/components/_Interface/types";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
@@ -106,14 +107,19 @@ export default function InvestmentDetailStatus() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: `Detail ${_.startCase(status as string)}`,
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () =>
|
||||
status === "draft" ? (
|
||||
<DotButton onPress={() => setOpenDrawerDraft(true)} />
|
||||
) : status === "publish" ? (
|
||||
<DotButton onPress={() => setOpenDrawerPublish(true)} />
|
||||
) : null,
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title={`Detail ${_.startCase(status as string)}`}
|
||||
left={<BackButton />}
|
||||
right={
|
||||
status === "draft" ? (
|
||||
<DotButton onPress={() => setOpenDrawerDraft(true)} />
|
||||
) : status === "publish" ? (
|
||||
<DotButton onPress={() => setOpenDrawerPublish(true)} />
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
MenuDrawerDynamicGrid,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IconDocument, IconEdit, IconNews } from "@/components/_Icon";
|
||||
import { IMenuDrawerItem } from "@/components/_Interface/types";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
@@ -105,14 +106,19 @@ export default function InvestmentDetail() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: `Detail ${_.startCase(status as string)}`,
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () =>
|
||||
status === "draft" ? (
|
||||
<DotButton onPress={() => setOpenDrawerDraft(true)} />
|
||||
) : status === "publish" ? (
|
||||
<DotButton onPress={() => setOpenDrawerPublish(true)} />
|
||||
) : null,
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title={`Detail ${_.startCase(status as string)}`}
|
||||
left={<BackButton />}
|
||||
right={
|
||||
status === "draft" ? (
|
||||
<DotButton onPress={() => setOpenDrawerDraft(true)} />
|
||||
) : status === "publish" ? (
|
||||
<DotButton onPress={() => setOpenDrawerPublish(true)} />
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { BackButton } from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IconHome, IconStatus } from "@/components/_Icon";
|
||||
import BackButtonFromNotification from "@/components/Button/BackButtonFromNotification";
|
||||
import { TabsStyles } from "@/styles/tabs-styles";
|
||||
@@ -7,31 +8,30 @@ import { Ionicons } from "@expo/vector-icons";
|
||||
import {
|
||||
router,
|
||||
Tabs,
|
||||
useLocalSearchParams,
|
||||
useNavigation
|
||||
useLocalSearchParams
|
||||
} from "expo-router";
|
||||
import { useLayoutEffect } from "react";
|
||||
|
||||
export default function JobTabsLayout() {
|
||||
const navigation = useNavigation();
|
||||
|
||||
const { from, category } = useLocalSearchParams<{
|
||||
from?: string;
|
||||
category?: string;
|
||||
}>();
|
||||
|
||||
// Atur header secara dinamis
|
||||
useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerLeft: () => (
|
||||
<BackButtonFromNotification from={from as string} category={category as string} />
|
||||
),
|
||||
});
|
||||
}, [from, router, navigation]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tabs screenOptions={TabsStyles}>
|
||||
<Tabs
|
||||
screenOptions={{
|
||||
...TabsStyles,
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Job Vacancy"
|
||||
left={
|
||||
<BackButtonFromNotification from={from as string} category={category as string} />
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
>
|
||||
<Tabs.Screen
|
||||
name="index"
|
||||
options={{
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
StackCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IconEdit } from "@/components/_Icon";
|
||||
import { IMenuDrawerItem } from "@/components/_Interface/types";
|
||||
import ReportBox from "@/components/Box/ReportBox";
|
||||
@@ -58,12 +59,17 @@ export default function JobDetailStatus() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: `Detail`,
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () =>
|
||||
status === "draft" ? (
|
||||
<DotButton onPress={() => setOpenDrawer(true)} />
|
||||
) : null,
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Detail"
|
||||
left={<BackButton />}
|
||||
right={
|
||||
status === "draft" ? (
|
||||
<DotButton onPress={() => setOpenDrawer(true)} />
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
|
||||
@@ -1,365 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import {
|
||||
ActionIcon,
|
||||
AvatarComp,
|
||||
BaseBox,
|
||||
ButtonCenteredOnly,
|
||||
CenterCustom,
|
||||
Grid,
|
||||
InformationBox,
|
||||
NewWrapper,
|
||||
SelectCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextAreaCustom,
|
||||
TextCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { IconPlus } from "@/components/_Icon";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_XLARGE } from "@/constants/constans-value";
|
||||
import DUMMY_IMAGE from "@/constants/dummy-image-value";
|
||||
import Portofolio_ButtonCreate from "@/screens/Portofolio/ButtonCreatePortofolio";
|
||||
import {
|
||||
apiMasterBidangBisnis,
|
||||
apiMasterSubBidangBisnis,
|
||||
} from "@/service/api-client/api-master";
|
||||
import {
|
||||
IMasterBidangBisnis,
|
||||
IMasterSubBidangBisnis,
|
||||
} from "@/types/Type-Master";
|
||||
import pickImage from "@/utils/pickImage";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { Image } from "expo-image";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { Text, TouchableOpacity, View } from "react-native";
|
||||
import PhoneInput, { ICountry } from "react-native-international-phone-number";
|
||||
import { Avatar } from "react-native-paper";
|
||||
import { ScreenPortofolioCreate } from "@/screens/Portofolio/ScreenPortofolioCreate";
|
||||
|
||||
export default function PortofolioCreate() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [selectedCountry, setSelectedCountry] = useState<null | ICountry>(null);
|
||||
const [inputValue, setInputValue] = useState<string>("");
|
||||
const [data, setData] = useState({
|
||||
namaBisnis: "",
|
||||
masterBidangBisnisId: "",
|
||||
alamatKantor: "",
|
||||
tlpn: "",
|
||||
deskripsi: "",
|
||||
});
|
||||
const [imageUri, setImageUri] = useState<string | null>(null);
|
||||
|
||||
const [bidangBisnis, setBidangBisnis] = useState<IMasterBidangBisnis[]>([]);
|
||||
const [subBidangBisnis, setSubBidangBisnis] = useState<
|
||||
IMasterSubBidangBisnis[]
|
||||
>([]);
|
||||
|
||||
const [selectedSubBidang, setSelectedSubBidang] = useState<string[]>([]);
|
||||
const [listSubBidangSelected, setListSubBidangSelected] = useState([
|
||||
{
|
||||
id: "",
|
||||
},
|
||||
]);
|
||||
|
||||
const [dataMedsos, setDataMedsos] = useState({
|
||||
facebook: "",
|
||||
twitter: "",
|
||||
instagram: "",
|
||||
youtube: "",
|
||||
tiktok: "",
|
||||
});
|
||||
|
||||
const [isLoadingCreate, setIsLoadingCreate] = useState(false);
|
||||
|
||||
function handleInputValue(phoneNumber: string) {
|
||||
setInputValue(phoneNumber);
|
||||
const callingCode = selectedCountry?.callingCode.replace(/^\+/, "") || "";
|
||||
let fixNumber = inputValue.replace(/\s+/g, "").replace(/^0+/, "");
|
||||
const realNumber = callingCode + fixNumber;
|
||||
setData({ ...data, tlpn: realNumber });
|
||||
}
|
||||
|
||||
function handleSelectedCountry(country: ICountry) {
|
||||
setSelectedCountry(country);
|
||||
}
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadMaster();
|
||||
onLoadMasterSubBidangBisnis();
|
||||
}, [])
|
||||
);
|
||||
|
||||
const onLoadMaster = async () => {
|
||||
try {
|
||||
const response = await apiMasterBidangBisnis();
|
||||
setBidangBisnis(response.data);
|
||||
} catch (error) {
|
||||
setBidangBisnis([]);
|
||||
console.log("Error onLoadMasterBidangBisnis", error);
|
||||
}
|
||||
};
|
||||
|
||||
const onLoadMasterSubBidangBisnis = async () => {
|
||||
try {
|
||||
const response = await apiMasterSubBidangBisnis({});
|
||||
setSubBidangBisnis(response.data);
|
||||
} catch (error) {
|
||||
setSubBidangBisnis([]);
|
||||
console.log("Error onLoadMasterBidangBisnis", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerSelectedSubBidang = ({ id }: { id: string }) => {
|
||||
const selectedList = subBidangBisnis?.filter(
|
||||
(item) => (item?.masterBidangBisnisId as any) === id
|
||||
);
|
||||
setSelectedSubBidang(selectedList as any[]);
|
||||
};
|
||||
|
||||
return (
|
||||
<NewWrapper
|
||||
footerComponent={
|
||||
<Portofolio_ButtonCreate
|
||||
id={id as string}
|
||||
data={data}
|
||||
dataMedsos={dataMedsos}
|
||||
imageUri={imageUri}
|
||||
subBidangSelected={listSubBidangSelected}
|
||||
isLoadingCreate={isLoadingCreate}
|
||||
setIsLoadingCreate={setIsLoadingCreate}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{/* <TextCustom>Portofolio Create {id}</TextCustom> */}
|
||||
<StackCustom gap={"xs"}>
|
||||
<InformationBox text="Lengkapi data bisnis anda." />
|
||||
<TextInputCustom
|
||||
required
|
||||
label="Nama Bisnis"
|
||||
placeholder="Masukkan nama bisnis"
|
||||
onChangeText={(value: any) => setData({ ...data, namaBisnis: value })}
|
||||
/>
|
||||
|
||||
<SelectCustom
|
||||
label="Bidang Usaha"
|
||||
required
|
||||
data={bidangBisnis.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))}
|
||||
value={data.masterBidangBisnisId}
|
||||
onChange={(value) => {
|
||||
const isSameBidang = data.masterBidangBisnisId === value;
|
||||
|
||||
if (!isSameBidang) {
|
||||
setListSubBidangSelected([{ id: "" }]);
|
||||
}
|
||||
|
||||
setData({ ...(data as any), masterBidangBisnisId: value });
|
||||
handlerSelectedSubBidang({ id: value as string });
|
||||
}}
|
||||
/>
|
||||
|
||||
{listSubBidangSelected.map((item, index) => (
|
||||
<SelectCustom
|
||||
key={index}
|
||||
disabled={data.masterBidangBisnisId === ""}
|
||||
label="Sub Bidang Usaha"
|
||||
required
|
||||
data={_.map(selectedSubBidang as any)
|
||||
.filter((option: any) => {
|
||||
const selectedValues = listSubBidangSelected.map((s) => s.id);
|
||||
return (
|
||||
option.id === item.id || // biarkan tetap muncul kalau ini valuenya sendiri
|
||||
!selectedValues.includes(option.id)
|
||||
);
|
||||
})
|
||||
.map((e: any) => ({
|
||||
value: e.id,
|
||||
label: e.name,
|
||||
}))}
|
||||
value={item.id || null}
|
||||
onChange={(value) => {
|
||||
const list = _.clone(listSubBidangSelected);
|
||||
list[index].id = value as any;
|
||||
setListSubBidangSelected(list);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
<CenterCustom>
|
||||
<View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
|
||||
<ActionIcon
|
||||
disabled={
|
||||
selectedSubBidang.length === listSubBidangSelected.length
|
||||
}
|
||||
onPress={() => {
|
||||
setListSubBidangSelected([
|
||||
...listSubBidangSelected,
|
||||
{ id: "" },
|
||||
]);
|
||||
}}
|
||||
icon={
|
||||
<Ionicons
|
||||
name="add-circle-outline"
|
||||
size={ICON_SIZE_XLARGE}
|
||||
color={MainColor.black}
|
||||
/>
|
||||
}
|
||||
size="xl"
|
||||
/>
|
||||
<ActionIcon
|
||||
disabled={listSubBidangSelected.length <= 1}
|
||||
onPress={() => {
|
||||
const list = _.clone(listSubBidangSelected);
|
||||
list.pop();
|
||||
setListSubBidangSelected(list);
|
||||
}}
|
||||
icon={
|
||||
<Ionicons
|
||||
name="remove-circle-outline"
|
||||
size={ICON_SIZE_XLARGE}
|
||||
color={MainColor.black}
|
||||
/>
|
||||
}
|
||||
size="xl"
|
||||
/>
|
||||
</View>
|
||||
</CenterCustom>
|
||||
<Spacing />
|
||||
|
||||
{/* <SelectCustom
|
||||
label="Bidang Usaha"
|
||||
required
|
||||
data={bidangBisnis.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))}
|
||||
value={null}
|
||||
onChange={(value) => {
|
||||
setData({ ...(data as any), masterBidangBisnisId: value });
|
||||
handlerSelectedSubBidang({ id: value as string });
|
||||
}}
|
||||
/> */}
|
||||
|
||||
{/* <ButtonCenteredOnly
|
||||
onPress={() => {
|
||||
setListSubBidangSelected([...listSubBidangSelected, { id: "" }]);
|
||||
}}
|
||||
>
|
||||
Tambah Pilihan
|
||||
</ButtonCenteredOnly>
|
||||
<Spacing /> */}
|
||||
|
||||
{/* <TextCustom>{JSON.stringify(bidangBisnis, null, 2)}</TextCustom> */}
|
||||
|
||||
<View>
|
||||
<View style={{ flexDirection: "row", alignItems: "center" }}>
|
||||
<TextCustom semiBold style={{ color: MainColor.white_gray }}>
|
||||
Nomor Telepon
|
||||
</TextCustom>
|
||||
<Text style={{ color: "red" }}> *</Text>
|
||||
</View>
|
||||
<Spacing height={5} />
|
||||
<PhoneInput
|
||||
value={inputValue}
|
||||
onChangePhoneNumber={handleInputValue}
|
||||
selectedCountry={selectedCountry}
|
||||
onChangeSelectedCountry={handleSelectedCountry}
|
||||
defaultCountry="ID"
|
||||
placeholder="xxx-xxx-xxx"
|
||||
/>
|
||||
</View>
|
||||
<Spacing />
|
||||
|
||||
<TextInputCustom
|
||||
required
|
||||
label="Alamat Bisnis"
|
||||
placeholder="Masukkan alamat bisnis"
|
||||
onChangeText={(value: any) =>
|
||||
setData({ ...data, alamatKantor: value })
|
||||
}
|
||||
/>
|
||||
|
||||
<TextAreaCustom
|
||||
label="Deskripsi Bisnis"
|
||||
placeholder="Masukkan deskripsi bisnis"
|
||||
value={data.deskripsi}
|
||||
onChangeText={(value: any) => setData({ ...data, deskripsi: value })}
|
||||
autosize
|
||||
minRows={2}
|
||||
maxRows={5}
|
||||
required
|
||||
showCount
|
||||
maxLength={1000}
|
||||
/>
|
||||
<Spacing />
|
||||
|
||||
{/* Logo */}
|
||||
<InformationBox text="Upload logo bisnis anda untuk di tampilaka pada portofolio." />
|
||||
|
||||
<CenterCustom>
|
||||
<Avatar.Image
|
||||
source={imageUri ? { uri: imageUri } : DUMMY_IMAGE.dummy_image}
|
||||
size={200}
|
||||
/>
|
||||
</CenterCustom>
|
||||
<Spacing />
|
||||
<ButtonCenteredOnly
|
||||
icon="upload"
|
||||
onPress={() => {
|
||||
pickImage({
|
||||
setImageUri,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Upload
|
||||
</ButtonCenteredOnly>
|
||||
<Spacing height={40} />
|
||||
|
||||
{/* Social Media */}
|
||||
<InformationBox text="Isi hanya pada sosial media yang anda miliki." />
|
||||
<TextInputCustom
|
||||
label="Tiktok"
|
||||
placeholder="Masukkan username tiktok"
|
||||
onChangeText={(value: any) =>
|
||||
setDataMedsos({ ...dataMedsos, tiktok: value })
|
||||
}
|
||||
/>
|
||||
<TextInputCustom
|
||||
label="Facebook"
|
||||
placeholder="Masukkan username facebook"
|
||||
onChangeText={(value: any) =>
|
||||
setDataMedsos({ ...dataMedsos, facebook: value })
|
||||
}
|
||||
/>
|
||||
<TextInputCustom
|
||||
label="Instagram"
|
||||
placeholder="Masukkan username instagram"
|
||||
onChangeText={(value: any) =>
|
||||
setDataMedsos({ ...dataMedsos, instagram: value })
|
||||
}
|
||||
/>
|
||||
<TextInputCustom
|
||||
label="Twitter"
|
||||
placeholder="Masukkan username twitter"
|
||||
onChangeText={(value: any) =>
|
||||
setDataMedsos({ ...dataMedsos, twitter: value })
|
||||
}
|
||||
/>
|
||||
<TextInputCustom
|
||||
label="Youtube"
|
||||
placeholder="Masukkan username youtube"
|
||||
onChangeText={(value: any) =>
|
||||
setDataMedsos({ ...dataMedsos, youtube: value })
|
||||
}
|
||||
/>
|
||||
{/* <Spacing /> */}
|
||||
</StackCustom>
|
||||
</NewWrapper>
|
||||
);
|
||||
return <ScreenPortofolioCreate />;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import LeftButtonCustom from "@/components/Button/BackButton";
|
||||
import GridTwoView from "@/components/_ShareComponent/GridTwoView";
|
||||
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
|
||||
@@ -72,20 +73,23 @@ export default function Portofolio() {
|
||||
{/* Header */}
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Portofolio",
|
||||
headerLeft: () => <LeftButtonCustom />,
|
||||
headerRight: () =>
|
||||
data?.Profile?.id !== profileId ? null : (
|
||||
<TouchableOpacity onPress={openDrawer}>
|
||||
<Ionicons
|
||||
name="ellipsis-vertical"
|
||||
size={20}
|
||||
color={MainColor.yellow}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
),
|
||||
headerStyle: GStyles.headerStyle,
|
||||
headerTitleStyle: GStyles.headerTitleStyle,
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Portofolio"
|
||||
left={<LeftButtonCustom />}
|
||||
right={
|
||||
data?.Profile?.id !== profileId ? null : (
|
||||
<TouchableOpacity onPress={openDrawer}>
|
||||
<Ionicons
|
||||
name="ellipsis-vertical"
|
||||
size={20}
|
||||
color={MainColor.yellow}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import LeftButtonCustom from "@/components/Button/BackButton";
|
||||
import { HeaderStyles } from "@/styles/header-styles";
|
||||
import { Stack } from "expo-router";
|
||||
|
||||
export default function PortofolioLayout() {
|
||||
@@ -7,8 +7,9 @@ export default function PortofolioLayout() {
|
||||
<>
|
||||
<Stack
|
||||
screenOptions={{
|
||||
...HeaderStyles,
|
||||
headerLeft: () => <LeftButtonCustom />,
|
||||
header: () => (
|
||||
<AppHeader title="Portofolio" left={<LeftButtonCustom />} />
|
||||
),
|
||||
}}
|
||||
>
|
||||
{/* <Stack.Screen name="[id]/index" options={{ title: "Portofolio" }} /> */}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { NewWrapper, StackCustom } from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
|
||||
import LeftButtonCustom from "@/components/Button/BackButton";
|
||||
import DrawerCustom from "@/components/Drawer/DrawerCustom";
|
||||
@@ -101,18 +102,20 @@ export default function Profile() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: `Profile`,
|
||||
headerLeft: () => <LeftButtonCustom />,
|
||||
headerRight: () => (
|
||||
<ButtonnDot
|
||||
id={id as string}
|
||||
openDrawer={openDrawer}
|
||||
isUserCheck={isUserCheck()}
|
||||
logout={logout}
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Profile"
|
||||
left={<LeftButtonCustom />}
|
||||
right={
|
||||
<ButtonnDot
|
||||
id={id as string}
|
||||
openDrawer={openDrawer}
|
||||
isUserCheck={isUserCheck()}
|
||||
logout={logout}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
),
|
||||
headerStyle: GStyles.headerStyle,
|
||||
headerTitleStyle: GStyles.headerTitleStyle,
|
||||
}}
|
||||
/>
|
||||
{/* Main View */}
|
||||
|
||||
@@ -1,47 +1,39 @@
|
||||
import { BackButton } from "@/components";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { Stack } from "expo-router";
|
||||
|
||||
export default function ProfileLayout() {
|
||||
return (
|
||||
<>
|
||||
<Stack
|
||||
screenOptions={{
|
||||
headerStyle: GStyles.headerStyle,
|
||||
headerTitleStyle: GStyles.headerTitleStyle,
|
||||
headerTitleAlign: "center",
|
||||
headerBackButtonDisplayMode: "minimal",
|
||||
}}
|
||||
>
|
||||
<Stack>
|
||||
{/* <Stack.Screen name="[id]/index" options={{ headerShown: false }} /> */}
|
||||
<Stack.Screen
|
||||
name="[id]/edit"
|
||||
options={{ title: "Edit Profile", headerLeft: () => <BackButton /> }}
|
||||
options={{ header: () => <AppHeader title="Edit Profile" /> }}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="[id]/update-photo"
|
||||
options={{ title: "Update Foto", headerLeft: () => <BackButton /> }}
|
||||
options={{ header: () => <AppHeader title="Update Foto" /> }}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="[id]/update-background"
|
||||
options={{
|
||||
title: "Update Latar Belakang",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Update Latar Belakang" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="create"
|
||||
options={{ title: "Buat Profile", headerBackVisible: false }}
|
||||
options={{ headerBackVisible: false }}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="[id]/blocked-list"
|
||||
options={{ title: "Daftar Blokir", headerLeft: () => <BackButton /> }}
|
||||
options={{ header: () => <AppHeader title="Daftar Blokir" /> }}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="[id]/detail-blocked"
|
||||
options={{ title: "Detail Blokir", headerLeft: () => <BackButton /> }}
|
||||
options={{ header: () => <AppHeader title="Detail Blokir" /> }}
|
||||
/>
|
||||
</Stack>
|
||||
</>
|
||||
|
||||
@@ -4,36 +4,34 @@ import {
|
||||
IconHome,
|
||||
IconStatus,
|
||||
} from "@/components/_Icon";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import BackButtonFromNotification from "@/components/Button/BackButtonFromNotification";
|
||||
import { TabsStyles } from "@/styles/tabs-styles";
|
||||
import { Tabs, useLocalSearchParams, useNavigation, router } from "expo-router";
|
||||
import { useLayoutEffect } from "react";
|
||||
import { router, Tabs, useLocalSearchParams } from "expo-router";
|
||||
|
||||
export default function VotingTabsLayout() {
|
||||
const navigation = useNavigation();
|
||||
|
||||
const { from, category } = useLocalSearchParams<{
|
||||
from?: string;
|
||||
category?: string;
|
||||
}>();
|
||||
|
||||
console.log("from", from);
|
||||
console.log("category", category);
|
||||
|
||||
// Atur header secara dinamis
|
||||
useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerLeft: () => (
|
||||
<BackButtonFromNotification
|
||||
from={from as string}
|
||||
category={category as string}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}, [from, router, navigation]);
|
||||
|
||||
return (
|
||||
<Tabs screenOptions={TabsStyles}>
|
||||
<Tabs
|
||||
screenOptions={{
|
||||
...TabsStyles,
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Voting"
|
||||
left={
|
||||
<BackButtonFromNotification
|
||||
from={from as string}
|
||||
category={category as string}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
>
|
||||
<Tabs.Screen
|
||||
name="index"
|
||||
options={{
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IconArchive, IconContribution, IconEdit } from "@/components/_Icon";
|
||||
import { IMenuDrawerItem } from "@/components/_Interface/types";
|
||||
import ReportBox from "@/components/Box/ReportBox";
|
||||
@@ -103,14 +104,19 @@ export default function VotingDetailStatus() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: `Detail`,
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () =>
|
||||
status === "draft" ? (
|
||||
<DotButton onPress={() => setOpenDrawerDraft(true)} />
|
||||
) : status === "publish" ? (
|
||||
<DotButton onPress={() => setOpenDrawerPublish(true)} />
|
||||
) : null,
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Detail"
|
||||
left={<BackButton />}
|
||||
right={
|
||||
status === "draft" ? (
|
||||
<DotButton onPress={() => setOpenDrawerDraft(true)} />
|
||||
) : status === "publish" ? (
|
||||
<DotButton onPress={() => setOpenDrawerPublish(true)} />
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
Spacing,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IconContribution } from "@/components/_Icon";
|
||||
import { IMenuDrawerItem } from "@/components/_Interface/types";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
@@ -81,10 +82,14 @@ export default function VotingDetailContribution() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Detail Kontribusi",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => (
|
||||
<DotButton onPress={() => setOpenDrawerPublish(true)} />
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Detail Kontribusi"
|
||||
left={<BackButton />}
|
||||
right={
|
||||
<DotButton onPress={() => setOpenDrawerPublish(true)} />
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
Spacing,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IconContribution } from "@/components/_Icon";
|
||||
import { IMenuDrawerItem } from "@/components/_Interface/types";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
@@ -82,10 +83,14 @@ export default function VotingDetailHistory() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Riwayat Voting",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => (
|
||||
<DotButton onPress={() => setOpenDrawerPublish(true)} />
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Riwayat Voting"
|
||||
left={<BackButton />}
|
||||
right={
|
||||
<DotButton onPress={() => setOpenDrawerPublish(true)} />
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
StackCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IconArchive, IconContribution } from "@/components/_Icon";
|
||||
import { IMenuDrawerItem } from "@/components/_Interface/types";
|
||||
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
|
||||
@@ -142,10 +143,14 @@ export default function VotingDetail() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: `Detail Voting`,
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => (
|
||||
<DotButton onPress={() => setOpenDrawerPublish(true)} />
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Detail Voting"
|
||||
left={<BackButton />}
|
||||
right={
|
||||
<DotButton onPress={() => setOpenDrawerPublish(true)} />
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { BackButton } from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import BackgroundNotificationHandler from "@/components/Notification/BackgroundNotificationHandler";
|
||||
import NotificationInitializer from "@/components/Notification/NotificationInitializer";
|
||||
import { NotificationProvider } from "@/hooks/use-notification-store";
|
||||
import { HeaderStyles } from "@/styles/header-styles";
|
||||
import { Stack } from "expo-router";
|
||||
|
||||
export default function ApplicationLayout() {
|
||||
@@ -20,7 +20,7 @@ export default function ApplicationLayout() {
|
||||
function ApplicationStack() {
|
||||
return (
|
||||
<>
|
||||
<Stack screenOptions={HeaderStyles}>
|
||||
<Stack>
|
||||
<Stack.Screen name="(user)" options={{ headerShown: false }} />
|
||||
<Stack.Screen name="admin" options={{ headerShown: false }} />
|
||||
|
||||
@@ -28,8 +28,7 @@ function ApplicationStack() {
|
||||
<Stack.Screen
|
||||
name="(image)/take-picture/[id]/index"
|
||||
options={{
|
||||
title: "Ambil Gambar",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Ambil Gambar" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -37,8 +36,7 @@ function ApplicationStack() {
|
||||
<Stack.Screen
|
||||
name="(image)/preview-image/[id]/index"
|
||||
options={{
|
||||
title: "Preview Gambar",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Preview Gambar" />,
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import DrawerAdmin from "@/components/Drawer/DrawerAdmin";
|
||||
import NavbarMenu from "@/components/Drawer/NavbarMenu";
|
||||
import NavbarMenu_V2 from "@/components/Drawer/NavbarMenu_V2";
|
||||
@@ -17,6 +18,7 @@ import {
|
||||
ICON_SIZE_XLARGE,
|
||||
} from "@/constants/constans-value";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { useNotificationStore } from "@/hooks/use-notification-store";
|
||||
import AdminNotificationBell from "@/screens/Admin/AdminNotificationBell";
|
||||
import {
|
||||
adminListMenu,
|
||||
@@ -34,12 +36,28 @@ import { useState } from "react";
|
||||
export default function AdminLayout() {
|
||||
const [openDrawerNavbar, setOpenDrawerNavbar] = useState(false);
|
||||
const [openDrawerUser, setOpenDrawerUser] = useState(false);
|
||||
// const [user, setUser] = useState(null);
|
||||
|
||||
const { logout, user } = useAuth();
|
||||
|
||||
console.log("[USER LAYOUT]", JSON.stringify(user, null, 2));
|
||||
|
||||
const headerLeft = () => (
|
||||
<Ionicons
|
||||
name="menu"
|
||||
size={ICON_SIZE_XLARGE}
|
||||
color={MainColor.white}
|
||||
onPress={() => setOpenDrawerNavbar(true)}
|
||||
/>
|
||||
);
|
||||
|
||||
const headerRight = () => (
|
||||
<FontAwesome6
|
||||
name="circle-user"
|
||||
size={ICON_SIZE_MEDIUM}
|
||||
color={MainColor.white}
|
||||
onPress={() => setOpenDrawerUser(true)}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack
|
||||
@@ -51,20 +69,33 @@ export default function AdminLayout() {
|
||||
contentStyle: {
|
||||
borderBottomColor: AccentColor.blue,
|
||||
},
|
||||
headerLeft: () => (
|
||||
<Ionicons
|
||||
name="menu"
|
||||
size={ICON_SIZE_XLARGE}
|
||||
color={MainColor.white}
|
||||
onPress={() => setOpenDrawerNavbar(true)}
|
||||
/>
|
||||
),
|
||||
headerRight: () => (
|
||||
<FontAwesome6
|
||||
name="circle-user"
|
||||
size={ICON_SIZE_MEDIUM}
|
||||
color={MainColor.white}
|
||||
onPress={() => setOpenDrawerUser(true)}
|
||||
|
||||
// headerLeft: () => (
|
||||
// <Ionicons
|
||||
// name="menu"
|
||||
// size={ICON_SIZE_XLARGE}
|
||||
// color={MainColor.white}
|
||||
// onPress={() => setOpenDrawerNavbar(true)}
|
||||
// />
|
||||
// ),
|
||||
// headerRight: () => (
|
||||
// <FontAwesome6
|
||||
// name="circle-user"
|
||||
// size={ICON_SIZE_MEDIUM}
|
||||
// color={MainColor.white}
|
||||
// onPress={() => setOpenDrawerUser(true)}
|
||||
// />
|
||||
// ),
|
||||
|
||||
|
||||
|
||||
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="HIPMI DASHBOARD"
|
||||
showBack={false}
|
||||
left={headerLeft()}
|
||||
right={headerRight()}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { BackButton, StackCustom, TextCustom, ViewWrapper } from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { router, Stack } from "expo-router";
|
||||
|
||||
export default function NotFoundScreen() {
|
||||
@@ -15,7 +16,7 @@ export default function NotFoundScreen() {
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{ headerShown: true, title: "", headerLeft: () => <BackButton onPress={() => handleBack()} /> }}
|
||||
options={{ header: () => <AppHeader title="" left={<BackButton onPress={() => handleBack()} />} /> }}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
<StackCustom
|
||||
|
||||
@@ -12,14 +12,15 @@ export default function BackButtonFromNotification({
|
||||
return (
|
||||
<>
|
||||
<BackButton
|
||||
|
||||
onPress={() => {
|
||||
if (from === "notifications") {
|
||||
router.replace(`/notifications?category=${category}`);
|
||||
router.push(`/notifications?category=${category}`);
|
||||
} else {
|
||||
if (from) {
|
||||
router.replace(`/${from}` as any);
|
||||
router.back();
|
||||
} else {
|
||||
router.navigate("/home");
|
||||
router.back();
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
114
components/_ShareComponent/AppHeader.tsx
Normal file
114
components/_ShareComponent/AppHeader.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { Platform, StyleSheet, Text, View } from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { BackButton } from "..";
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
right?: React.ReactNode;
|
||||
showBack?: boolean;
|
||||
onPressLeft?: () => void;
|
||||
left?: React.ReactNode;
|
||||
};
|
||||
|
||||
export default function AppHeader({
|
||||
title,
|
||||
right,
|
||||
showBack = true,
|
||||
onPressLeft,
|
||||
left,
|
||||
}: Props) {
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
// iOS 16+ detection (Dynamic Island) - insets.top > 47 indicates Dynamic Island
|
||||
const isIOS26Plus =
|
||||
Platform.OS === "ios" && insets.top > 47;
|
||||
|
||||
// Dynamic padding berdasarkan platform dan iOS version
|
||||
const paddingTop =
|
||||
Platform.OS === "ios"
|
||||
? isIOS26Plus
|
||||
? insets.top - 10
|
||||
: insets.top
|
||||
: 10;
|
||||
|
||||
const paddingBottom = Platform.OS === "ios" ? 8 : 13;
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
{
|
||||
backgroundColor: MainColor.darkblue,
|
||||
paddingTop,
|
||||
paddingBottom,
|
||||
},
|
||||
]}
|
||||
pointerEvents="box-none"
|
||||
>
|
||||
{/* Header Container dengan absolute positioning untuk title center */}
|
||||
<View style={styles.headerApp} pointerEvents="box-none">
|
||||
{/* Left Section - Absolute Left */}
|
||||
<View style={styles.headerLeft}>
|
||||
{showBack ? (
|
||||
<BackButton onPress={onPressLeft} />
|
||||
) : left ? (
|
||||
left
|
||||
) : (
|
||||
<View style={styles.placeholder} />
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* Title - Absolute Center */}
|
||||
<View style={styles.headerCenter}>
|
||||
<Text
|
||||
style={styles.headerTitle}
|
||||
numberOfLines={1}
|
||||
ellipsizeMode="tail"
|
||||
>
|
||||
{title ? title.charAt(0).toUpperCase() + title.slice(1) : ""}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Right Section - Absolute Right */}
|
||||
<View style={styles.headerRight}>
|
||||
{right}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
headerApp: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
paddingHorizontal: 16,
|
||||
height: 44, // Fixed height untuk consistency
|
||||
},
|
||||
headerLeft: {
|
||||
position: "absolute",
|
||||
left: 16,
|
||||
zIndex: 1,
|
||||
},
|
||||
headerCenter: {
|
||||
flex: 1,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
headerRight: {
|
||||
position: "absolute",
|
||||
right: 16,
|
||||
zIndex: 1,
|
||||
},
|
||||
placeholder: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
},
|
||||
headerTitle: {
|
||||
color: MainColor.yellow,
|
||||
fontSize: 18,
|
||||
fontWeight: "600",
|
||||
textAlign: "center",
|
||||
},
|
||||
});
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
SafeAreaView,
|
||||
} from "react-native-safe-area-context";
|
||||
import type { ScrollViewProps, FlatListProps } from "react-native";
|
||||
import Spacing from "./Spacing";
|
||||
|
||||
// --- ✅ Tambahkan refreshControl ke BaseProps ---
|
||||
interface BaseProps {
|
||||
@@ -111,7 +112,6 @@ const NewWrapper = (props: NewWrapperProps) => {
|
||||
return `${String(item.id)}-${index}`;
|
||||
})
|
||||
}
|
||||
|
||||
refreshControl={refreshControl} // ✅ dari BaseProps
|
||||
onEndReached={listProps.onEndReached}
|
||||
onEndReachedThreshold={0.5}
|
||||
@@ -156,15 +156,27 @@ const NewWrapper = (props: NewWrapperProps) => {
|
||||
<View style={GStyles.stickyHeader}>{headerComponent}</View>
|
||||
)}
|
||||
|
||||
<ScrollView
|
||||
contentContainerStyle={{ flexGrow: 1 }}
|
||||
<View style={{ flex: 0 }} collapsable={false}>
|
||||
<ScrollView
|
||||
contentContainerStyle={{ flexGrow: 1 }}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
refreshControl={refreshControl} // ✅ sekarang valid
|
||||
>
|
||||
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
||||
{renderContainer(staticProps.children)}
|
||||
</TouchableWithoutFeedback>
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
||||
{/* <ScrollView
|
||||
contentContainerStyle={{ flexGrow: 0 }}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
refreshControl={refreshControl} // ✅ sekarang valid
|
||||
>
|
||||
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
||||
{renderContainer(staticProps.children)}
|
||||
</TouchableWithoutFeedback>
|
||||
</ScrollView>
|
||||
</ScrollView> */}
|
||||
|
||||
{footerComponent ? (
|
||||
<SafeAreaView
|
||||
|
||||
@@ -24,7 +24,7 @@ export {
|
||||
|
||||
// OS Height
|
||||
const OS_ANDROID_HEIGHT = 115
|
||||
const OS_IOS_HEIGHT = 90
|
||||
const OS_IOS_HEIGHT = 80
|
||||
const OS_HEIGHT = Platform.OS === "ios" ? OS_IOS_HEIGHT : OS_ANDROID_HEIGHT
|
||||
|
||||
// Text Size
|
||||
|
||||
@@ -22,7 +22,7 @@ type AuthContextType = {
|
||||
isAdmin: boolean;
|
||||
isUserActive: boolean;
|
||||
loginWithNomor: (nomor: string) => Promise<boolean>;
|
||||
validateOtp: (nomor: string) => Promise<any>;
|
||||
validateOtp: (nomor: string, code: string) => Promise<any>;
|
||||
logout: () => Promise<void>;
|
||||
registerUser: (userData: {
|
||||
username: string;
|
||||
@@ -97,10 +97,10 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
};
|
||||
|
||||
// --- 2. Validasi OTP & cek user ---
|
||||
const validateOtp = async (nomor: string) => {
|
||||
const validateOtp = async (nomor: string, code: string) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await apiValidationCode({ nomor: nomor });
|
||||
const response = await apiValidationCode({ nomor: nomor, code: code });
|
||||
const { token } = response;
|
||||
console.log("[RESPONSE VALIDASI OTP]", JSON.stringify(response, null, 2));
|
||||
|
||||
|
||||
253
docs/QR_CODE_TESTING.md
Normal file
253
docs/QR_CODE_TESTING.md
Normal file
@@ -0,0 +1,253 @@
|
||||
# QR Code Testing Guide - HIPMI Mobile
|
||||
|
||||
## 📋 Overview
|
||||
|
||||
Dokumentasi ini menjelaskan cara testing QR Code untuk Universal Links (iOS) dan App Links (Android) pada fitur Event Confirmation.
|
||||
|
||||
## 🔧 Update Terbaru
|
||||
|
||||
File `screens/Admin/Event/EventDetailQRCode.tsx` telah diupdate dengan fitur:
|
||||
- **Toggle Button**: Switch antara HTTPS link dan Custom Scheme link
|
||||
- **HTTPS Link**: Untuk testing Universal Links/App Links dengan domain staging
|
||||
- **Custom Scheme**: Untuk testing langsung tanpa domain verification
|
||||
|
||||
## 🎯 Cara Testing QR Code
|
||||
|
||||
### Opsi 1: HTTPS Link (Recommended untuk Production)
|
||||
|
||||
**Gunakan tombol "HTTPS"** di component QR Code.
|
||||
|
||||
**Link yang di-generate:**
|
||||
```
|
||||
https://cld-dkr-staging-hipmi.wibudev.com/event/{id}/confirmation?userId={userId}
|
||||
```
|
||||
|
||||
**Cara kerja:**
|
||||
1. User scan QR code dengan kamera
|
||||
2. Safari/Chrome terbuka dengan URL HTTPS
|
||||
3. iOS/Android mendeteksi domain terverifikasi
|
||||
4. App terbuka otomatis dan menuju halaman confirmation
|
||||
|
||||
**Prerequisites:**
|
||||
- ✅ File `apple-app-site-association` harus accessible di Next.js server
|
||||
- ✅ File `assetlinks.json` harus accessible di Next.js server
|
||||
- ✅ Domain harus terverifikasi di app.config.js
|
||||
- ✅ App harus di-build ulang setelah perubahan domain
|
||||
|
||||
**Testing Steps:**
|
||||
```bash
|
||||
# 1. Pastikan .well-known files accessible
|
||||
curl https://cld-dkr-staging-hipmi.wibudev.com/.well-known/apple-app-site-association
|
||||
curl https://cld-dkr-staging-hipmi.wibudev.com/.well-known/assetlinks.json
|
||||
|
||||
# 2. Rebuild app
|
||||
bunx expo prebuild --clean
|
||||
|
||||
# 3. Run di physical device (bukan simulator)
|
||||
bun run android # untuk Android
|
||||
bun run ios # untuk iOS
|
||||
```
|
||||
|
||||
### Opsi 2: Custom Scheme Link (Untuk Development/Testing Cepat)
|
||||
|
||||
**Gunakan tombol "Custom Scheme"** di component QR Code.
|
||||
|
||||
**Link yang di-generate:**
|
||||
```
|
||||
hipmimobile://event/{id}/confirmation?userId={userId}
|
||||
```
|
||||
|
||||
**Cara kerja:**
|
||||
1. User scan QR code dengan kamera
|
||||
2. iOS: Pilih "Open in HIPMI Badung Connect"
|
||||
3. Android: Langsung buka app
|
||||
4. App terbuka dan menuju halaman confirmation
|
||||
|
||||
**Keuntungan:**
|
||||
- ✅ Tidak butuh domain verification
|
||||
- ✅ Bisa testing langsung tanpa rebuild
|
||||
- ✅ Cocok untuk development
|
||||
|
||||
**Kekurangan:**
|
||||
- ❌ Tidak bisa dibuka dari web browser
|
||||
- ❌ Tidak support universal linking dari website lain
|
||||
|
||||
## 📱 Testing Checklist
|
||||
|
||||
### iOS (Universal Links)
|
||||
|
||||
- [ ] File `apple-app-site-association` valid dan accessible
|
||||
- [ ] Domain terdaftar di `app.config.js` → `ios.associatedDomains`
|
||||
- [ ] Bundle ID match dengan konfigurasi
|
||||
- [ ] Team ID benar di apple-app-site-association
|
||||
- [ ] Test dengan **physical device** (simulator tidak support)
|
||||
- [ ] Test dengan **Safari** (bukan Chrome)
|
||||
- [ ] Long press link → ada opsi "Open"
|
||||
|
||||
**Debug iOS:**
|
||||
```bash
|
||||
# Cek apple-app-site-association
|
||||
curl -I https://cld-dkr-staging-hipmi.wibudev.com/.well-known/apple-app-site-association
|
||||
|
||||
# Harus return:
|
||||
# Content-Type: application/json
|
||||
# HTTP/2 200
|
||||
```
|
||||
|
||||
### Android (App Links)
|
||||
|
||||
- [ ] File `assetlinks.json` valid dan accessible
|
||||
- [ ] SHA256 fingerprint benar
|
||||
- [ ] Package name match
|
||||
- [ ] Intent filters terdaftar di app.config.js
|
||||
- [ ] Test dengan **physical device**
|
||||
- [ ] Test dengan **Chrome**
|
||||
|
||||
**Debug Android:**
|
||||
```bash
|
||||
# Dapatkan SHA256 fingerprint
|
||||
cd android
|
||||
./gradlew signingReport
|
||||
|
||||
# Cek assetlinks.json
|
||||
curl https://cld-dkr-staging-hipmi.wibudev.com/.well-known/assetlinks.json
|
||||
```
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Problem: QR Scan Terbuka di Safari, Tidak Balik ke App
|
||||
|
||||
**Penyebab:**
|
||||
- Domain belum terverifikasi untuk Universal Links/App Links
|
||||
- File `.well-known` tidak accessible atau invalid
|
||||
- App belum di-rebuild setelah perubahan domain
|
||||
|
||||
**Solusi:**
|
||||
1. Pastikan file `.well-known` accessible:
|
||||
```bash
|
||||
curl https://cld-dkr-staging-hipmi.wibudev.com/.well-known/apple-app-site-association
|
||||
```
|
||||
|
||||
2. Rebuild app:
|
||||
```bash
|
||||
bunx expo prebuild --clean
|
||||
bun run android # atau bun run ios
|
||||
```
|
||||
|
||||
3. Gunakan **Custom Scheme** untuk testing cepat
|
||||
|
||||
### Problem: Link Tidak Membuka App Sama Sekali
|
||||
|
||||
**Cek:**
|
||||
1. App sudah terinstall di device
|
||||
2. Link format benar (hipmimobile:// atau https://)
|
||||
3. Route handler sudah ada di app folder
|
||||
|
||||
**Test manual:**
|
||||
```bash
|
||||
# iOS Simulator
|
||||
xcrun simctl openurl booted "hipmimobile://event/123/confirmation?userId=456"
|
||||
|
||||
# Android Emulator
|
||||
adb shell am start -W -a android.intent.action.VIEW \
|
||||
-d "hipmimobile://event/123/confirmation?userId=456" \
|
||||
com.bip.hipmimobileapp
|
||||
```
|
||||
|
||||
### Problem: "Cannot GET /event/..." di Next.js
|
||||
|
||||
**Penyebab:**
|
||||
Route `/event/[id]/confirmation` tidak ada di Next.js server
|
||||
|
||||
**Solusi:**
|
||||
Pastikan Next.js project punya file:
|
||||
```
|
||||
public/.well-known/apple-app-site-association
|
||||
public/.well-known/assetlinks.json
|
||||
```
|
||||
|
||||
Dan API route untuk handle:
|
||||
```
|
||||
pages/api/event/[id]/confirmation.ts
|
||||
```
|
||||
|
||||
## 📄 File Configuration
|
||||
|
||||
### app.config.js - iOS
|
||||
```javascript
|
||||
ios: {
|
||||
associatedDomains: ["applinks:cld-dkr-staging-hipmi.wibudev.com"],
|
||||
}
|
||||
```
|
||||
|
||||
### app.config.js - Android
|
||||
```javascript
|
||||
android: {
|
||||
intentFilters: [
|
||||
{
|
||||
action: "VIEW",
|
||||
autoVerify: true,
|
||||
data: [
|
||||
{
|
||||
scheme: "https",
|
||||
host: "cld-dkr-staging-hipmi.wibudev.com",
|
||||
pathPrefix: "/",
|
||||
},
|
||||
],
|
||||
category: ["BROWSABLE", "DEFAULT"],
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
### apple-app-site-association (Next.js)
|
||||
```json
|
||||
{
|
||||
"applinks": {
|
||||
"apps": [],
|
||||
"details": [
|
||||
{
|
||||
"appID": "TEAM_ID.com.anonymous.hipmi-mobile",
|
||||
"paths": ["/event/*/confirmation"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### assetlinks.json (Next.js)
|
||||
```json
|
||||
[
|
||||
{
|
||||
"relation": ["delegate_permission/common.handle_all_urls"],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "com.bip.hipmimobileapp",
|
||||
"sha256_cert_fingerprints": ["YOUR_SHA256_FINGERPRINT"]
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## 🎓 Best Practices
|
||||
|
||||
1. **Development**: Gunakan Custom Scheme untuk testing cepat
|
||||
2. **Staging**: Gunakan HTTPS link dengan domain staging
|
||||
3. **Production**: Gunakan HTTPS link dengan domain production
|
||||
4. **Testing**: Selalu test di physical device, bukan simulator
|
||||
5. **Debugging**: Enable logging di confirmation page untuk track deep link
|
||||
|
||||
## 🔗 Related Files
|
||||
|
||||
- `screens/Admin/Event/EventDetailQRCode.tsx` - QR Code generator
|
||||
- `app/(application)/(user)/event/[id]/confirmation.tsx` - Confirmation page
|
||||
- `app.config.js` - App configuration
|
||||
- `service/api-config.ts` - API configuration (DEEP_LINK_URL)
|
||||
|
||||
## 📞 Support
|
||||
|
||||
Jika masih ada masalah:
|
||||
1. Cek logs di console
|
||||
2. Test manual dengan adb/xcrun
|
||||
3. Verify .well-known files dengan curl
|
||||
4. Pastikan app rebuild setelah perubahan config
|
||||
@@ -55,10 +55,10 @@ Component yang digunakan: components/_ShareComponent/NewWrapper.tsx
|
||||
|
||||
<!-- START Prompt Admin Refactoring -->
|
||||
<!-- Pindah kode ke Screen Component -->
|
||||
File source: app/(application)/admin/event/[id]/[status]/index.tsx
|
||||
Folder tujuan: screens/Admin/Event
|
||||
Nama file utama: ScreenEventDetail.tsx
|
||||
Nama function utama: Admin_ScreenEventDetail
|
||||
File source: app/(application)/(user)/portofolio/[id]/create.tsx
|
||||
Folder tujuan: screens/Portofolio
|
||||
Nama file utama: ScreenPortofolioCreate.tsx
|
||||
Nama function utama: Admin_ScreenPortofolioCreate
|
||||
File komponen wrapper: components/_ShareComponent/NewWrapper.tsx
|
||||
|
||||
Buat file baru pada "Folder tujuan" dengan nama "Nama file utama" dan ubah nama function menjadi "Nama function utama" kemudian clean code, import dan panggil function tersebut pada file "File source"
|
||||
@@ -120,4 +120,9 @@ Buatkan file baru pada "Folder tujuan" dengan nama "Nama file utama" dan ubah na
|
||||
|
||||
<!-- 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 -->
|
||||
|
||||
@@ -183,6 +183,21 @@
|
||||
FB0CB57BF4D74C1D87C2036C /* Remove signature files (Xcode workaround) */,
|
||||
14B3DE54EE4049AEB1EADA6B /* Remove signature files (Xcode workaround) */,
|
||||
B4CF5E09DBB44A4FB9CB91B9 /* Remove signature files (Xcode workaround) */,
|
||||
C894BD25C8224984AAD73398 /* Remove signature files (Xcode workaround) */,
|
||||
F0C608193824414E93E23BC7 /* Remove signature files (Xcode workaround) */,
|
||||
A3E2EDBCFB514A6487E28BEC /* Remove signature files (Xcode workaround) */,
|
||||
0D62979D96BF4B99AB9FBE7C /* Remove signature files (Xcode workaround) */,
|
||||
49B80EF12BE8476C86534CEA /* Remove signature files (Xcode workaround) */,
|
||||
6218417B3C954EFF9B5F4853 /* Remove signature files (Xcode workaround) */,
|
||||
7B5AE3770142492D84AEAAB3 /* Remove signature files (Xcode workaround) */,
|
||||
0F1E3753571D42AB932C4F72 /* Remove signature files (Xcode workaround) */,
|
||||
B020BDE42E304FBD99BD2279 /* Remove signature files (Xcode workaround) */,
|
||||
6ECA5F81B0BC4C70A91BE265 /* Remove signature files (Xcode workaround) */,
|
||||
44265583B67C48F2A24BA93E /* Remove signature files (Xcode workaround) */,
|
||||
D5CA1D54CFF74AB4B8B5B583 /* Remove signature files (Xcode workaround) */,
|
||||
97C01196E2194AF5A13C7773 /* Remove signature files (Xcode workaround) */,
|
||||
EB19F4C53C8B434CBAD50897 /* Remove signature files (Xcode workaround) */,
|
||||
95ABFC1FE48F4F2ABAF407D8 /* Remove signature files (Xcode workaround) */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -995,6 +1010,261 @@
|
||||
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
|
||||
";
|
||||
};
|
||||
C894BD25C8224984AAD73398 /* Remove signature files (Xcode workaround) */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
name = "Remove signature files (Xcode workaround)";
|
||||
inputPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "
|
||||
echo \"Remove signature files (Xcode workaround)\";
|
||||
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
|
||||
";
|
||||
};
|
||||
F0C608193824414E93E23BC7 /* Remove signature files (Xcode workaround) */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
name = "Remove signature files (Xcode workaround)";
|
||||
inputPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "
|
||||
echo \"Remove signature files (Xcode workaround)\";
|
||||
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
|
||||
";
|
||||
};
|
||||
A3E2EDBCFB514A6487E28BEC /* Remove signature files (Xcode workaround) */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
name = "Remove signature files (Xcode workaround)";
|
||||
inputPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "
|
||||
echo \"Remove signature files (Xcode workaround)\";
|
||||
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
|
||||
";
|
||||
};
|
||||
0D62979D96BF4B99AB9FBE7C /* Remove signature files (Xcode workaround) */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
name = "Remove signature files (Xcode workaround)";
|
||||
inputPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "
|
||||
echo \"Remove signature files (Xcode workaround)\";
|
||||
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
|
||||
";
|
||||
};
|
||||
49B80EF12BE8476C86534CEA /* Remove signature files (Xcode workaround) */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
name = "Remove signature files (Xcode workaround)";
|
||||
inputPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "
|
||||
echo \"Remove signature files (Xcode workaround)\";
|
||||
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
|
||||
";
|
||||
};
|
||||
6218417B3C954EFF9B5F4853 /* Remove signature files (Xcode workaround) */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
name = "Remove signature files (Xcode workaround)";
|
||||
inputPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "
|
||||
echo \"Remove signature files (Xcode workaround)\";
|
||||
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
|
||||
";
|
||||
};
|
||||
7B5AE3770142492D84AEAAB3 /* Remove signature files (Xcode workaround) */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
name = "Remove signature files (Xcode workaround)";
|
||||
inputPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "
|
||||
echo \"Remove signature files (Xcode workaround)\";
|
||||
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
|
||||
";
|
||||
};
|
||||
0F1E3753571D42AB932C4F72 /* Remove signature files (Xcode workaround) */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
name = "Remove signature files (Xcode workaround)";
|
||||
inputPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "
|
||||
echo \"Remove signature files (Xcode workaround)\";
|
||||
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
|
||||
";
|
||||
};
|
||||
B020BDE42E304FBD99BD2279 /* Remove signature files (Xcode workaround) */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
name = "Remove signature files (Xcode workaround)";
|
||||
inputPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "
|
||||
echo \"Remove signature files (Xcode workaround)\";
|
||||
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
|
||||
";
|
||||
};
|
||||
6ECA5F81B0BC4C70A91BE265 /* Remove signature files (Xcode workaround) */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
name = "Remove signature files (Xcode workaround)";
|
||||
inputPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "
|
||||
echo \"Remove signature files (Xcode workaround)\";
|
||||
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
|
||||
";
|
||||
};
|
||||
44265583B67C48F2A24BA93E /* Remove signature files (Xcode workaround) */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
name = "Remove signature files (Xcode workaround)";
|
||||
inputPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "
|
||||
echo \"Remove signature files (Xcode workaround)\";
|
||||
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
|
||||
";
|
||||
};
|
||||
D5CA1D54CFF74AB4B8B5B583 /* Remove signature files (Xcode workaround) */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
name = "Remove signature files (Xcode workaround)";
|
||||
inputPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "
|
||||
echo \"Remove signature files (Xcode workaround)\";
|
||||
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
|
||||
";
|
||||
};
|
||||
97C01196E2194AF5A13C7773 /* Remove signature files (Xcode workaround) */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
name = "Remove signature files (Xcode workaround)";
|
||||
inputPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "
|
||||
echo \"Remove signature files (Xcode workaround)\";
|
||||
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
|
||||
";
|
||||
};
|
||||
EB19F4C53C8B434CBAD50897 /* Remove signature files (Xcode workaround) */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
name = "Remove signature files (Xcode workaround)";
|
||||
inputPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "
|
||||
echo \"Remove signature files (Xcode workaround)\";
|
||||
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
|
||||
";
|
||||
};
|
||||
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\";
|
||||
";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<string>development</string>
|
||||
<key>com.apple.developer.associated-domains</key>
|
||||
<array>
|
||||
<string>applinks:cld-dkr-staging-hipmi.wibudev.com</string>
|
||||
<string>applinks:cld-dkr-hipmi-stg.wibudev.com</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -39,7 +39,7 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>3</string>
|
||||
<string>5</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
|
||||
@@ -4,10 +4,16 @@ import { ICON_SIZE_SMALL } from "@/constants/constans-value";
|
||||
import { useNotificationStore } from "@/hooks/use-notification-store";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { router } from "expo-router";
|
||||
import { useEffect } from "react";
|
||||
import { Text, View } from "react-native";
|
||||
|
||||
export default function AdminNotificationBell() {
|
||||
const { unreadCount } = useNotificationStore();
|
||||
const { unreadCount, syncUnreadCount } = useNotificationStore();
|
||||
|
||||
useEffect(() => {
|
||||
console.log("Syncing unread count");
|
||||
syncUnreadCount();
|
||||
}, [syncUnreadCount]);
|
||||
|
||||
return (
|
||||
<View style={{ position: "relative" }}>
|
||||
|
||||
@@ -1,26 +1,93 @@
|
||||
import { BaseBox, LoaderCustom, Spacing, StackCustom, TextCustom } from "@/components";
|
||||
import {
|
||||
BaseBox,
|
||||
ButtonCustom,
|
||||
LoaderCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { BASE_URL } from "@/service/api-config";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { StyleSheet } from "react-native";
|
||||
import QRCode from "react-native-qrcode-svg";
|
||||
|
||||
interface EventDetailQRCodeProps {
|
||||
qrValue: string;
|
||||
userId: string;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export function EventDetailQRCode({ qrValue, isLoading }: EventDetailQRCodeProps) {
|
||||
export function EventDetailQRCode({
|
||||
userId,
|
||||
isLoading,
|
||||
}: EventDetailQRCodeProps) {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [useHttpsLink, setUseHttpsLink] = useState(true);
|
||||
|
||||
// HTTPS link untuk Universal Links (iOS) dan App Links (Android)
|
||||
// Ini akan membuka file .well-known di Next.js server Anda
|
||||
const httpsLink = `https://cld-dkr-hipmi-stg.wibudev.com/event/${id}/confirmation?userId=${userId}`;
|
||||
|
||||
// Custom scheme link untuk fallback atau testing tanpa universal links
|
||||
const deepLinkURL = `${BASE_URL}/event/${id}/confirmation?userId=${userId}`;
|
||||
|
||||
// Toggle antara HTTPS link dan custom scheme
|
||||
const qrValue = useHttpsLink ? httpsLink : deepLinkURL;
|
||||
|
||||
return (
|
||||
<BaseBox>
|
||||
<StackCustom style={{ alignItems: "center" }}>
|
||||
<TextCustom bold>QR Code Event</TextCustom>
|
||||
{isLoading ? (
|
||||
<LoaderCustom />
|
||||
) : (
|
||||
<QRCode
|
||||
value={qrValue}
|
||||
size={200}
|
||||
/>
|
||||
)}
|
||||
{isLoading ? <LoaderCustom /> : <QRCode value={qrValue} size={200} />}
|
||||
</StackCustom>
|
||||
<Spacing />
|
||||
<TextCustom color="gray" align="center" size="small">
|
||||
{qrValue}
|
||||
</TextCustom>
|
||||
<Spacing />
|
||||
<StackCustom direction="row" gap="sm">
|
||||
<ButtonCustom
|
||||
onPress={() => setUseHttpsLink(true)}
|
||||
backgroundColor={useHttpsLink ? MainColor.yellow : "transparent"}
|
||||
textColor={useHttpsLink ? MainColor.black : MainColor.yellow}
|
||||
style={[
|
||||
stylesButton.smallButton,
|
||||
useHttpsLink && stylesButton.border,
|
||||
]}
|
||||
>
|
||||
HTTPS
|
||||
</ButtonCustom>
|
||||
<ButtonCustom
|
||||
onPress={() => setUseHttpsLink(false)}
|
||||
backgroundColor={!useHttpsLink ? MainColor.yellow : "transparent"}
|
||||
textColor={!useHttpsLink ? MainColor.black : MainColor.yellow}
|
||||
style={[
|
||||
stylesButton.smallButton,
|
||||
!useHttpsLink && stylesButton.border,
|
||||
]}
|
||||
>
|
||||
Custom Scheme
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
<Spacing />
|
||||
<TextCustom color="gray" align="center" size={"small"}>
|
||||
{useHttpsLink
|
||||
? "✅ Testing Universal Links/App Links (butuh .well-known config)"
|
||||
: "🔧 Testing langsung (tanpa domain verification)"}
|
||||
</TextCustom>
|
||||
</BaseBox>
|
||||
);
|
||||
}
|
||||
|
||||
const stylesButton = StyleSheet.create({
|
||||
smallButton: {
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 6,
|
||||
minWidth: 140,
|
||||
},
|
||||
border: {
|
||||
borderWidth: 1,
|
||||
borderColor: MainColor.yellow,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { ActionIcon, AlertDefaultSystem } from "@/components";
|
||||
import { ActionIcon, AlertDefaultSystem, Spacing } from "@/components";
|
||||
import { IconDot } from "@/components/_Icon/IconComponent";
|
||||
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
|
||||
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
|
||||
import AdminButtonReview from "@/components/_ShareComponent/Admin/ButtonReview";
|
||||
import ReportBox from "@/components/Box/ReportBox";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import ReportBox from "@/components/Box/ReportBox";
|
||||
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { funUpdateStatusEvent } from "@/screens/Admin/Event/funUpdateStatus";
|
||||
import { apiAdminEventById } from "@/service/api-admin/api-admin-event";
|
||||
import { DEEP_LINK_URL } from "@/service/api-config";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
import { BoxEventDetail } from "./BoxEventDetail";
|
||||
import { EventDetailDrawer } from "./EventDetailDrawer";
|
||||
import { EventDetailQRCode } from "./EventDetailQRCode";
|
||||
import { View } from "react-native";
|
||||
|
||||
export function Admin_ScreenEventDetail() {
|
||||
const { user } = useAuth();
|
||||
@@ -25,11 +25,6 @@ export function Admin_ScreenEventDetail() {
|
||||
const [data, setData] = useState<any | null>(null);
|
||||
const [loadData, setLoadData] = useState(false);
|
||||
|
||||
const deepLinkURL = `${DEEP_LINK_URL}/event/${id}/confirmation?userId=${user?.id}`;
|
||||
const deepLinkURLDEV = `${DEEP_LINK_URL}/--/event/${id}/confirmation?userId=${user?.id}`;
|
||||
const isDevLink =
|
||||
process.env.NODE_ENV === "development" ? deepLinkURLDEV : deepLinkURL;
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
@@ -140,7 +135,11 @@ export function Admin_ScreenEventDetail() {
|
||||
<>
|
||||
<NewWrapper
|
||||
headerComponent={headerComponent}
|
||||
footerComponent={footerComponent}
|
||||
// footerComponent={
|
||||
// <View style={{ paddingInline: 8 }}>
|
||||
// {footerComponent}
|
||||
// </View>
|
||||
// }
|
||||
>
|
||||
<BoxEventDetail data={data} status={status as string} />
|
||||
|
||||
@@ -149,8 +148,11 @@ export function Admin_ScreenEventDetail() {
|
||||
)}
|
||||
|
||||
{(status === "publish" || status === "history") && (
|
||||
<EventDetailQRCode qrValue={isDevLink} isLoading={loadData} />
|
||||
<EventDetailQRCode userId={user?.id || ""} isLoading={loadData} />
|
||||
)}
|
||||
|
||||
{footerComponent}
|
||||
<Spacing />
|
||||
</NewWrapper>
|
||||
|
||||
<EventDetailDrawer
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IconPlus } from "@/components/_Icon";
|
||||
import { IconDot } from "@/components/_Icon/IconComponent";
|
||||
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
|
||||
@@ -121,12 +122,16 @@ export default function Admin_ScreenNotification() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Admin Notifikasi",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => (
|
||||
<IconDot
|
||||
color={MainColor.yellow}
|
||||
onPress={() => setOpenDrawer(true)}
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Admin Notifikasi"
|
||||
left={<BackButton />}
|
||||
right={
|
||||
<IconDot
|
||||
color={MainColor.yellow}
|
||||
onPress={() => setOpenDrawer(true)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from "@/components";
|
||||
import { IconPlus } from "@/components/_Icon";
|
||||
import { IconDot } from "@/components/_Icon/IconComponent";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { AccentColor, MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_SMALL, PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
|
||||
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||
@@ -169,12 +170,16 @@ export default function Admin_ScreenNotification2() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Admin Notifikasi",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => (
|
||||
<IconDot
|
||||
color={MainColor.yellow}
|
||||
onPress={() => setOpenDrawer(true)}
|
||||
// title: "Admin Notifikasi",
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Admin Notifikasi"
|
||||
right={
|
||||
<IconDot
|
||||
color={MainColor.yellow}
|
||||
onPress={() => setOpenDrawer(true)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { NewWrapper } from "@/components";
|
||||
import { NewWrapper, ViewWrapper } from "@/components";
|
||||
import ButtonCustom from "@/components/Button/ButtonCustom";
|
||||
import ModalReactNative from "@/components/Modal/ModalReactNative";
|
||||
import Spacing from "@/components/_ShareComponent/Spacing";
|
||||
@@ -128,7 +128,7 @@ export default function LoginView() {
|
||||
}
|
||||
|
||||
return (
|
||||
<NewWrapper
|
||||
<ViewWrapper
|
||||
withBackground
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />
|
||||
@@ -205,6 +205,6 @@ export default function LoginView() {
|
||||
setLoadingTerm={setLoadingTerm}
|
||||
/>
|
||||
</ModalReactNative>
|
||||
</NewWrapper>
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,10 +21,10 @@ export default function VerificationView() {
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [recodeOtp, setRecodeOtp] = useState<boolean>(false);
|
||||
|
||||
// 🔑 DETEKSI MODE REVIEW (HANYA UNTUK NOMOR DEMO & PRODUCTION)
|
||||
// 🔑 DETEKSI MODE REVIEW (HANYA UNTUK NOMOR DEMO & DEVELOPMENT BUILD)
|
||||
// Menggunakan Constants.expoConfig untuk mendeteksi development build
|
||||
const isReviewMode =
|
||||
typeof window !== "undefined" && // pastikan di browser/production
|
||||
process.env.NODE_ENV === "production" &&
|
||||
process.env.NODE_ENV === "development" &&
|
||||
nomor === "6282340374412";
|
||||
|
||||
// --- Context ---
|
||||
@@ -37,10 +37,6 @@ export default function VerificationView() {
|
||||
// Hanya jalankan logika OTP normal jika BUKAN review mode
|
||||
onLoadCheckCodeOtp();
|
||||
}
|
||||
|
||||
console.log("[NODE_ENV]:", process.env.NODE_ENV);
|
||||
console.log("[isReviewMode]:", isReviewMode);
|
||||
console.log("[nomor]:", nomor);
|
||||
}, [recodeOtp, isReviewMode]);
|
||||
|
||||
async function onLoadCheckCodeOtp() {
|
||||
@@ -85,29 +81,30 @@ export default function VerificationView() {
|
||||
|
||||
const handleVerification = async () => {
|
||||
if (isReviewMode) {
|
||||
// ✅ VERIFIKASI OTOMATIS UNTUK APPLE REVIEW
|
||||
if (inputOtp === "1234") {
|
||||
try {
|
||||
await validateOtp(nomor as string);
|
||||
|
||||
return;
|
||||
} catch (error) {
|
||||
console.log("Error verification", error);
|
||||
Toast.show({ type: "error", text1: "Gagal verifikasi" });
|
||||
}
|
||||
} else {
|
||||
// ✅ VERIFIKASI OTOMATIS UNTUK APPLE REVIEW (Development Only)
|
||||
if (inputOtp !== "1234") {
|
||||
Toast.show({ type: "error", text1: "Kode OTP tidak sesuai" });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await validateOtp(nomor as string, inputOtp);
|
||||
return;
|
||||
} catch (error) {
|
||||
console.log("Error verification", error);
|
||||
Toast.show({ type: "error", text1: "Gagal verifikasi" });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 🔁 VERIFIKASI NORMAL (untuk pengguna sungguhan)
|
||||
try {
|
||||
await validateOtp(nomor as string);
|
||||
return
|
||||
} catch (error) {
|
||||
await validateOtp(nomor as string, inputOtp);
|
||||
return;
|
||||
} catch (error: any) {
|
||||
console.log("Error verification", error);
|
||||
Toast.show({ type: "error", text1: "Gagal verifikasi" });
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: error.response?.data?.message || "Gagal verifikasi",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { BackButton, DrawerCustom, MenuDrawerDynamicGrid } from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IconPlus } from "@/components/_Icon";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
@@ -52,8 +53,12 @@ export default function Donation_ScreenListOfNews({
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Daftar Kabar",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Daftar Kabar"
|
||||
left={<BackButton />}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<NewWrapper
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
DrawerCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IconPlus } from "@/components/_Icon";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
|
||||
@@ -61,9 +62,13 @@ export default function Donation_ScreenRecapOfNews({
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Rekap Kabar",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Rekap Kabar"
|
||||
left={<BackButton />}
|
||||
right={<DotButton onPress={() => setOpenDrawer(true)} />}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<NewWrapper
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
LoaderCustom,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiForumGetAll } from "@/service/api-client/api-forum";
|
||||
import { apiUser } from "@/service/api-client/api-user";
|
||||
@@ -54,13 +55,17 @@ export default function Forum_ViewBeranda() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Forum",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => (
|
||||
<AvatarComp
|
||||
fileId={dataUser?.Profile?.imageId}
|
||||
size="base"
|
||||
href={`/forum/${user?.id}/forumku`}
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Forum"
|
||||
left={<BackButton />}
|
||||
right={
|
||||
<AvatarComp
|
||||
fileId={dataUser?.Profile?.imageId}
|
||||
size="base"
|
||||
href={`/forum/${user?.id}/forumku`}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
StackCustom,
|
||||
TextCustom, // ← gunakan NewWrapper yang sudah diperbaiki
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import SkeletonCustom from "@/components/_ShareComponent/SkeletonCustom";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
@@ -155,13 +156,17 @@ export default function Forum_ViewBeranda2() {
|
||||
{/* 🔹 Header Navigation */}
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Forum",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => (
|
||||
<AvatarComp
|
||||
fileId={dataUser?.Profile?.imageId}
|
||||
size="base"
|
||||
href={`/forum/${user?.id}/forumku`}
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Forum"
|
||||
left={<BackButton />}
|
||||
right={
|
||||
<AvatarComp
|
||||
fileId={dataUser?.Profile?.imageId}
|
||||
size="base"
|
||||
href={`/forum/${user?.id}/forumku`}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
FloatingButton,
|
||||
SearchInput,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||
@@ -17,7 +18,7 @@ import _ from "lodash";
|
||||
import { useEffect, useState } from "react";
|
||||
import { RefreshControl, TouchableOpacity, View } from "react-native";
|
||||
|
||||
const PAGE_SIZE = 5;
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
export default function Forum_ViewBeranda3() {
|
||||
const { user } = useAuth();
|
||||
@@ -84,18 +85,22 @@ export default function Forum_ViewBeranda3() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Forum",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => (
|
||||
<TouchableOpacity
|
||||
onPress={() => router.navigate(`/forum/${user?.id}/forumku`)}
|
||||
>
|
||||
<AvatarComp
|
||||
fileId={dataUser?.Profile?.imageId}
|
||||
size="base"
|
||||
href={`/forum/${user?.id}/forumku`}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Forum"
|
||||
left={<BackButton />}
|
||||
right={
|
||||
<TouchableOpacity
|
||||
onPress={() => router.navigate(`/forum/${user?.id}/forumku`)}
|
||||
>
|
||||
<AvatarComp
|
||||
fileId={dataUser?.Profile?.imageId}
|
||||
size="base"
|
||||
href={`/forum/${user?.id}/forumku`}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -94,7 +94,7 @@ export const stylesHome = StyleSheet.create({
|
||||
jobVacancyHeader: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginBottom: 16,
|
||||
marginBottom: 20,
|
||||
},
|
||||
jobVacancyTitle: {
|
||||
fontSize: 18,
|
||||
|
||||
@@ -5,7 +5,6 @@ import { router } from "expo-router";
|
||||
import React from "react";
|
||||
import { Text, TouchableOpacity, View } from "react-native";
|
||||
|
||||
|
||||
const CustomTab = ({ icon, label, isActive, onPress }: ICustomTab) => (
|
||||
<TouchableOpacity
|
||||
style={[GStyles.tabItem, isActive && GStyles.activeTab]}
|
||||
@@ -17,7 +16,7 @@ const CustomTab = ({ icon, label, isActive, onPress }: ICustomTab) => (
|
||||
>
|
||||
<Ionicons
|
||||
name={icon as any}
|
||||
size={20}
|
||||
size={18}
|
||||
color={isActive ? "#fff" : "#666"}
|
||||
/>
|
||||
</View>
|
||||
@@ -30,8 +29,8 @@ const CustomTab = ({ icon, label, isActive, onPress }: ICustomTab) => (
|
||||
export default function TabSection({ tabs }: { tabs: ITabs[] }) {
|
||||
return (
|
||||
<>
|
||||
<View style={GStyles.tabBar}>
|
||||
<View style={GStyles.tabContainer}>
|
||||
<View style={GStyles.tabBar} pointerEvents="box-none">
|
||||
<View style={GStyles.tabContainer} pointerEvents="box-none">
|
||||
{tabs.map((e) => (
|
||||
<CustomTab
|
||||
key={e.id}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
DrawerCustom,
|
||||
MenuDrawerDynamicGrid
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IconEdit } from "@/components/_Icon";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
@@ -125,14 +126,18 @@ export default function Investment_ScreenRecapOfDocument() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Rekap Dokumen",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => (
|
||||
<DotButton
|
||||
onPress={() => {
|
||||
setOpenDrawer(true);
|
||||
setOpenDrawerBox(false);
|
||||
}}
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Rekap Dokumen"
|
||||
left={<BackButton />}
|
||||
right={
|
||||
<DotButton
|
||||
onPress={() => {
|
||||
setOpenDrawer(true);
|
||||
setOpenDrawerBox(false);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
MenuDrawerDynamicGrid,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IconPlus } from "@/components/_Icon";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import { usePagination } from "@/hooks/use-pagination";
|
||||
@@ -64,9 +65,13 @@ export default function Investment_ScreenListOfNews({
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Daftar Berita",
|
||||
headerLeft: () => <BackButton />,
|
||||
// headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Daftar Berita"
|
||||
left={<BackButton />}
|
||||
// headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
Spacing,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IconPlus } from "@/components/_Icon";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import { usePagination } from "@/hooks/use-pagination";
|
||||
@@ -66,9 +67,13 @@ export default function Investment_ScreenRecapOfNews({
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Rekap Berita",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Rekap Berita"
|
||||
left={<BackButton />}
|
||||
right={<DotButton onPress={() => setOpenDrawer(true)} />}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<NewWrapper
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IconDot } from "@/components/_Icon/IconComponent";
|
||||
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
|
||||
import NoDataText from "@/components/_ShareComponent/NoDataText";
|
||||
@@ -217,12 +218,16 @@ export default function ScreenNotification_V1() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Notifikasi",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => (
|
||||
<IconDot
|
||||
color={MainColor.yellow}
|
||||
onPress={() => setOpenDrawer(true)}
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Notifikasi"
|
||||
left={<BackButton />}
|
||||
right={
|
||||
<IconDot
|
||||
color={MainColor.yellow}
|
||||
onPress={() => setOpenDrawer(true)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IconDot } from "@/components/_Icon/IconComponent";
|
||||
import { AccentColor, MainColor } from "@/constants/color-palet";
|
||||
import {
|
||||
@@ -182,12 +183,16 @@ export default function ScreenNotification_V2() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Notifikasi",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => (
|
||||
<IconDot
|
||||
color={MainColor.yellow}
|
||||
onPress={() => setOpenDrawer(true)}
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Notifikasi"
|
||||
left={<BackButton />}
|
||||
right={
|
||||
<IconDot
|
||||
color={MainColor.yellow}
|
||||
onPress={() => setOpenDrawer(true)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
|
||||
341
screens/Portofolio/ScreenPortofolioCreate.tsx
Normal file
341
screens/Portofolio/ScreenPortofolioCreate.tsx
Normal file
@@ -0,0 +1,341 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import {
|
||||
ActionIcon,
|
||||
ButtonCenteredOnly,
|
||||
CenterCustom,
|
||||
InformationBox,
|
||||
NewWrapper,
|
||||
SelectCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextAreaCustom,
|
||||
TextCustom,
|
||||
TextInputCustom,
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_XLARGE } from "@/constants/constans-value";
|
||||
import DUMMY_IMAGE from "@/constants/dummy-image-value";
|
||||
import Portofolio_ButtonCreate from "@/screens/Portofolio/ButtonCreatePortofolio";
|
||||
import {
|
||||
apiMasterBidangBisnis,
|
||||
apiMasterSubBidangBisnis,
|
||||
} from "@/service/api-client/api-master";
|
||||
import {
|
||||
IMasterBidangBisnis,
|
||||
IMasterSubBidangBisnis,
|
||||
} from "@/types/Type-Master";
|
||||
import pickImage from "@/utils/pickImage";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import { Text, View } from "react-native";
|
||||
import PhoneInput, { ICountry } from "react-native-international-phone-number";
|
||||
import { Avatar } from "react-native-paper";
|
||||
|
||||
export function ScreenPortofolioCreate() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [selectedCountry, setSelectedCountry] = useState<null | ICountry>(null);
|
||||
const [inputValue, setInputValue] = useState<string>("");
|
||||
const [data, setData] = useState({
|
||||
namaBisnis: "",
|
||||
masterBidangBisnisId: "",
|
||||
alamatKantor: "",
|
||||
tlpn: "",
|
||||
deskripsi: "",
|
||||
});
|
||||
const [imageUri, setImageUri] = useState<string | null>(null);
|
||||
|
||||
const [bidangBisnis, setBidangBisnis] = useState<IMasterBidangBisnis[]>([]);
|
||||
const [subBidangBisnis, setSubBidangBisnis] = useState<
|
||||
IMasterSubBidangBisnis[]
|
||||
>([]);
|
||||
|
||||
const [selectedSubBidang, setSelectedSubBidang] = useState<string[]>([]);
|
||||
const [listSubBidangSelected, setListSubBidangSelected] = useState([
|
||||
{
|
||||
id: "",
|
||||
},
|
||||
]);
|
||||
|
||||
const [dataMedsos, setDataMedsos] = useState({
|
||||
facebook: "",
|
||||
twitter: "",
|
||||
instagram: "",
|
||||
youtube: "",
|
||||
tiktok: "",
|
||||
});
|
||||
|
||||
const [isLoadingCreate, setIsLoadingCreate] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadMaster();
|
||||
onLoadMasterSubBidangBisnis();
|
||||
}, []),
|
||||
);
|
||||
|
||||
const onLoadMaster = async () => {
|
||||
try {
|
||||
const response = await apiMasterBidangBisnis();
|
||||
setBidangBisnis(response.data);
|
||||
} catch (error) {
|
||||
setBidangBisnis([]);
|
||||
console.log("Error onLoadMasterBidangBisnis", error);
|
||||
}
|
||||
};
|
||||
|
||||
const onLoadMasterSubBidangBisnis = async () => {
|
||||
try {
|
||||
const response = await apiMasterSubBidangBisnis({});
|
||||
setSubBidangBisnis(response.data);
|
||||
} catch (error) {
|
||||
setSubBidangBisnis([]);
|
||||
console.log("Error onLoadMasterSubBidangBisnis", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerSelectedSubBidang = ({ id }: { id: string }) => {
|
||||
const selectedList = subBidangBisnis?.filter(
|
||||
(item) => (item?.masterBidangBisnisId as any) === id,
|
||||
);
|
||||
setSelectedSubBidang(selectedList as any[]);
|
||||
};
|
||||
|
||||
const handleInputValue = (phoneNumber: string) => {
|
||||
setInputValue(phoneNumber);
|
||||
const callingCode = selectedCountry?.callingCode.replace(/^\+/, "") || "";
|
||||
let fixNumber = inputValue.replace(/\s+/g, "").replace(/^0+/, "");
|
||||
const realNumber = callingCode + fixNumber;
|
||||
setData({ ...data, tlpn: realNumber });
|
||||
};
|
||||
|
||||
const handleSelectedCountry = (country: ICountry) => {
|
||||
setSelectedCountry(country);
|
||||
};
|
||||
|
||||
return (
|
||||
<NewWrapper
|
||||
footerComponent={
|
||||
<Portofolio_ButtonCreate
|
||||
id={id as string}
|
||||
data={data}
|
||||
dataMedsos={dataMedsos}
|
||||
imageUri={imageUri}
|
||||
subBidangSelected={listSubBidangSelected}
|
||||
isLoadingCreate={isLoadingCreate}
|
||||
setIsLoadingCreate={setIsLoadingCreate}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<StackCustom gap="xs">
|
||||
<InformationBox text="Lengkapi data bisnis anda." />
|
||||
|
||||
<TextInputCustom
|
||||
required
|
||||
label="Nama Bisnis"
|
||||
placeholder="Masukkan nama bisnis"
|
||||
onChangeText={(value: any) => setData({ ...data, namaBisnis: value })}
|
||||
/>
|
||||
|
||||
<SelectCustom
|
||||
label="Bidang Usaha"
|
||||
required
|
||||
data={bidangBisnis.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))}
|
||||
value={data.masterBidangBisnisId}
|
||||
onChange={(value) => {
|
||||
const isSameBidang = data.masterBidangBisnisId === value;
|
||||
|
||||
if (!isSameBidang) {
|
||||
setListSubBidangSelected([{ id: "" }]);
|
||||
}
|
||||
|
||||
setData({ ...(data as any), masterBidangBisnisId: value });
|
||||
handlerSelectedSubBidang({ id: value as string });
|
||||
}}
|
||||
/>
|
||||
|
||||
{listSubBidangSelected.map((item, index) => (
|
||||
<SelectCustom
|
||||
key={index}
|
||||
disabled={data.masterBidangBisnisId === ""}
|
||||
label="Sub Bidang Usaha"
|
||||
required
|
||||
data={_.map(selectedSubBidang as any)
|
||||
.filter((option: any) => {
|
||||
const selectedValues = listSubBidangSelected.map((s) => s.id);
|
||||
return (
|
||||
option.id === item.id || !selectedValues.includes(option.id)
|
||||
);
|
||||
})
|
||||
.map((e: any) => ({
|
||||
value: e.id,
|
||||
label: e.name,
|
||||
}))}
|
||||
value={item.id || null}
|
||||
onChange={(value) => {
|
||||
const list = _.clone(listSubBidangSelected);
|
||||
list[index].id = value as any;
|
||||
setListSubBidangSelected(list);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
<CenterCustom>
|
||||
<View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
|
||||
<ActionIcon
|
||||
disabled={
|
||||
selectedSubBidang.length === listSubBidangSelected.length
|
||||
}
|
||||
onPress={() => {
|
||||
setListSubBidangSelected([
|
||||
...listSubBidangSelected,
|
||||
{ id: "" },
|
||||
]);
|
||||
}}
|
||||
icon={
|
||||
<Ionicons
|
||||
name="add-circle-outline"
|
||||
size={ICON_SIZE_XLARGE}
|
||||
color={MainColor.black}
|
||||
/>
|
||||
}
|
||||
size="xl"
|
||||
/>
|
||||
<ActionIcon
|
||||
disabled={listSubBidangSelected.length <= 1}
|
||||
onPress={() => {
|
||||
const list = _.clone(listSubBidangSelected);
|
||||
list.pop();
|
||||
setListSubBidangSelected(list);
|
||||
}}
|
||||
icon={
|
||||
<Ionicons
|
||||
name="remove-circle-outline"
|
||||
size={ICON_SIZE_XLARGE}
|
||||
color={MainColor.black}
|
||||
/>
|
||||
}
|
||||
size="xl"
|
||||
/>
|
||||
</View>
|
||||
</CenterCustom>
|
||||
|
||||
<Spacing />
|
||||
|
||||
<View>
|
||||
<View style={{ flexDirection: "row", alignItems: "center" }}>
|
||||
<TextCustom semiBold style={{ color: MainColor.white_gray }}>
|
||||
Nomor Telepon
|
||||
</TextCustom>
|
||||
<Text style={{ color: "red" }}> *</Text>
|
||||
</View>
|
||||
<Spacing height={5} />
|
||||
<PhoneInput
|
||||
value={inputValue}
|
||||
onChangePhoneNumber={handleInputValue}
|
||||
selectedCountry={selectedCountry}
|
||||
onChangeSelectedCountry={handleSelectedCountry}
|
||||
defaultCountry="ID"
|
||||
placeholder="xxx-xxx-xxx"
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Spacing />
|
||||
|
||||
<TextInputCustom
|
||||
required
|
||||
label="Alamat Bisnis"
|
||||
placeholder="Masukkan alamat bisnis"
|
||||
onChangeText={(value: any) =>
|
||||
setData({ ...data, alamatKantor: value })
|
||||
}
|
||||
/>
|
||||
|
||||
<TextAreaCustom
|
||||
label="Deskripsi Bisnis"
|
||||
placeholder="Masukkan deskripsi bisnis"
|
||||
value={data.deskripsi}
|
||||
onChangeText={(value: any) => setData({ ...data, deskripsi: value })}
|
||||
autosize
|
||||
minRows={2}
|
||||
maxRows={5}
|
||||
required
|
||||
showCount
|
||||
maxLength={1000}
|
||||
/>
|
||||
|
||||
<Spacing />
|
||||
|
||||
<InformationBox text="Upload logo bisnis anda untuk di tampilaka pada portofolio." />
|
||||
|
||||
<CenterCustom>
|
||||
<Avatar.Image
|
||||
source={imageUri ? { uri: imageUri } : DUMMY_IMAGE.dummy_image}
|
||||
size={200}
|
||||
/>
|
||||
</CenterCustom>
|
||||
|
||||
<Spacing />
|
||||
|
||||
<ButtonCenteredOnly
|
||||
icon="upload"
|
||||
onPress={() => {
|
||||
pickImage({
|
||||
setImageUri,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Upload
|
||||
</ButtonCenteredOnly>
|
||||
|
||||
<Spacing height={40} />
|
||||
|
||||
<InformationBox text="Isi hanya pada sosial media yang anda miliki." />
|
||||
|
||||
<TextInputCustom
|
||||
label="Tiktok"
|
||||
placeholder="Masukkan username tiktok"
|
||||
onChangeText={(value: any) =>
|
||||
setDataMedsos({ ...dataMedsos, tiktok: value })
|
||||
}
|
||||
/>
|
||||
|
||||
<TextInputCustom
|
||||
label="Facebook"
|
||||
placeholder="Masukkan username facebook"
|
||||
onChangeText={(value: any) =>
|
||||
setDataMedsos({ ...dataMedsos, facebook: value })
|
||||
}
|
||||
/>
|
||||
|
||||
<TextInputCustom
|
||||
label="Instagram"
|
||||
placeholder="Masukkan username instagram"
|
||||
onChangeText={(value: any) =>
|
||||
setDataMedsos({ ...dataMedsos, instagram: value })
|
||||
}
|
||||
/>
|
||||
|
||||
<TextInputCustom
|
||||
label="Twitter"
|
||||
placeholder="Masukkan username twitter"
|
||||
onChangeText={(value: any) =>
|
||||
setDataMedsos({ ...dataMedsos, twitter: value })
|
||||
}
|
||||
/>
|
||||
|
||||
<TextInputCustom
|
||||
label="Youtube"
|
||||
placeholder="Masukkan username youtube"
|
||||
onChangeText={(value: any) =>
|
||||
setDataMedsos({ ...dataMedsos, youtube: value })
|
||||
}
|
||||
/>
|
||||
</StackCustom>
|
||||
</NewWrapper>
|
||||
);
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
ICON_SIZE_SMALL,
|
||||
PAGINATION_DEFAULT_TAKE,
|
||||
} from "@/constants/constans-value";
|
||||
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||
import { usePagination } from "@/hooks/use-pagination";
|
||||
import { apiAllUser } from "@/service/api-client/api-user";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
@@ -19,21 +20,89 @@ import { router, useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
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() {
|
||||
const isInitialMount = useRef(true);
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const {
|
||||
listData,
|
||||
loading,
|
||||
refreshing,
|
||||
hasMore,
|
||||
onRefresh,
|
||||
loadMore,
|
||||
isInitialLoad,
|
||||
} = usePagination({
|
||||
const pagination = usePagination({
|
||||
fetchFunction: async (page, searchQuery) => {
|
||||
const response = await apiAllUser({
|
||||
page: String(page),
|
||||
@@ -41,7 +110,7 @@ export default function UserSearchMainView_V2() {
|
||||
});
|
||||
return response;
|
||||
},
|
||||
pageSize: PAGINATION_DEFAULT_TAKE,
|
||||
pageSize: PAGE_SIZE,
|
||||
searchQuery: search,
|
||||
});
|
||||
|
||||
@@ -49,119 +118,42 @@ export default function UserSearchMainView_V2() {
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
if (isInitialMount.current) {
|
||||
// Skip saat pertama kali mount
|
||||
isInitialMount.current = false;
|
||||
return;
|
||||
}
|
||||
// Hanya refresh saat kembali dari screen lain
|
||||
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>
|
||||
pagination.onRefresh();
|
||||
}, [pagination.onRefresh]),
|
||||
);
|
||||
|
||||
const { ListEmptyComponent, ListFooterComponent } =
|
||||
createPaginationComponents({
|
||||
loading,
|
||||
refreshing,
|
||||
listData,
|
||||
loading: pagination.loading,
|
||||
refreshing: pagination.refreshing,
|
||||
listData: pagination.listData,
|
||||
searchQuery: search,
|
||||
emptyMessage: "Tidak ada pengguna ditemukan",
|
||||
emptySearchMessage: "Tidak ada hasil pencarian",
|
||||
skeletonCount: PAGINATION_DEFAULT_TAKE,
|
||||
skeletonHeight: 100,
|
||||
loadingFooterText: "Memuat lebih banyak pengguna...",
|
||||
isInitialLoad,
|
||||
isInitialLoad: pagination.isInitialLoad,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<NewWrapper
|
||||
headerComponent={renderHeader()}
|
||||
listData={listData}
|
||||
renderItem={renderItem}
|
||||
onEndReached={loadMore}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
progressBackgroundColor={MainColor.yellow}
|
||||
refreshing={refreshing}
|
||||
onRefresh={onRefresh}
|
||||
/>
|
||||
}
|
||||
ListFooterComponent={ListFooterComponent}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
/>
|
||||
</>
|
||||
<NewWrapper
|
||||
headerComponent={renderHeader(search, setSearch)}
|
||||
listData={pagination.listData}
|
||||
renderItem={renderItem}
|
||||
onEndReached={pagination.loadMore}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
progressBackgroundColor={MainColor.yellow}
|
||||
refreshing={pagination.refreshing}
|
||||
onRefresh={pagination.onRefresh}
|
||||
/>
|
||||
}
|
||||
ListFooterComponent={ListFooterComponent}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,13 +6,13 @@ export async function apiUser(id: string) {
|
||||
}
|
||||
|
||||
export async function apiAllUser({
|
||||
page,
|
||||
page = "1",
|
||||
search,
|
||||
}: {
|
||||
page?: string;
|
||||
search?: string;
|
||||
}) {
|
||||
const pageQuery = page ? `?page=${page}` : "";
|
||||
const pageQuery = `?page=${page}`;
|
||||
const searchQuery = search ? `&search=${search}` : "";
|
||||
|
||||
try {
|
||||
|
||||
@@ -45,9 +45,16 @@ export async function apiCheckCodeOtp({ kodeId }: { kodeId: string }) {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function apiValidationCode({ nomor }: { nomor: string }) {
|
||||
export async function apiValidationCode({
|
||||
nomor,
|
||||
code,
|
||||
}: {
|
||||
nomor: string;
|
||||
code: string;
|
||||
}) {
|
||||
const response = await apiConfig.post(`/auth/mobile-validasi`, {
|
||||
nomor: nomor,
|
||||
code: code,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ export const GStyles = StyleSheet.create({
|
||||
transform: [{ scale: 1.05 }],
|
||||
},
|
||||
iconContainer: {
|
||||
padding: 8,
|
||||
padding: 5,
|
||||
borderRadius: 20,
|
||||
// marginBottom: 4,
|
||||
},
|
||||
|
||||
@@ -11,7 +11,7 @@ export const TabsStyles: BottomTabNavigationOptions = {
|
||||
tabBarStyle: Platform.select({
|
||||
ios: {
|
||||
borderTopWidth: 0,
|
||||
paddingTop: 5,
|
||||
paddingTop: 12,
|
||||
height: OS_IOS_HEIGHT,
|
||||
},
|
||||
android: {
|
||||
|
||||
Reference in New Issue
Block a user