Compare commits

...

38 Commits

Author SHA1 Message Date
eb64c30d49 feat: Complete User Phase 9 Investment screens migration + note known upload issue
- Migrate 24 investment screens to OS_Wrapper (tabs, list, detail, forms, transaction flow)
- Add contentPadding={PADDING_INLINE} to 9 list/tabs screens for consistent spacing
- Add enableKeyboardHandling to 6 form screens
- Fix investment tabs layout height to use OS_IOS_HEIGHT/OS_ANDROID_HEIGHT constants
- Update TASK-005 with known issue: upload image returns 500 error in dev env
- Note: Investment feature not fully complete - upload issue needs investigation tomorrow

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-13 17:56:49 +08:00
2f87776d8b docs: Add User Phase 9 Investment screens details to TASK-005
- Document 24 investment screens to migrate
- Include tabs, list/document, detail, form, and transaction flow screens
- Note tabs layout height fix needed for investment

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-13 16:45:15 +08:00
243313b8eb feat: Complete User Phase 8 Donation screens + add PADDING_INLINE to all list/recap screens
- Migrate 25 donation screens to OS_Wrapper (tabs, list, detail, forms, transaction flow)
- Add contentPadding={PADDING_INLINE} to 7 list/recap screens for consistent spacing
- Migrate crowdfunding screen to OS_Wrapper
- Fix voting tabs layout height to use OS_IOS_HEIGHT/OS_ANDROID_HEIGHT constants
- Migrate news detail screen to OS_Wrapper
- Update TASK-005 progress to 73% complete (104 files migrated)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-13 16:24:14 +08:00
8e61c980af fix: Remove PADDING_INLINE from non-tabs ScreenListOfContributor + update task
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-13 12:25:12 +08:00
c709dffd20 feat: Complete User Phase 7 Voting screens migration to OS_Wrapper
- Migrate 5 list screens (Beranda, Contribution, History, Status, ListOfContributor)
- Migrate 2 form screens (create, edit) with enableKeyboardHandling
- Migrate 4 detail screens (index, contribution, history, detail)
- Add PADDING_INLINE to tabs screens for consistent spacing
- Update TASK-005 progress to 80% complete (71/89 files)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-13 12:24:23 +08:00
54537d2449 feat: Complete Phase 6 Event screens migration + add PADDING_INLINE to tabs
User Phase 6 - Event Screens (16 files):
- Beranda, Status, History, Contribution (tabs screens)
  → NewWrapper → OS_Wrapper + contentPadding={PADDING_INLINE}
- create.tsx & edit.tsx → Forms with enableKeyboardHandling + contentPaddingBottom={250}
- [id]/publish.tsx, history.tsx, contribution.tsx, confirmation.tsx → Static detail screens
- [id]/[status]/detail-event.tsx → Status detail screen
- detail/[id].tsx → Detail route screen
- ScreenListOfParticipants.tsx → Participants list screen

Key Improvements:
- Added contentPadding={PADDING_INLINE} to all Event (tabs) screens to prevent tight edge margins.
- Form screens (create, edit) use enableKeyboardHandling + contentPaddingBottom={250}.
- Removed all ViewWrapper and NewWrapper instances from Event feature.

Documentation:
- Update TASK-005 with Phase 6 completion details and new progress totals.
- Added note about PADDING_INLINE usage for tab screens.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-10 13:50:59 +08:00
66792186ca feat: Complete Forum & Admin User Access migration + fix scroll issues
Forum Screens (User Phase 5 - 17 files):
- Migrate all forum list, detail, create, and report screens to OS_Wrapper.
- ViewBeranda, ViewBeranda2, ViewBeranda3: List screens with pull-to-refresh.
- DetailForum, DetailForum2: Comment sections with headers (apply disableFlexGrow fix).
- create, edit, report-*, other-report-*, preview-report-*: Forms with keyboard handling.

Admin Phase 9 (User Access - 2 files):
- index.tsx: List with search and pagination.
- [id]/index.tsx: Detail with status toggle footer.

Scroll Fixes (Critical Bugs):
- Fix "Ghost Scroll" in Android FlatList: Removed TouchableWithoutFeedback and KeyboardAvoidingView wrappers in List Mode.
- Fix Large Header Cut-off: Added optional disableFlexGrow={true} to OS_Wrapper for screens with complex ListHeaderComponents (e.g., Forum Detail).
- Fix Keyboard Dismiss: Changed keyboardShouldPersistTaps to "handled" so taps on empty areas dismiss the keyboard while allowing scroll.

Documentation:
- Update TASK-005 with complete Phase 5 details and new progress totals.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-09 17:48:51 +08:00
c3cf354c28 feat: Migrate Portfolio & Maps screens + perbaiki bug auto-scroll keyboard
Phase 3 - Portfolio Screens (6 files):
- [id]/index.tsx: ViewWrapper → OS_Wrapper (detail dengan pull-to-refresh)
- [id]/edit.tsx: NewWrapper → OS_Wrapper (form + keyboard handling)
- [id]/edit-logo.tsx: ViewWrapper → OS_Wrapper (upload logo)
- [id]/edit-social-media.tsx: ViewWrapper → OS_Wrapper (form + keyboard handling)
- ViewListPortofolio.tsx: NewWrapper → OS_Wrapper (pagination list)
- ScreenPortofolioCreate.tsx: NewWrapper → OS_Wrapper (form + keyboard handling)

Phase 4 - Maps Screens (2 files):
- ScreenMapsCreate.tsx: NewWrapper → OS_Wrapper (form + keyboard handling)
- ScreenMapsEdit.tsx: ViewWrapper → OS_Wrapper (form + keyboard handling)

Bug Fixes:
- Perbaiki auto-scroll keyboard yang membuat input paling atas 'terlempar' keluar layar
- Gunakan UIManager.measure untuk mendapatkan posisi absolut input (pageY) secara akurat
- Logika conditional scroll:
  * Jika input terlihat (di atas keyboard) → TIDAK SCROLL
  * Jika input tertutup keyboard → Scroll secukupnya
- Helper cloneChildrenWithFocusHandler sekarang aktif menyuntikan onFocus handler ke semua TextInput/TextArea/PhoneInput/Select
- Hapus KeyboardAvoidingView dari AndroidWrapper static mode (tidak diperlukan lagi)

Pattern yang diterapkan:
- List screens: contentPaddingBottom=100 (default)
- Form screens: contentPaddingBottom={250} + enableKeyboardHandling
- NO PADDING_INLINE (sesuai preferensi user - mencegah box menyempit)

Dokumentasi:
- Update TASK-005 dengan status lengkap Phase 1-4 (27 files migrated)
- Tambahkan urutan phase baru: Event (Phase 5), Voting (Phase 6), Forum (Phase 7), Donation (Phase 8)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-08 17:27:06 +08:00
6ec839fd67 feat: Migrate Profile, Waiting Room, and Delete Account to OS_Wrapper
Profile Screens (8 files):
- [id]/index.tsx: NewWrapper → OS_Wrapper (list with refresh)
- [id]/edit.tsx: ViewWrapper → OS_Wrapper (form + keyboard handling)
- create.tsx: ViewWrapper → OS_Wrapper (form + keyboard handling)
- [id]/blocked-list.tsx: NewWrapper → OS_Wrapper (pagination list)
- [id]/detail-blocked.tsx: NewWrapper → OS_Wrapper (static with footer)
- [id]/update-background.tsx: ViewWrapper → OS_Wrapper (static with footer)
- [id]/update-photo.tsx: ViewWrapper → OS_Wrapper (static with footer)
- All Profile forms use enableKeyboardHandling + contentPaddingBottom={250}

Other Screens (2 files):
- waiting-room.tsx: NewWrapper → OS_Wrapper (static with refresh + footer)
- delete-account.tsx: ViewWrapper → OS_Wrapper (form + keyboard handling)

Bug Fixes:
- AndroidWrapper: Add refreshControl to ScrollView (fix pull-to-refresh on static mode)

Pattern Applied:
- List screens: contentPaddingBottom=100 (default)
- Form screens: contentPaddingBottom=250 (with TextInput)
- No PADDING_INLINE (user preference - prevents box narrowing)

Documentation:
- Update TASK-005 with Phase 1 completion details

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-08 14:32:11 +08:00
b8f8a361d6 refactor: Adjust OS_Wrapper defaults for better spacing
- Reduce default contentPaddingBottom from 250 to 100
  - Better for list screens (less empty space)
  - Only form screens with TextInput need 250px

- Set OS_ANDROID_PADDING_TOP to 6px
  - More compact tabs on Android

- Update form screens (Create/Edit):
  - Explicit contentPaddingBottom={250}
  - Only screens with TextInput use larger spacing

- Remove unnecessary PADDING_INLINE from detail screens
  - Detail screen doesn't need inline padding

Pattern:
- Default: contentPaddingBottom=100 (list screens)
- Forms: contentPaddingBottom=250 (screens with TextInput)
- contentPadding=0 (per-screen control)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-08 10:31:36 +08:00
1a5ca78041 feat: Complete OS_Wrapper implementation with keyboard handling and PADDING_INLINE
OS_Wrapper System:
- Simplify API: Remove PageWrapper, merge keyboard props into OS_Wrapper
- Add auto-scroll when keyboard appears (Android only)
- Add tap-to-dismiss keyboard for both Static and List modes
- Fix contentPaddingBottom default to 250px (prevent keyboard overlap)
- Change default contentPadding to 0 (per-screen control)
- Remove Platform.OS checks from IOSWrapper and AndroidWrapper

Constants:
- Add PADDING_INLINE constant (16px) for consistent inline padding
- Add OS_PADDING_TOP constants for tab layouts

Job Screens Migration (9 files):
- Apply PADDING_INLINE to all Job screens:
  - ScreenBeranda, ScreenBeranda2
  - ScreenArchive, ScreenArchive2
  - MainViewStatus, MainViewStatus2
  - ScreenJobCreate, ScreenJobEdit
  - Job detail screen

Keyboard Handling:
- Simplified useKeyboardForm hook
- Auto-scroll by keyboard height when keyboard appears
- Track scroll position for accurate scroll targets
- TouchableWithoutFeedback wraps all content for tap-to-dismiss

Documentation:
- Update TASK-005 with Phase 1 completion status
- Update Quick Reference with unified API examples

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-07 17:50:15 +08:00
502cd7bc65 feat: Implement OS_Wrapper system and migrate all Job screens
Create OS-specific wrapper system:
- Add IOSWrapper (based on NewWrapper for iOS)
- Add AndroidWrapper (based on NewWrapper_V2 with keyboard handling)
- Add OS_Wrapper (auto platform detection)
- Add PageWrapper (with keyboard handling for Android forms)

Migrate all Job screens (8 files):
- ScreenJobCreate: NewWrapper_V2 → PageWrapper
- ScreenJobEdit: NewWrapper_V2 → PageWrapper
- ScreenBeranda2: NewWrapper_V2 → OS_Wrapper
- ScreenArchive2: NewWrapper_V2 → OS_Wrapper
- MainViewStatus2: NewWrapper_V2 → OS_Wrapper
- ScreenBeranda: ViewWrapper → OS_Wrapper
- ScreenArchive: ViewWrapper → OS_Wrapper
- MainViewStatus: ViewWrapper → OS_Wrapper

Benefits:
- Automatic platform detection (no manual Platform.OS checks)
- Consistent tabs behavior across iOS and Android
- Keyboard handling for forms (Android only)
- Cleaner code with unified API

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-07 14:14:00 +08:00
44d9025afe refactor: Update header components and improve job edit footer layout
- Replace Waiting Room header with AppHeader component
- Replace Profile Create header with AppHeader with showBack=false
- Wrap Job Edit submit button with BoxButtonOnFooter for consistent footer layout

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-06 17:49:28 +08:00
b34bc3799e feat: Add default contentPadding to NewWrapper_V2
- Set default contentPadding to 16px for consistent spacing
- Added contentPadding prop to BaseProps interface
- Updated documentation in TASK-004

Benefits:
- All screens automatically have 16px padding
- Cleaner code (no need to specify padding everywhere)
- Still customizable (set to 0 or custom value if needed)
- Box content not too tight to screen edges

Phase 1 completed with consistent padding!

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-02 17:16:19 +08:00
7cb4f30ae9 refactor: Replace NewWrapper with NewWrapper_V2 for all Job screens
- ScreenBeranda2.tsx: NewWrapper → NewWrapper_V2
- ScreenArchive2.tsx: NewWrapper → NewWrapper_V2
- MainViewStatus2.tsx: NewWrapper → NewWrapper_V2

All Job screens now using NewWrapper_V2 with:
- Better keyboard handling
- Footer width 100%
- Content padding bottom 80px
- Auto-scroll to focused input (for forms)
- No white area when keyboard close

Phase 1 completed: All Job screens migrated!

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-02 16:13:23 +08:00
0f552443c4 refactor: Cleanup test files and migrate Job Detail screen
- Delete ScreenJobCreate2.tsx and ScreenJobEdit2.tsx (test files)
- Delete TestWrapper.tsx and TestKeyboardInput.tsx (test components)
- Delete test pages (test-keyboard.tsx, test-keyboard-bug.tsx)
- Update create.tsx to use ScreenJobCreate (not test version)
- Update edit.tsx to use ScreenJobEdit (not test version)
- Migrate Job Detail screen to NewWrapper_V2
- Remove TestWrapper from exports
- Clean up imports

Phase 1 cleanup completed!

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-02 15:31:26 +08:00
90bc8ae343 feat: Migrate Job screens to NewWrapper_V2 with keyboard handling
- Migrate ScreenJobCreate.tsx to NewWrapper_V2
    - Migrate ScreenJobEdit.tsx to NewWrapper_V2
    - Add NewWrapper_V2 component with auto-scroll keyboard handling
    - Add useKeyboardForm hook for keyboard management
    - Add FormWrapper component for forms
    - Create ScreenJobEdit.tsx from edit route (separation of concerns)
    - Add documentation for keyboard implementation
    - Add TASK-004 migration plan
    - Fix: Footer width 100% with safe positioning
    - Fix: Content padding bottom 80px for navigation bar
    - Fix: Auto-scroll to focused input
    - Fix: No white area when keyboard close
    - Fix: Footer not raised after keyboard close

    Phase 1 completed: Job screens migrated

### No Issue
2026-04-02 15:07:10 +08:00
98f8c7e2bf Fix layout tabs pada komponen
Fix home tabs

### No Issue
2026-04-01 17:17:12 +08:00
81bbd8e6b0 Clean project and release with new domain: hipmi.muku.id
### No issue
2026-03-31 17:07:27 +08:00
57159d2c45 update app config domain
### No Issue
2026-03-31 14:29:19 +08:00
66373fa65b Clean code
Update android domain

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-03-25 16:22:54 +08:00
f68deab8c0 Fix App Header
Layouts & Navigation
- app/(application)/_layout.tsx
- app/(application)/(user)/_layout.tsx
- app/(application)/(user)/event/(tabs)/_layout.tsx
- app/(application)/(user)/job/(tabs)/_layout.tsx
- app/(application)/(user)/voting/(tabs)/_layout.tsx
- app/(application)/(user)/portofolio/_layout.tsx
- app/(application)/(user)/profile/_layout.tsx
- app/(application)/admin/_layout.tsx
- app/+not-found.tsx

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

iOS
- ios/HIPMIBadungConnect.xcodeproj/project.pbxproj

Docs
- QWEN.md

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

Components
- components/_ShareComponent/AppHeader.tsx

Screens
- screens/Portofolio/ScreenPortofolioCreate.tsx

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

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

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

### No issue
2026-03-12 16:50:02 +08:00
57c9215771 Feat validasi mobile otp
modified:   context/AuthContext.tsx
        modified:   screens/Authentication/VerificationView.tsx
        modified:   service/api-config.ts

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

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

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

### No Issue
2026-03-09 16:39:01 +08:00
a5026cc285 add: Admin Event detail screen dan komponen pendukung
Deskripsi:

Menambahkan halaman detail event pada admin panel dengan status parameter

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

Update konfigurasi aplikasi dan iOS project

Perbaikan pada halaman verifikasi authentication

Update dokumentasi prompt untuk Qwen

File yang diubah:

Modified

app.config.js

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

docs/prompt-for-qwen-code.md

ios/HIPMIBadungConnect.xcodeproj/project.pbxproj

ios/HIPMIBadungConnect/Info.plist

screens/Authentication/VerificationView.tsx

New Admin Event Components

screens/Admin/Event/BoxEventDetail.tsx

screens/Admin/Event/EventDetailDrawer.tsx

screens/Admin/Event/EventDetailQRCode.tsx

screens/Admin/Event/ScreenEventDetail.tsx

### No Issue
2026-03-06 16:39:55 +08:00
205 changed files with 7725 additions and 3306 deletions

3
.gitignore vendored
View File

@@ -81,4 +81,7 @@ yarn-error.*
# typescript
*.tsbuildinfo
# secrets
secrets/
# @end expo-cli

8
.qwen/settings.json Normal file
View File

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

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

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

30
QWEN.md
View File

@@ -387,7 +387,7 @@ apiConfig.interceptors.request.use(async (config) => {
### Deep Linking
- Scheme: `hipmimobile://`
- HTTPS: `cld-dkr-staging-hipmi.wibudev.com`
- HTTPS: `cld-dkr-hipmi-stg.wibudev.com`
- Configured for both platforms
### Camera
@@ -513,3 +513,31 @@ When using Maplibre MapView on iOS, prevent "Attempt to recycle a mounted view"
- [Expo Router Documentation](https://docs.expo.dev/router/introduction/)
- [TypeScript Documentation](https://www.typescriptlang.org/docs/)
- [Maplibre React Native](https://github.com/maplibre/maplibre-react-native)
## Qwen Added Memories
- OS_Wrapper contentPaddingBottom pattern:
- Default: contentPaddingBottom=100 (untuk list screens)
- Forms: contentPaddingBottom=250 (HANYA untuk screens yang punya TextInput/TextArea)
- contentPadding=0 (default, per-screen control)
- OS_ANDROID_PADDING_TOP=6 (compact tabs)
- OS_IOS_PADDING_TOP=12
- PADDING_INLINE=16 (constant)
Contoh:
```tsx
// List screen (default 100px)
<OS_Wrapper listData={data} renderItem={renderItem} />
// Form screen (explicit 250px)
<OS_Wrapper enableKeyboardHandling contentPaddingBottom={250}>
<FormWithTextInput />
</OS_Wrapper>
```
- PADDING_INLINE usage pattern - User preference:
- PADDING_INLINE (16px) TIDAK selalu diperlukan
- User remove PADDING_INLINE dari Profile screens karena mempersempit box tampilan
- Decision: Tambahkan PADDING_INLINE HANYA jika diperlukan per-screen, jangan default
- User akan review dan tambahkan sendiri jika perlu
Profile screens: PADDING_INLINE dihapus dari edit.tsx dan create.tsx
- User ingin mengecek semua user layout tabs setelah perubahan height layout tabs donation di constants. Pattern yang perlu dicek: semua tabs screens harus pakai contentPadding={PADDING_INLINE} untuk konsistensi layout.

View File

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

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application android:usesCleartextTraffic="true" tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning" tools:replace="android:usesCleartextTraffic" />
</manifest>

View File

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

View File

@@ -6,32 +6,17 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.google.gms:google-services:4.4.1'
classpath 'com.google.gms:google-services:4.4.1'
classpath('com.android.tools.build:gradle')
classpath('com.facebook.react:react-native-gradle-plugin')
classpath('org.jetbrains.kotlin:kotlin-gradle-plugin')
}
}
allprojects {
repositories {
google()
mavenCentral()
maven { url 'https://www.jitpack.io' }
}
}
apply plugin: "expo-root-project"
apply plugin: "com.facebook.react.rootproject"
// @generated begin @rnmapbox/maps-v2-maven - expo prebuild (DO NOT MODIFY) sync-d4ccbfdff48fdba3138b02a8ba41b9722af001d8
allprojects {
repositories {
maven {
url 'https://api.mapbox.com/downloads/v2/releases/maven'
// Authentication is no longer required as per Mapbox's removal of download token requirement
// See: https://github.com/mapbox/mapbox-maps-flutter/issues/775
// Keeping this as optional for backward compatibility
def token = project.properties['MAPBOX_DOWNLOADS_TOKEN'] ?: System.getenv('RNMAPBOX_MAPS_DOWNLOAD_TOKEN')
if (token) {
authentication { basic(BasicAuthentication) }
@@ -41,7 +26,11 @@ allprojects {
}
}
}
google()
mavenCentral()
maven { url 'https://www.jitpack.io' }
}
}
// @generated end @rnmapbox/maps-v2-maven
apply plugin: "expo-root-project"
apply plugin: "com.facebook.react.rootproject"

View File

@@ -1,6 +1,17 @@
// app.config.js
require("dotenv").config();
// const isDev = process.env.NODE_ENV === "development";
// const isStaging = process.env.NEXT_PUBLIC_ENV === "staging";
// const isProd = process.env.NEXT_PUBLIC_ENV === "production";
// Domain berdasarkan environment
// const domain = isDev
// ? "localhost:3000"
// : isStaging
// ? "cld-dkr-hipmi-stg.wibudev.com"
// : "hipmi.muku.id"; // Production domain
export default {
name: "HIPMI Badung Connect",
slug: "hipmi-mobile",
@@ -14,25 +25,27 @@ export default {
ios: {
supportsTablet: true,
bundleIdentifier: "com.anonymous.hipmi-mobile",
googleServicesFile: "./ios/HIPMIBadungConnect/GoogleService-Info.plist",
googleServicesFile: "./secrets/GoogleService-Info.plist",
infoPlist: {
ITSAppUsesNonExemptEncryption: false,
NSLocationWhenInUseUsageDescription:
"Aplikasi membutuhkan akses lokasi untuk menampilkan peta.",
},
associatedDomains: ["applinks:cld-dkr-staging-hipmi.wibudev.com"],
buildNumber: "2",
associatedDomains: [
"applinks:hipmi.muku.id",
],
buildNumber: "7",
},
android: {
googleServicesFile: "./google-services.json",
googleServicesFile: "./secrets/google-services.json",
adaptiveIcon: {
foregroundImage: "./assets/images/splash-icon.png",
backgroundColor: "#ffffff",
},
edgeToEdgeEnabled: true,
package: "com.bip.hipmimobileapp",
versionCode: 1,
versionCode: 5,
// softwareKeyboardLayoutMode: 'resize', // option: untuk mengatur keyboard pada room chst collaboration
intentFilters: [
{
@@ -41,7 +54,7 @@ export default {
data: [
{
scheme: "https",
host: "cld-dkr-staging-hipmi.wibudev.com",
host: "hipmi.muku.id",
pathPrefix: "/",
},
],
@@ -57,6 +70,7 @@ export default {
},
plugins: [
"./plugins/withCustomConfig",
"expo-router",
"expo-web-browser",
[

View File

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

View File

@@ -1,29 +1,27 @@
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
name="waiting-room"
options={{
title: "Waiting Room",
headerBackVisible: false,
header: () => <AppHeader title="Waiting Room" />,
}}
/>
@@ -47,8 +45,7 @@ export default function UserLayout() {
<Stack.Screen
name="user-search/index"
options={{
title: "Pencarian Pengguna",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Pencarian Pengguna" />,
}}
/>
@@ -71,10 +68,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 +90,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 +120,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 +145,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 +167,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 +195,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 +205,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 +302,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 +322,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 +344,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 +456,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 +474,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 +528,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 +596,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 +622,7 @@ export default function UserLayout() {
<Stack.Screen
name="marketplace/index"
options={{
title: "Market Place",
headerLeft: () => <BackButton />,
header: () => <AppHeader title="Market Place" />,
}}
/>
</Stack>

View File

@@ -2,35 +2,64 @@ import { IconHome } from "@/components/_Icon";
import { TabsStyles } from "@/styles/tabs-styles";
import { Ionicons } from "@expo/vector-icons";
import { Tabs } from "expo-router";
import { View } from "react-native";
import { MainColor } from "@/constants/color-palet";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { Platform } from "react-native";
function CollaborationTabsWrapper() {
const insets = useSafeAreaInsets();
const paddingBottom = Platform.OS === "android" ? insets.bottom : 0;
export default function CollaborationTabsLayout() {
return (
<Tabs screenOptions={TabsStyles}>
<Tabs.Screen
name="index"
options={{
title: "Beranda",
tabBarIcon: ({ color }) => <IconHome color={color} />,
<View style={{ flex: 1, backgroundColor: MainColor.darkblue }}>
<Tabs
screenOptions={{
...TabsStyles,
tabBarStyle: Platform.select({
ios: {
borderTopWidth: 0,
paddingTop: 12,
height: 80,
},
android: {
borderTopWidth: 0,
paddingTop: 5,
height: 70 + paddingBottom,
},
}),
}}
/>
<Tabs.Screen
name="participant"
options={{
title: "Partisipan",
tabBarIcon: ({ color }) => (
<Ionicons size={20} name="people" color={color} />
),
}}
/>
<Tabs.Screen
name="group"
options={{
title: "Grup",
tabBarIcon: ({ color }) => (
<Ionicons size={20} name="chatbox-ellipses" color={color} />
),
}}
/>
</Tabs>
>
<Tabs.Screen
name="index"
options={{
title: "Beranda",
tabBarIcon: ({ color }) => <IconHome color={color} />,
}}
/>
<Tabs.Screen
name="participant"
options={{
title: "Partisipan",
tabBarIcon: ({ color }) => (
<Ionicons size={20} name="people" color={color} />
),
}}
/>
<Tabs.Screen
name="group"
options={{
title: "Grup",
tabBarIcon: ({ color }) => (
<Ionicons size={20} name="chatbox-ellipses" color={color} />
),
}}
/>
</Tabs>
</View>
);
}
export default function CollaborationTabsLayout() {
return <CollaborationTabsWrapper />;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,9 +1,9 @@
import {
BaseBox,
Grid,
OS_Wrapper,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
@@ -25,7 +25,7 @@ export default function Crowdfunding() {
];
return (
<ViewWrapper>
<OS_Wrapper>
<StackCustom>
<Image
source={require("@/assets/images/constants/crowd-hipmi.png")}
@@ -63,6 +63,6 @@ export default function Crowdfunding() {
</BaseBox>
))}
</StackCustom>
</ViewWrapper>
</OS_Wrapper>
);
}

View File

@@ -3,10 +3,10 @@ import {
BaseBox,
ButtonCustom,
CenterCustom,
OS_Wrapper,
StackCustom,
TextCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import { useAuth } from "@/hooks/use-auth";
import { apiDeleteUser } from "@/service/api-client/api-user";
@@ -68,7 +68,10 @@ export default function DeleteAccount() {
return (
<>
<ViewWrapper>
<OS_Wrapper
enableKeyboardHandling
contentPaddingBottom={250}
>
<StackCustom>
<BaseBox>
<StackCustom>
@@ -105,7 +108,7 @@ export default function DeleteAccount() {
</StackCustom>
</BaseBox>
</StackCustom>
</ViewWrapper>
</OS_Wrapper>
</>
);
}

View File

@@ -1,37 +1,66 @@
import { IconHome, IconStatus } from "@/components/_Icon";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { ICON_SIZE_SMALL, OS_ANDROID_HEIGHT, OS_IOS_HEIGHT } from "@/constants/constans-value";
import { TabsStyles } from "@/styles/tabs-styles";
import {
FontAwesome5
} from "@expo/vector-icons";
import { Tabs } from "expo-router";
import { View } from "react-native";
import { MainColor } from "@/constants/color-palet";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { Platform } from "react-native";
function DonationTabsWrapper() {
const insets = useSafeAreaInsets();
const paddingBottom = Platform.OS === "android" ? insets.bottom : 0;
export default function InvestmentTabsLayout() {
return (
<Tabs screenOptions={TabsStyles}>
<Tabs.Screen
name="index"
options={{
title: "Beranda",
tabBarIcon: ({ color }) => <IconHome color={color} />,
<View style={{ flex: 1, backgroundColor: MainColor.darkblue }}>
<Tabs
screenOptions={{
...TabsStyles,
tabBarStyle: Platform.select({
ios: {
borderTopWidth: 0,
paddingTop: 12,
height: OS_IOS_HEIGHT,
},
android: {
borderTopWidth: 0,
paddingTop: 5,
height: OS_ANDROID_HEIGHT + paddingBottom,
},
}),
}}
/>
<Tabs.Screen
name="status"
options={{
title: "Galang Dana",
tabBarIcon: ({ color }) => <IconStatus color={color} />,
}}
/>
<Tabs.Screen
name="my-donation"
options={{
title: "Donasi Saya",
tabBarIcon: ({ color }) => (
<FontAwesome5 name="donate" color={color} size={ICON_SIZE_SMALL} />
),
}}
/>
</Tabs>
>
<Tabs.Screen
name="index"
options={{
title: "Beranda",
tabBarIcon: ({ color }) => <IconHome color={color} />,
}}
/>
<Tabs.Screen
name="status"
options={{
title: "Galang Dana",
tabBarIcon: ({ color }) => <IconStatus color={color} />,
}}
/>
<Tabs.Screen
name="my-donation"
options={{
title: "Donasi Saya",
tabBarIcon: ({ color }) => (
<FontAwesome5 name="donate" color={color} size={ICON_SIZE_SMALL} />
),
}}
/>
</Tabs>
</View>
);
}
export default function DonationTabsLayout() {
return <DonationTabsWrapper />;
}

View File

@@ -5,11 +5,11 @@ import {
ButtonCustom,
InformationBox,
LandscapeFrameUploaded,
OS_Wrapper,
Spacing,
StackCustom,
TextAreaCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import API_STRORAGE from "@/constants/base-url-api-strorage";
import DIRECTORY_ID from "@/constants/directory-id";
@@ -105,7 +105,9 @@ export default function DonationEditNews() {
};
return (
<ViewWrapper
<OS_Wrapper
enableKeyboardHandling
contentPaddingBottom={250}
footerComponent={
<BoxButtonOnFooter>
<ButtonCustom
@@ -165,6 +167,6 @@ export default function DonationEditNews() {
<Spacing />
</StackCustom>
<Spacing />
</ViewWrapper>
</OS_Wrapper>
);
}

View File

@@ -7,10 +7,11 @@ import {
DrawerCustom,
DummyLandscapeImage,
MenuDrawerDynamicGrid,
OS_Wrapper,
StackCustom,
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";
@@ -27,6 +28,7 @@ import {
} from "expo-router";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
import { dateTimeView } from "@/utils/dateTimeView";
export default function DonationNews() {
const { user } = useAuth();
@@ -57,19 +59,24 @@ 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>
<OS_Wrapper>
<BaseBox>
<StackCustom>
<TextCustom style={{ alignSelf: "flex-end" }}>
{formatChatTime(data?.createdAt)}
<TextCustom color="gray" size={"small"} style={{ alignSelf: "flex-end" }}>
{dateTimeView({date: data?.createdAt})}
</TextCustom>
{data && data.imageId && (
@@ -83,7 +90,7 @@ export default function DonationNews() {
<TextCustom>{data?.deskripsi || "-"}</TextCustom>
</StackCustom>
</BaseBox>
</ViewWrapper>
</OS_Wrapper>
<DrawerCustom
isVisible={openDrawer}

View File

@@ -4,12 +4,11 @@ import {
ButtonCustom,
InformationBox,
LandscapeFrameUploaded,
NewWrapper,
OS_Wrapper,
Spacing,
StackCustom,
TextAreaCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import DIRECTORY_ID from "@/constants/directory-id";
import { apiDonationCreateNews } from "@/service/api-client/api-donation";
@@ -72,7 +71,9 @@ export default function DonationAddNews() {
};
return (
<NewWrapper
<OS_Wrapper
enableKeyboardHandling
contentPaddingBottom={250}
footerComponent={
<BoxButtonOnFooter>
<ButtonCustom
@@ -133,6 +134,6 @@ export default function DonationAddNews() {
<Spacing />
</StackCustom>
</NewWrapper>
</OS_Wrapper>
);
}

View File

@@ -2,10 +2,10 @@
import {
BaseBox,
Grid,
OS_Wrapper,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { apiDonationGetInvoiceById } from "@/service/api-client/api-donation";
@@ -63,7 +63,7 @@ export default function DonasiFailed() {
];
return (
<ViewWrapper>
<OS_Wrapper>
<StackCustom>
<BaseBox>
<StackCustom>
@@ -105,6 +105,6 @@ export default function DonasiFailed() {
</StackCustom>
</BaseBox>
</StackCustom>
</ViewWrapper>
</OS_Wrapper>
);
}

View File

@@ -6,10 +6,10 @@ import {
ButtonCustom,
Grid,
InformationBox,
OS_Wrapper,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import CopyButton from "@/components/Button/CoyButton";
import { MainColor } from "@/constants/color-palet";
@@ -101,8 +101,7 @@ export default function DonationInvoice() {
return (
<>
<ViewWrapper
hideFooter
<OS_Wrapper
footerComponent={
<BoxButtonOnFooter>
<ButtonCustom
@@ -222,7 +221,7 @@ export default function DonationInvoice() {
</BaseBox>
</StackCustom>
<Spacing />
</ViewWrapper>
</OS_Wrapper>
</>
);
}

View File

@@ -1,11 +1,11 @@
import { BaseBox, StackCustom, TextCustom, ViewWrapper } from "@/components";
import { BaseBox, OS_Wrapper, StackCustom, TextCustom } from "@/components";
import MoneyTransferAnimation from "@/components/_ShareComponent/MoneyTransferAnimation";
import { View } from "react-native";
export default function DonationProcess() {
return (
<>
<ViewWrapper>
<OS_Wrapper>
<BaseBox>
<StackCustom>
<TextCustom align="center" bold>
@@ -35,7 +35,7 @@ export default function DonationProcess() {
</Grid.Col>
</Grid>
</BaseBox> */}
</ViewWrapper>
</OS_Wrapper>
</>
);
}

View File

@@ -2,10 +2,10 @@
import {
BaseBox,
Grid,
OS_Wrapper,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { apiDonationGetInvoiceById } from "@/service/api-client/api-donation";
@@ -63,7 +63,7 @@ export default function DonationSuccess() {
];
return (
<ViewWrapper>
<OS_Wrapper>
<StackCustom>
<BaseBox>
<StackCustom>
@@ -105,7 +105,7 @@ export default function DonationSuccess() {
</StackCustom>
</BaseBox>
</StackCustom>
</ViewWrapper>
</OS_Wrapper>
);
}

View File

@@ -3,9 +3,9 @@ import {
BoxButtonOnFooter,
ButtonCustom,
Grid,
OS_Wrapper,
TextCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
@@ -64,7 +64,7 @@ export default function InvestmentInputDonation() {
);
return (
<>
<ViewWrapper footerComponent={bottomComponent}>
<OS_Wrapper enableKeyboardHandling contentPaddingBottom={250} footerComponent={bottomComponent}>
{listData.map((item, i) => (
<BaseBox key={i} onPress={() => setNominal(item.value)}>
<Grid>
@@ -98,7 +98,7 @@ export default function InvestmentInputDonation() {
Minimal donasi Rp. 10.000
</TextCustom>
</BaseBox>
</ViewWrapper>
</OS_Wrapper>
</>
);
}

View File

@@ -2,7 +2,7 @@ import {
BaseBox,
BoxButtonOnFooter,
ButtonCustom,
ViewWrapper,
OS_Wrapper,
} from "@/components";
import { RadioCustom, RadioGroup } from "@/components/Radio/RadioCustom";
import { LOCAL_STORAGE_KEY } from "@/constants/local-storage-key";
@@ -91,7 +91,7 @@ export default function DonationSelectBank() {
);
};
return (
<ViewWrapper footerComponent={buttonSubmit()}>
<OS_Wrapper footerComponent={buttonSubmit()}>
<RadioGroup value={select} onChange={setSelect}>
{_.isEmpty(listBank)
? []
@@ -101,6 +101,6 @@ export default function DonationSelectBank() {
</BaseBox>
))}
</RadioGroup>
</ViewWrapper>
</OS_Wrapper>
);
}

View File

@@ -4,9 +4,10 @@ import {
DotButton,
DrawerCustom,
MenuDrawerDynamicGrid,
NewWrapper,
OS_Wrapper,
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,17 +98,22 @@ 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
<OS_Wrapper
refreshControl={
<RefreshControl
refreshing={refreshing}
@@ -150,7 +156,7 @@ export default function DonasiDetailStatus() {
<Spacing />
</>
)}
</NewWrapper>
</OS_Wrapper>
<DrawerCustom
isVisible={openDrawer}

View File

@@ -1,9 +1,9 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
DummyLandscapeImage,
OS_Wrapper,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { apiDonationGetOne } from "@/service/api-client/api-donation";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
@@ -32,12 +32,12 @@ export default function DonationDetailStory() {
}
};
return (
<ViewWrapper>
<OS_Wrapper>
<StackCustom>
<TextCustom>{data?.pembukaan || "-"}</TextCustom>
<DummyLandscapeImage imageId={data?.imageId} />
<TextCustom>{data?.cerita || "-"}</TextCustom>
</StackCustom>
</ViewWrapper>
</OS_Wrapper>
);
}

View File

@@ -1,6 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
ViewWrapper,
OS_Wrapper,
StackCustom,
InformationBox,
TextInputCustom,
@@ -76,7 +76,7 @@ export default function DonationEditRekening() {
};
return (
<ViewWrapper>
<OS_Wrapper enableKeyboardHandling contentPaddingBottom={250}>
<StackCustom gap={"xs"}>
<InformationBox text="Pastikan Anda mengisi nama bank dan nomor rekening dengan benar. Informasi ini akan membantu admin memverifikasi dan memproses penggalangan dana Anda dengan cepat dan tepat setelah penggalangan dana dipublikasikan." />
<TextInputCustom
@@ -105,6 +105,6 @@ export default function DonationEditRekening() {
</ButtonCustom>
</StackCustom>
<Spacing />
</ViewWrapper>
</OS_Wrapper>
);
}

View File

@@ -4,10 +4,10 @@ import {
ButtonCustom,
InformationBox,
LandscapeFrameUploaded,
OS_Wrapper,
Spacing,
StackCustom,
TextAreaCustom,
ViewWrapper,
} from "@/components";
import API_IMAGE from "@/constants/api-storage";
import DIRECTORY_ID from "@/constants/directory-id";
@@ -93,7 +93,7 @@ export default function DonationEditStory() {
};
return (
<ViewWrapper>
<OS_Wrapper enableKeyboardHandling contentPaddingBottom={250}>
<StackCustom gap={"xs"}>
<InformationBox text="Cerita Anda adalah kunci untuk menginspirasi kebaikan. Jelaskan dengan jujur dan jelas tujuan penggalangan dana ini agar calon donatur memahami dampak positif yang dapat mereka wujudkan melalui kontribusi mereka." />
<TextAreaCustom
@@ -146,6 +146,6 @@ export default function DonationEditStory() {
</ButtonCustom>
</StackCustom>
<Spacing />
</ViewWrapper>
</OS_Wrapper>
);
}

View File

@@ -6,12 +6,11 @@ import {
InformationBox,
LandscapeFrameUploaded,
LoaderCustom,
NewWrapper,
OS_Wrapper,
SelectCustom,
Spacing,
StackCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
import API_IMAGE from "@/constants/api-storage";
@@ -184,8 +183,9 @@ export default function DonationEdit() {
};
return (
<NewWrapper
hideFooter
<OS_Wrapper
enableKeyboardHandling
contentPaddingBottom={250}
footerComponent={
<BoxButtonOnFooter>
<ButtonCustom
@@ -279,6 +279,6 @@ export default function DonationEdit() {
</StackCustom>
)}
<Spacing />
</NewWrapper>
</OS_Wrapper>
);
}

View File

@@ -6,10 +6,10 @@ import {
DotButton,
DrawerCustom,
MenuDrawerDynamicGrid,
NewWrapper,
OS_Wrapper,
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,15 +90,20 @@ 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}>
<OS_Wrapper footerComponent={buttonSection}>
{!data ? (
<CustomSkeleton height={400} />
) : (
@@ -121,7 +126,7 @@ export default function DonasiDetailBeranda() {
/>
</StackCustom>
)}
</NewWrapper>
</OS_Wrapper>
<DrawerCustom
isVisible={openDrawer}

View File

@@ -5,9 +5,9 @@ import {
ButtonCustom,
Grid,
LoaderCustom,
OS_Wrapper,
Spacing,
TextCustom,
ViewWrapper
} from "@/components";
import Donation_BoxPublish from "@/screens/Donation/BoxPublish";
import { apiDonationFundrising } from "@/service/api-client/api-donation";
@@ -44,7 +44,7 @@ export default function DonationInformationFunrising() {
return (
<>
<ViewWrapper>
<OS_Wrapper>
<BaseBox>
<Grid>
<Grid.Col span={6} style={{ justifyContent: "center" }}>
@@ -80,7 +80,7 @@ export default function DonationInformationFunrising() {
<Donation_BoxPublish key={index} id={item?.id} data={item} />
))
)}
</ViewWrapper>
</OS_Wrapper>
</>
);
}

View File

@@ -10,7 +10,7 @@ import {
TextAreaCustom,
TextInputCustom,
} from "@/components";
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
import OS_Wrapper from "@/components/_ShareComponent/OS_Wrapper";
import DIRECTORY_ID from "@/constants/directory-id";
import { useAuth } from "@/hooks/use-auth";
import {
@@ -113,8 +113,9 @@ export default function DonationCreateStory() {
};
return (
<NewWrapper
hideFooter
<OS_Wrapper
enableKeyboardHandling
contentPaddingBottom={250}
footerComponent={
<>
<BoxButtonOnFooter>
@@ -185,6 +186,6 @@ export default function DonationCreateStory() {
/>
</StackCustom>
<Spacing />
</NewWrapper>
</OS_Wrapper>
);
}

View File

@@ -5,12 +5,12 @@ import {
InformationBox,
LandscapeFrameUploaded,
LoaderCustom,
OS_Wrapper,
SelectCustom,
Spacing,
StackCustom,
TextInputCustom,
} from "@/components";
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
import DIRECTORY_ID from "@/constants/directory-id";
import { apiDonationCreate } from "@/service/api-client/api-donation";
import { apiMasterDonation } from "@/service/api-client/api-master";
@@ -126,8 +126,9 @@ export default function DonationCreate() {
};
return (
<NewWrapper
hideFooter
<OS_Wrapper
enableKeyboardHandling
contentPaddingBottom={250}
footerComponent={
<>
<BoxButtonOnFooter>
@@ -221,6 +222,6 @@ export default function DonationCreate() {
)}
</StackCustom>
<Spacing />
</NewWrapper>
</OS_Wrapper>
);
}

View File

@@ -4,64 +4,87 @@ 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";
export default function EventTabsLayout() {
const navigation = useNavigation();
import { router, Tabs, useLocalSearchParams } from "expo-router";
import { View } from "react-native";
import { MainColor } from "@/constants/color-palet";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { Platform } from "react-native";
import { OS_ANDROID_HEIGHT, OS_IOS_HEIGHT } from "@/constants/constans-value";
function EventTabsWrapper() {
const insets = useSafeAreaInsets();
const paddingBottom = Platform.OS === "android" ? insets.bottom : 0;
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.Screen
name="index"
options={{
title: "Beranda",
tabBarIcon: ({ color }) => <IconHome color={color} />,
<View style={{ flex: 1, backgroundColor: MainColor.darkblue }}>
<Tabs
screenOptions={{
...TabsStyles,
tabBarStyle: Platform.select({
ios: {
borderTopWidth: 0,
paddingTop: 12,
height: OS_IOS_HEIGHT,
},
android: {
borderTopWidth: 0,
paddingTop: 5,
height: OS_ANDROID_HEIGHT + paddingBottom,
},
}),
header: () => (
<AppHeader
title="Event"
left={
<BackButtonFromNotification
from={from || ""}
category={category}
/>
}
/>
),
}}
/>
<Tabs.Screen
name="status"
options={{
title: "Status",
tabBarIcon: ({ color }) => <IconStatus color={color} />,
}}
/>
<Tabs.Screen
name="contribution"
options={{
title: "Kontribusi",
tabBarIcon: ({ color }) => <IconContribution color={color} />,
}}
/>
<Tabs.Screen
name="history"
options={{
title: "Riwayat",
tabBarIcon: ({ color }) => <IconHistory color={color} />,
}}
/>
</Tabs>
>
<Tabs.Screen
name="index"
options={{
title: "Beranda",
tabBarIcon: ({ color }) => <IconHome color={color} />,
}}
/>
<Tabs.Screen
name="status"
options={{
title: "Status",
tabBarIcon: ({ color }) => <IconStatus color={color} />,
}}
/>
<Tabs.Screen
name="contribution"
options={{
title: "Kontribusi",
tabBarIcon: ({ color }) => <IconContribution color={color} />,
}}
/>
<Tabs.Screen
name="history"
options={{
title: "Riwayat",
tabBarIcon: ({ color }) => <IconHistory color={color} />,
}}
/>
</Tabs>
</View>
);
}
export default function EventTabsLayout() {
return <EventTabsWrapper />;
}

View File

@@ -5,11 +5,12 @@ import {
DrawerCustom,
Grid,
MenuDrawerDynamicGrid,
OS_Wrapper,
Spacing,
StackCustom,
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,15 +82,20 @@ 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>
<OS_Wrapper>
<BaseBox>
<StackCustom>
<TextCustom bold align="center" size="xlarge">
@@ -112,7 +118,7 @@ export default function EventDetailStatus() {
status={status as string}
/>
<Spacing />
</ViewWrapper>
</OS_Wrapper>
<DrawerCustom
isVisible={openDrawer}

View File

@@ -5,11 +5,12 @@ import {
ButtonCustom,
CenterCustom,
LoaderCustom,
OS_Wrapper,
StackCustom,
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,20 +266,22 @@ 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>
<OS_Wrapper>{handlerReturn()}</OS_Wrapper>
</>
);
}
@@ -497,7 +505,6 @@ const UserNotParticipan_And_DuringEvent = ({
);
};
// 🟡 ZONA ACARA BERLANGSUN
// User sudah terdaftar & Event sedang berlangsung & user harus konfirmasi
const UserParticipan_And_DuringEvent = ({

View File

@@ -4,9 +4,10 @@ import {
DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid,
OS_Wrapper,
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,19 +50,23 @@ 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>
<OS_Wrapper>
{isLoadData ? (
<LoaderCustom />
) : (
<Event_BoxDetailPublishSection data={data} />
)}
<Spacing />
</ViewWrapper>
</OS_Wrapper>
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}

View File

@@ -3,14 +3,13 @@ import {
BoxButtonOnFooter,
ButtonCustom,
LoaderCustom,
NewWrapper,
OS_Wrapper,
SelectCustom,
Spacing,
StackCustom,
TextAreaCustom,
TextCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom";
@@ -186,7 +185,9 @@ export default function EventEdit() {
return (
<>
<NewWrapper
<OS_Wrapper
enableKeyboardHandling
contentPaddingBottom={250}
footerComponent={
<BoxButtonOnFooter>
<ButtonCustom
@@ -283,7 +284,7 @@ export default function EventEdit() {
/>
</StackCustom>
)}
</NewWrapper>
</OS_Wrapper>
</>
);
}

View File

@@ -3,9 +3,10 @@ import {
DotButton,
DrawerCustom,
MenuDrawerDynamicGrid,
ViewWrapper,
OS_Wrapper,
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,15 +45,19 @@ 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>
<OS_Wrapper>
<Event_BoxDetailPublishSection data={data} />
<Spacing />
</ViewWrapper>
</OS_Wrapper>
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}

View File

@@ -6,8 +6,9 @@ import {
DotButton,
DrawerCustom,
MenuDrawerDynamicGrid,
ViewWrapper,
OS_Wrapper,
} 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";
@@ -121,9 +122,9 @@ export default function EventDetailPublish() {
if (isEventFinished) {
return (
<ViewWrapper>
<OS_Wrapper>
<CustomSkeleton />
</ViewWrapper>
</OS_Wrapper>
);
}
@@ -156,12 +157,16 @@ 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>
<OS_Wrapper>
{isLoadingData ? (
<CustomSkeleton height={400} />
) : (
@@ -170,7 +175,7 @@ export default function EventDetailPublish() {
footerButton={FooterButton()}
/>
)}
</ViewWrapper>
</OS_Wrapper>
<DrawerCustom
isVisible={openDrawer}

View File

@@ -1,7 +1,7 @@
import {
BoxButtonOnFooter,
ButtonCustom,
NewWrapper,
OS_Wrapper,
SelectCustom,
Spacing,
StackCustom,
@@ -112,7 +112,9 @@ export default function EventCreate() {
return (
<>
<NewWrapper
<OS_Wrapper
enableKeyboardHandling
contentPaddingBottom={250}
footerComponent={<BoxButtonOnFooter>{buttonSubmit}</BoxButtonOnFooter>}
>
<StackCustom gap={"xs"}>
@@ -182,7 +184,7 @@ export default function EventCreate() {
/>
</StackCustom>
</StackCustom>
</NewWrapper>
</OS_Wrapper>
</>
);
}

View File

@@ -1,4 +1,4 @@
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { OS_Wrapper } from "@/components";
import { GStyles } from "@/styles/global-styles";
import { useLocalSearchParams } from "expo-router";
import { Text } from "react-native";
@@ -7,8 +7,8 @@ export default function DetailEvent() {
const { id } = useLocalSearchParams();
console.log("id event >", id);
return (
<ViewWrapper>
<OS_Wrapper>
<Text style={GStyles.textLabel}>Detail Event {id}</Text>
</ViewWrapper>
</OS_Wrapper>
);
}

View File

@@ -2,8 +2,8 @@ import {
BoxButtonOnFooter,
ButtonCustom,
LoaderCustom,
OS_Wrapper,
TextAreaCustom,
ViewWrapper,
} from "@/components";
import AlertWarning from "@/components/Alert/AlertWarning";
import { apiForumGetOne, apiForumUpdate } from "@/service/api-client/api-forum";
@@ -88,7 +88,11 @@ export default function ForumEdit() {
};
return (
<ViewWrapper footerComponent={buttonFooter()}>
<OS_Wrapper
enableKeyboardHandling
contentPaddingBottom={250}
footerComponent={buttonFooter()}
>
{!loadingGetData ? (
<TextAreaCustom
placeholder="Ketik diskusi anda..."
@@ -102,6 +106,6 @@ export default function ForumEdit() {
) : (
<LoaderCustom />
)}
</ViewWrapper>
</OS_Wrapper>
);
}

View File

@@ -1,8 +1,8 @@
import {
BoxButtonOnFooter,
ButtonCustom,
OS_Wrapper,
TextAreaCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
@@ -62,13 +62,17 @@ export default function ForumOtherReportCommentar() {
return (
<>
<ViewWrapper footerComponent={handleSubmit}>
<OS_Wrapper
enableKeyboardHandling
contentPaddingBottom={250}
footerComponent={handleSubmit}
>
<TextAreaCustom
placeholder="Laporkan Komentar"
value={value}
onChangeText={setValue}
/>
</ViewWrapper>
</OS_Wrapper>
</>
);
}

View File

@@ -1,8 +1,8 @@
import {
BoxButtonOnFooter,
ButtonCustom,
OS_Wrapper,
TextAreaCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
@@ -61,13 +61,17 @@ export default function ForumOtherReportPosting() {
);
return (
<>
<ViewWrapper footerComponent={handleSubmit}>
<OS_Wrapper
enableKeyboardHandling
contentPaddingBottom={250}
footerComponent={handleSubmit}
>
<TextAreaCustom
placeholder="Laporkan Diskusi"
value={value}
onChangeText={setValue}
/>
</ViewWrapper>
</OS_Wrapper>
</>
);
}

View File

@@ -1,6 +1,6 @@
import {
BaseBox,
NewWrapper,
OS_Wrapper,
Spacing,
StackCustom,
TextCustom,
@@ -41,7 +41,7 @@ export default function ForumPreviewReportComment() {
return (
<>
<NewWrapper>
<OS_Wrapper>
<StackCustom>
<TextCustom color="red" bold>
Komentar anda telah melanggar aturan forum ! Admin mengambil
@@ -85,7 +85,7 @@ export default function ForumPreviewReportComment() {
</BaseBox>
))
)}
</NewWrapper>
</OS_Wrapper>
</>
);
}

View File

@@ -1,6 +1,6 @@
import {
BaseBox,
NewWrapper,
OS_Wrapper,
Spacing,
StackCustom,
TextCustom,
@@ -41,7 +41,7 @@ export default function ForumPreviewReportPosting() {
return (
<>
<NewWrapper>
<OS_Wrapper>
<StackCustom>
<TextCustom color="red" bold>
Postingan anda telah melanggar aturan forum ! Admin mengambil
@@ -85,7 +85,7 @@ export default function ForumPreviewReportPosting() {
</BaseBox>
))
)}
</NewWrapper>
</OS_Wrapper>
</>
);
}

View File

@@ -1,9 +1,9 @@
import {
ButtonCustom,
LoaderCustom,
OS_Wrapper,
Spacing,
StackCustom,
ViewWrapper,
} from "@/components";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
@@ -69,7 +69,7 @@ export default function ForumReportCommentar() {
return (
<>
<ViewWrapper>
<OS_Wrapper>
{isLoadingList ? (
<LoaderCustom />
) : (
@@ -101,7 +101,7 @@ export default function ForumReportCommentar() {
<Spacing />
</StackCustom>
)}
</ViewWrapper>
</OS_Wrapper>
</>
);
}

View File

@@ -2,9 +2,9 @@ import {
AlertDefaultSystem,
ButtonCustom,
LoaderCustom,
OS_Wrapper,
Spacing,
StackCustom,
ViewWrapper,
} from "@/components";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
@@ -73,7 +73,7 @@ export default function ForumReportPosting() {
return (
<>
<ViewWrapper>
<OS_Wrapper>
{isLoadingList ? (
<LoaderCustom />
) : (
@@ -114,7 +114,7 @@ export default function ForumReportPosting() {
<Spacing />
</StackCustom>
)}
</ViewWrapper>
</OS_Wrapper>
</>
);
}

View File

@@ -1,8 +1,8 @@
import {
BoxButtonOnFooter,
ButtonCustom,
OS_Wrapper,
TextAreaCustom,
ViewWrapper,
} from "@/components";
import AlertWarning from "@/components/Alert/AlertWarning";
import { useAuth } from "@/hooks/use-auth";
@@ -67,7 +67,11 @@ export default function ForumCreate() {
);
return (
<ViewWrapper footerComponent={buttonFooter}>
<OS_Wrapper
enableKeyboardHandling
contentPaddingBottom={250}
footerComponent={buttonFooter}
>
<TextAreaCustom
placeholder="Ketik diskusi anda..."
maxLength={1000}
@@ -75,6 +79,6 @@ export default function ForumCreate() {
value={text}
onChangeText={setText}
/>
</ViewWrapper>
</OS_Wrapper>
);
}

View File

@@ -2,7 +2,7 @@ import {
BaseBox,
ButtonCustom,
CheckboxCustom,
NewWrapper,
OS_Wrapper,
StackCustom,
TextCustom,
} from "@/components";
@@ -54,7 +54,7 @@ export default function ForumSplash() {
};
return (
<NewWrapper>
<OS_Wrapper>
{/* <TextCustom bold>HIPMI Badung Connect</TextCustom> . */}
<BaseBox>
@@ -162,7 +162,7 @@ export default function ForumSplash() {
</ButtonCustom>
</StackCustom>
</BaseBox>
</NewWrapper>
</OS_Wrapper>
);
}

View File

@@ -1,25 +1,27 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable react-hooks/exhaustive-deps */
import { BasicWrapper, StackCustom, ViewWrapper } from "@/components";
import { BasicWrapper, Spacing, 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";
import { useNotificationStore } from "@/hooks/use-notification-store";
import Home_BottomFeatureSection from "@/screens/Home/bottomFeatureSection";
import HeaderBell from "@/screens/Home/HeaderBell";
import HomeTabs from "@/screens/Home/HomeTabs";
import { stylesHome } from "@/screens/Home/homeViewStyle";
import Home_ImageSection from "@/screens/Home/imageSection";
import TabSection from "@/screens/Home/tabSection";
import { tabsHome } from "@/screens/Home/tabsList";
import Home_FeatureSection from "@/screens/Home/topFeatureSection";
import { apiJobGetAll } from "@/service/api-client/api-job";
import { apiUser } from "@/service/api-client/api-user";
import { apiVersion } from "@/service/api-config";
import { GStyles } from "@/styles/global-styles";
import { Ionicons } from "@expo/vector-icons";
import { Redirect, router, Stack, useFocusEffect } from "expo-router";
import { useCallback, useState } from "react";
import { RefreshControl, View } from "react-native";
import { RefreshControl, ScrollView, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { Platform } from "react-native";
export default function Application() {
const { token, user, userData } = useAuth();
@@ -27,6 +29,8 @@ export default function Application() {
const [refreshing, setRefreshing] = useState(false);
const { syncUnreadCount } = useNotificationStore();
const [listData, setListData] = useState<any[] | null>(null);
const insets = useSafeAreaInsets();
const paddingBottom = Platform.OS === "android" ? insets.bottom : 0;
useFocusEffect(
useCallback(() => {
@@ -104,98 +108,95 @@ export default function Application() {
);
}
// if (data && data?.masterUserRoleId !== "1") {
// console.log("User is not admin");
// return (
// <BasicWrapper>
// <Redirect href={`/admin/dashboard`} />
// </BasicWrapper>
// );
// }
return (
<>
<Stack.Screen
options={{
title: `HIPMI`,
headerLeft: () =>
data ? (
<Ionicons
name="search"
size={20}
color={MainColor.yellow}
onPress={() => {
router.push("/user-search");
}}
/>
) : (
<CustomSkeleton height={30} width={30} radius={100} />
),
headerRight: () =>
data ? (
<HeaderBell />
) : (
<CustomSkeleton height={30} width={30} radius={100} />
),
header: () => (
<AppHeader
title="HIPMI"
showBack={false}
left={
data ? (
<Ionicons
name="search"
size={20}
color={MainColor.yellow}
onPress={() => {
router.push("/user-search");
}}
/>
) : (
<CustomSkeleton height={30} width={30} radius={100} />
)
}
right={
data ? (
<HeaderBell />
) : (
<CustomSkeleton height={30} width={30} radius={100} />
)
}
/>
),
}}
/>
<ViewWrapper
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
tintColor={MainColor.yellow}
colors={[MainColor.yellow]}
/>
}
footerComponent={
data && data ? (
<TabSection
tabs={tabsHome({
acceptedForumTermsAt: data?.acceptedForumTermsAt,
profileId: data?.Profile?.id,
})}
<View style={{ flex: 1, backgroundColor: MainColor.darkblue }}>
<ScrollView
style={{ flex: 1 }}
contentContainerStyle={{
flexGrow: 1,
paddingInline: 10,
paddingBottom: paddingBottom + 80, // Space for tabs + safe area
}}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
tintColor={MainColor.yellow}
colors={[MainColor.yellow]}
/>
) : (
<View style={GStyles.tabBar}>
<View style={[GStyles.tabContainer, { paddingTop: 10 }]}>
{Array.from({ length: 4 }).map((e, index) => (
}
keyboardShouldPersistTaps="handled"
>
<StackCustom>
<Home_ImageSection />
{data && data ? (
<Home_FeatureSection />
) : (
<View style={stylesHome.gridContainer}>
{Array.from({ length: 4 }).map((_, index) => (
<CustomSkeleton
key={index}
height={40}
width={40}
radius={100}
style={stylesHome.gridItem}
radius={50}
/>
))}
</View>
</View>
)
}
>
<StackCustom>
<Home_ImageSection />
)}
{data && data ? (
<Home_FeatureSection />
) : (
<View style={stylesHome.gridContainer}>
{Array.from({ length: 4 }).map((item, index) => (
<CustomSkeleton
key={index}
style={stylesHome.gridItem}
radius={50}
/>
))}
</View>
)}
{data ? (
<Home_BottomFeatureSection listData={listData} />
) : (
<CustomSkeleton height={150} />
)}
</StackCustom>
</ScrollView>
{data ? (
<Home_BottomFeatureSection listData={listData} />
) : (
<CustomSkeleton height={200} />
)}
</StackCustom>
</ViewWrapper>
{/* Home Tabs di bawah */}
{data && data ? (
<HomeTabs
tabs={tabsHome({
acceptedForumTermsAt: data?.acceptedForumTermsAt,
profileId: data?.Profile?.id,
})}
/>
) : (
<View style={{ height: 80 + paddingBottom, backgroundColor: MainColor.darkblue }} />
)}
</View>
</>
);
}

View File

@@ -1,83 +1,108 @@
import BackButtonFromNotification from "@/components/Button/BackButtonFromNotification";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { ICON_SIZE_SMALL, OS_ANDROID_HEIGHT, OS_IOS_HEIGHT } from "@/constants/constans-value";
import { TabsStyles } from "@/styles/tabs-styles";
import { Feather, FontAwesome6, Ionicons } from "@expo/vector-icons";
import { router, Tabs, useLocalSearchParams, useNavigation } from "expo-router";
import { useLayoutEffect } from "react";
import { View } from "react-native";
import { MainColor } from "@/constants/color-palet";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { Platform } from "react-native";
export default function InvestmentTabsLayout() {
// const navigation = useNavigation();
function InvestmentTabsWrapper() {
const insets = useSafeAreaInsets();
const paddingBottom = Platform.OS === "android" ? insets.bottom : 0;
const navigation = useNavigation();
// const { from, category } = useLocalSearchParams<{
// from?: string;
// category?: string;
// }>();
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]);
// Atur header secara dinamis
useLayoutEffect(() => {
navigation.setOptions({
headerLeft: () => (
<BackButtonFromNotification
from={from || ""}
category={category}
/>
),
});
}, [from, category, router, navigation]);
return (
<Tabs screenOptions={TabsStyles}>
<Tabs.Screen
name="index"
options={{
title: "Bursa",
tabBarIcon: ({ color }) => (
<Ionicons
name="bar-chart-outline"
color={color}
size={ICON_SIZE_SMALL}
/>
),
<View style={{ flex: 1, backgroundColor: MainColor.darkblue }}>
<Tabs
screenOptions={{
...TabsStyles,
tabBarStyle: Platform.select({
ios: {
borderTopWidth: 0,
paddingTop: 12,
height: OS_IOS_HEIGHT,
},
android: {
borderTopWidth: 0,
paddingTop: 5,
height: OS_ANDROID_HEIGHT + paddingBottom,
},
}),
}}
/>
<Tabs.Screen
name="portofolio"
options={{
title: "Portofolio",
tabBarIcon: ({ color }) => (
<Feather name="pie-chart" color={color} size={ICON_SIZE_SMALL} />
),
}}
/>
<Tabs.Screen
name="my-holding"
options={{
title: "Saham Saya",
tabBarIcon: ({ color }) => (
<FontAwesome6
name="hand-holding-dollar"
color={color}
size={ICON_SIZE_SMALL}
/>
),
}}
/>
<Tabs.Screen
name="transaction"
options={{
title: "Transaksi",
tabBarIcon: ({ color }) => (
<FontAwesome6
name="money-bill-transfer"
color={color}
size={ICON_SIZE_SMALL}
/>
),
}}
/>
</Tabs>
>
<Tabs.Screen
name="index"
options={{
title: "Bursa",
tabBarIcon: ({ color }) => (
<Ionicons
name="bar-chart-outline"
color={color}
size={ICON_SIZE_SMALL}
/>
),
}}
/>
<Tabs.Screen
name="portofolio"
options={{
title: "Portofolio",
tabBarIcon: ({ color }) => (
<Feather name="pie-chart" color={color} size={ICON_SIZE_SMALL} />
),
}}
/>
<Tabs.Screen
name="my-holding"
options={{
title: "Saham Saya",
tabBarIcon: ({ color }) => (
<FontAwesome6
name="hand-holding-dollar"
color={color}
size={ICON_SIZE_SMALL}
/>
),
}}
/>
<Tabs.Screen
name="transaction"
options={{
title: "Transaksi",
tabBarIcon: ({ color }) => (
<FontAwesome6
name="money-bill-transfer"
color={color}
size={ICON_SIZE_SMALL}
/>
),
}}
/>
</Tabs>
</View>
);
}
export default function InvestmentTabsLayout() {
return <InvestmentTabsWrapper />;
}

View File

@@ -5,11 +5,11 @@ import {
ButtonCustom,
CenterCustom,
InformationBox,
OS_Wrapper,
Spacing,
StackCustom,
TextCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import DIRECTORY_ID from "@/constants/directory-id";
@@ -83,9 +83,12 @@ export default function InvestmentAddDocument() {
);
return (
<>
<ViewWrapper footerComponent={buttonFooter}>
<StackCustom gap={"xs"}>
<OS_Wrapper
enableKeyboardHandling
contentPaddingBottom={250}
footerComponent={buttonFooter}
>
<StackCustom gap={"xs"}>
<InformationBox text="File dokumen bersifat opsional, jika memang ada file yang bisa membantu meyakinkan investor. Anda bisa mengupload nya." />
<Spacing />
<TextInputCustom
@@ -124,7 +127,6 @@ export default function InvestmentAddDocument() {
Upload
</ButtonCenteredOnly>
</StackCustom>
</ViewWrapper>
</>
</OS_Wrapper>
);
}

View File

@@ -6,11 +6,11 @@ import {
ButtonCustom,
CenterCustom,
InformationBox,
OS_Wrapper,
Spacing,
StackCustom,
TextCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import DIRECTORY_ID from "@/constants/directory-id";
import {
@@ -105,9 +105,12 @@ export default function InvestmentEditDocument() {
);
return (
<>
<ViewWrapper footerComponent={buttonFooter}>
<StackCustom gap={"xs"}>
<OS_Wrapper
enableKeyboardHandling
contentPaddingBottom={250}
footerComponent={buttonFooter}
>
<StackCustom gap={"xs"}>
<InformationBox text="File dokumen bersifat opsional, jika memang ada file yang bisa membantu meyakinkan investor. Anda bisa mengupload nya." />
<Spacing />
<TextInputCustom
@@ -142,7 +145,6 @@ export default function InvestmentEditDocument() {
Upload
</ButtonCenteredOnly>
</StackCustom>
</ViewWrapper>
</>
</OS_Wrapper>
);
}

View File

@@ -6,10 +6,11 @@ import {
DrawerCustom,
Grid,
MenuDrawerDynamicGrid,
OS_Wrapper,
StackCustom,
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,18 +77,23 @@ 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
}
/>
),
}}
/>
<ViewWrapper>
<OS_Wrapper>
<BaseBox>
<StackCustom gap={"xs"}>
<Grid>
@@ -118,7 +124,7 @@ export default function InvestmentDetailHolding() {
status={"publish"}
bottomSection={bottomSection}
/>
</ViewWrapper>
</OS_Wrapper>
{/* ========= Draft Drawer ========= */}
<DrawerCustom

View File

@@ -7,10 +7,11 @@ import {
DrawerCustom,
DummyLandscapeImage,
MenuDrawerDynamicGrid,
OS_Wrapper,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import { IconTrash } from "@/components/_Icon/IconTrash";
import { useAuth } from "@/hooks/use-auth";
import {
@@ -56,15 +57,20 @@ 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>
<OS_Wrapper>
<BaseBox>
<StackCustom>
{data && data?.imageId && (
@@ -76,7 +82,7 @@ export default function InvestmentNews() {
<TextCustom>{(data && data?.deskripsi) || "-"}</TextCustom>
</StackCustom>
</BaseBox>
</ViewWrapper>
</OS_Wrapper>
<DrawerCustom
isVisible={openDrawer}

View File

@@ -3,11 +3,11 @@ import {
ButtonCustom,
InformationBox,
LandscapeFrameUploaded,
OS_Wrapper,
Spacing,
StackCustom,
TextAreaCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import DIRECTORY_ID from "@/constants/directory-id";
import { apiInvestmentCreateNews } from "@/service/api-client/api-investment";
@@ -80,7 +80,10 @@ export default function InvestmentAddNews() {
};
return (
<ViewWrapper>
<OS_Wrapper
enableKeyboardHandling
contentPaddingBottom={250}
>
<StackCustom gap={"xs"}>
<InformationBox text="Pengunggahan foto ke aplikasi bersifat opsional dan tidak diwajibkan, Anda dapat menyimpan berita tanpa mengunggah foto." />
<LandscapeFrameUploaded image={image?.uri} />
@@ -123,7 +126,6 @@ export default function InvestmentAddNews() {
Simpan
</ButtonCustom>
</StackCustom>
<Spacing />
</ViewWrapper>
</OS_Wrapper>
);
}

View File

@@ -2,10 +2,10 @@
import {
BaseBox,
Grid,
OS_Wrapper,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { apiInvestmentGetInvoice } from "@/service/api-client/api-investment";
@@ -69,7 +69,7 @@ export default function InvestmentFailed() {
];
return (
<ViewWrapper>
<OS_Wrapper>
<StackCustom>
<BaseBox>
<StackCustom>
@@ -110,6 +110,6 @@ export default function InvestmentFailed() {
</StackCustom>
</BaseBox>
</StackCustom>
</ViewWrapper>
</OS_Wrapper>
);
}

View File

@@ -5,10 +5,10 @@ import {
ButtonCustom,
Divider,
Grid,
OS_Wrapper,
StackCustom,
TextCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import { LOCAL_STORAGE_KEY } from "@/constants/local-storage-key";
import { apiInvestmentGetOne } from "@/service/api-client/api-investment";
@@ -99,9 +99,12 @@ export default function InvestmentInvest() {
};
return (
<>
<ViewWrapper footerComponent={buttonSubmit()}>
<BaseBox>
<OS_Wrapper
enableKeyboardHandling
contentPaddingBottom={250}
footerComponent={buttonSubmit()}
>
<BaseBox>
<StackCustom gap={"xs"}>
<Grid>
<Grid.Col span={6}>
@@ -165,7 +168,6 @@ export default function InvestmentInvest() {
</Grid>
</StackCustom>
</BaseBox>
</ViewWrapper>
</>
</OS_Wrapper>
);
}

View File

@@ -1,11 +1,11 @@
import { BaseBox, StackCustom, TextCustom, ViewWrapper } from "@/components";
import { BaseBox, OS_Wrapper, StackCustom, TextCustom } from "@/components";
import MoneyTransferAnimation from "@/components/_ShareComponent/MoneyTransferAnimation";
import { View } from "react-native";
export default function InvestmentProcess() {
return (
<>
<ViewWrapper>
<OS_Wrapper>
<BaseBox>
<StackCustom>
<TextCustom align="center" bold>
@@ -35,7 +35,7 @@ export default function InvestmentProcess() {
</Grid.Col>
</Grid>
</BaseBox> */}
</ViewWrapper>
</OS_Wrapper>
</>
);
}

View File

@@ -2,7 +2,7 @@ import {
BaseBox,
BoxButtonOnFooter,
ButtonCustom,
ViewWrapper,
OS_Wrapper,
} from "@/components";
import { RadioCustom, RadioGroup } from "@/components/Radio/RadioCustom";
import { LOCAL_STORAGE_KEY } from "@/constants/local-storage-key";
@@ -86,7 +86,7 @@ export default function InvestmentSelectBank() {
};
return (
<ViewWrapper footerComponent={buttonSubmit()}>
<OS_Wrapper footerComponent={buttonSubmit()}>
<RadioGroup value={select} onChange={setSelect}>
{_.isEmpty(listBank)
? []
@@ -96,6 +96,6 @@ export default function InvestmentSelectBank() {
</BaseBox>
))}
</RadioGroup>
</ViewWrapper>
</OS_Wrapper>
);
}

View File

@@ -2,10 +2,10 @@
import {
BaseBox,
Grid,
OS_Wrapper,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { apiInvestmentGetInvoice } from "@/service/api-client/api-investment";
@@ -69,7 +69,7 @@ export default function InvestmentSuccess() {
];
return (
<ViewWrapper>
<OS_Wrapper>
<StackCustom>
<BaseBox>
<StackCustom>
@@ -110,6 +110,6 @@ export default function InvestmentSuccess() {
</StackCustom>
</BaseBox>
</StackCustom>
</ViewWrapper>
</OS_Wrapper>
);
}

View File

@@ -4,8 +4,9 @@ import {
DotButton,
DrawerCustom,
MenuDrawerDynamicGrid,
ViewWrapper,
OS_Wrapper,
} 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,25 +107,30 @@ 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
}
/>
),
}}
/>
<ViewWrapper>
<OS_Wrapper>
<Invesment_DetailDataPublishSection
status={status as string}
data={data}
bottomSection={bottomSection}
buttonSection={buttonSection}
/>
</ViewWrapper>
</OS_Wrapper>
{/* ========= Draft Drawer ========= */}
<DrawerCustom

View File

@@ -7,10 +7,10 @@ import {
CenterCustom,
InformationBox,
LoaderCustom,
OS_Wrapper,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import DIRECTORY_ID from "@/constants/directory-id";
import {
@@ -116,7 +116,7 @@ export default function InvestmentEditProspectus() {
</BoxButtonOnFooter>
);
return (
<ViewWrapper footerComponent={!loadingGet && buttonFooter}>
<OS_Wrapper footerComponent={!loadingGet && buttonFooter}>
<StackCustom gap={"xs"}>
<InformationBox text="File prospektus wajib untuk diupload, agar calon investor paham dengan prospek investasi yang akan anda jalankan kedepan." />
<Spacing />
@@ -153,6 +153,6 @@ export default function InvestmentEditProspectus() {
Upload
</ButtonCenteredOnly>
</StackCustom>
</ViewWrapper>
</OS_Wrapper>
);
}

View File

@@ -6,7 +6,7 @@ import {
InformationBox,
LandscapeFrameUploaded,
LoaderCustom,
NewWrapper,
OS_Wrapper,
SelectCustom,
Spacing,
StackCustom,
@@ -199,7 +199,9 @@ export default function InvestmentEdit() {
};
return (
<NewWrapper
<OS_Wrapper
enableKeyboardHandling
contentPaddingBottom={250}
footerComponent={
<BoxButtonOnFooter>
<ButtonCustom isLoading={isLoading} onPress={handleSubmitUpdate}>
@@ -350,6 +352,6 @@ export default function InvestmentEdit() {
<Spacing />
</StackCustom>
</NewWrapper>
</OS_Wrapper>
);
}

View File

@@ -4,8 +4,9 @@ import {
DotButton,
DrawerCustom,
MenuDrawerDynamicGrid,
ViewWrapper,
OS_Wrapper,
} 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,25 +106,30 @@ 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
}
/>
),
}}
/>
<ViewWrapper>
<OS_Wrapper>
<Invesment_DetailDataPublishSection
status={"publish"}
data={data}
bottomSection={bottomSection}
buttonSection={buttonSection}
/>
</ViewWrapper>
</OS_Wrapper>
{/* ========= Draft Drawer ========= */}
<DrawerCustom

View File

@@ -6,7 +6,7 @@ import {
CenterCustom,
InformationBox,
LandscapeFrameUploaded,
NewWrapper,
OS_Wrapper,
SelectCustom,
Spacing,
StackCustom,
@@ -185,7 +185,9 @@ export default function InvestmentCreate() {
// const [coba, setCoba] = useState("");
return (
<NewWrapper
<OS_Wrapper
enableKeyboardHandling
contentPaddingBottom={250}
footerComponent={
<BoxButtonOnFooter>
<ButtonCustom
@@ -373,7 +375,6 @@ export default function InvestmentCreate() {
<Spacing />
</StackCustom>
{/* <Spacing height={50} /> */}
</NewWrapper>
</OS_Wrapper>
);
}

View File

@@ -1,37 +1,60 @@
/* 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";
import { Ionicons } from "@expo/vector-icons";
import { router, Tabs, useLocalSearchParams } from "expo-router";
import { View } from "react-native";
import { MainColor } from "@/constants/color-palet";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { Platform } from "react-native";
import {
router,
Tabs,
useLocalSearchParams,
useNavigation
} from "expo-router";
import { useLayoutEffect } from "react";
export default function JobTabsLayout() {
const navigation = useNavigation();
OS_ANDROID_HEIGHT,
OS_ANDROID_PADDING_TOP,
OS_IOS_HEIGHT,
OS_IOS_PADDING_TOP,
} from "@/constants/constans-value";
function JobTabsWrapper() {
const insets = useSafeAreaInsets();
const paddingBottom = Platform.OS === "android" ? insets.bottom : 0;
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}>
<View style={{ flex: 1, backgroundColor: MainColor.darkblue }}>
<Tabs
screenOptions={{
...TabsStyles,
tabBarStyle: Platform.select({
ios: {
borderTopWidth: 0,
paddingTop: OS_IOS_PADDING_TOP,
height: OS_IOS_HEIGHT,
},
android: {
borderTopWidth: 0,
paddingTop: OS_ANDROID_PADDING_TOP,
height: OS_ANDROID_HEIGHT + paddingBottom,
},
}),
header: () => (
<AppHeader
title="Job Vacancy"
left={
<BackButtonFromNotification
from={from || ""}
category={category}
/>
}
/>
),
}}
>
<Tabs.Screen
name="index"
options={{
@@ -56,6 +79,10 @@ export default function JobTabsLayout() {
}}
/>
</Tabs>
</>
</View>
);
}
export default function JobTabsLayout() {
return <JobTabsWrapper />;
}

View File

@@ -5,10 +5,11 @@ import {
DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid,
OS_Wrapper,
Spacing,
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";
@@ -22,6 +23,7 @@ import {
useLocalSearchParams,
} from "expo-router";
import { useCallback, useState } from "react";
import { PADDING_INLINE } from "@/constants/constans-value";
export default function JobDetailStatus() {
const { id, status } = useLocalSearchParams();
@@ -58,15 +60,20 @@ 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>
<OS_Wrapper >
{isLoadData ? (
<LoaderCustom />
) : (
@@ -77,7 +84,7 @@ export default function JobDetailStatus() {
(status === "draft" || status === "reject") && (
<ReportBox text={data?.catatan} />
)}
<Job_BoxDetailSection data={data} />
<Job_ButtonStatusSection
id={id as string}
@@ -90,7 +97,7 @@ export default function JobDetailStatus() {
<Spacing />
</>
)}
</ViewWrapper>
</OS_Wrapper>
<DrawerCustom
isVisible={openDrawer}

View File

@@ -1,198 +1,5 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BaseBox,
ButtonCenteredOnly,
ButtonCustom,
DummyLandscapeImage,
InformationBox,
LandscapeFrameUploaded,
LoaderCustom,
Spacing,
StackCustom,
TextAreaCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import DIRECTORY_ID from "@/constants/directory-id";
import { apiJobGetOne, apiJobUpdateData } from "@/service/api-client/api-job";
import {
deleteFileService,
uploadFileService,
} from "@/service/upload-service";
import pickImage from "@/utils/pickImage";
import { router, useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react";
import Toast from "react-native-toast-message";
import { Job_ScreenEdit } from "@/screens/Job/ScreenJobEdit";
export default function JobEdit() {
const { id } = useLocalSearchParams();
const [data, setData] = useState<any>({
title: "",
content: "",
deskripsi: "",
});
const [isLoadData, setIsLoadData] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [imageUri, setImageUri] = useState<string | null>(null);
useEffect(() => {
onLoadData();
}, [id]);
const onLoadData = async () => {
try {
setIsLoadData(true);
const response = await apiJobGetOne({ id: id as string });
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadData(false);
}
};
const handlerOnUpdate = async () => {
if (!data.title || !data.content || !data.deskripsi) {
Toast.show({
type: "info",
text1: "Info",
text2: "Harap isi semua data",
});
return;
}
try {
setIsLoading(true);
let newImageId = "";
if (imageUri) {
const responseUploadImage = await uploadFileService({
imageUri: imageUri,
dirId: DIRECTORY_ID.job_image,
});
if (responseUploadImage.success) {
newImageId = responseUploadImage.data.id;
}
}
if (data?.imageId) {
const responseDeleteImage = await deleteFileService({
id: data.imageId,
});
if (!responseDeleteImage.success) {
console.log("[ERROR DELETE IMAGE]", responseDeleteImage.message);
}
}
const newData = {
title: data.title,
content: data.content,
deskripsi: data.deskripsi,
imageId: newImageId,
};
const response = await apiJobUpdateData({
id: id as string,
data: newData,
category: "edit",
});
if (response.success) {
Toast.show({
type: "success",
text1: response.message,
});
router.back();
} else {
Toast.show({
type: "info",
text1: "Info",
text2: response.message,
});
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
const buttonSubmit = () => {
return (
<>
<ButtonCustom isLoading={isLoading} onPress={() => handlerOnUpdate()}>
Update
</ButtonCustom>
<Spacing />
</>
);
};
return (
<ViewWrapper>
{isLoadData ? (
<LoaderCustom />
) : (
<StackCustom gap={"xs"}>
<InformationBox text="Poster atau gambar lowongan kerja bersifat opsional, tidak wajib untuk dimasukkan dan upload lah gambar yang sesuai dengan deskripsi lowongan kerja." />
{imageUri ? (
<LandscapeFrameUploaded image={imageUri as any} />
) : (
<BaseBox>
<DummyLandscapeImage imageId={data?.imageId} />
</BaseBox>
)}
<ButtonCenteredOnly
onPress={() => {
pickImage({
setImageUri,
});
}}
icon="upload"
>
Upload
</ButtonCenteredOnly>
<Spacing />
<TextInputCustom
label="Judul Lowongan"
placeholder="Masukan Judul Lowongan Kerja"
required
value={data.title}
onChangeText={(value) => setData({ ...data, title: value })}
/>
<TextAreaCustom
label="Syarat & Kualifikasi"
placeholder="Masukan Syarat & Kualifikasi Lowongan Kerja"
required
showCount
maxLength={1000}
value={data.content}
onChangeText={(value) => setData({ ...data, content: value })}
/>
<TextAreaCustom
label="Deskripsi Lowongan"
placeholder="Masukan Deskripsi Lowongan Kerja"
required
showCount
maxLength={1000}
value={data.deskripsi}
onChangeText={(value) => setData({ ...data, deskripsi: value })}
/>
{buttonSubmit()}
</StackCustom>
)}
</ViewWrapper>
);
return <Job_ScreenEdit />;
}

View File

@@ -1,168 +1,5 @@
import {
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
InformationBox,
LandscapeFrameUploaded,
NewWrapper,
Spacing,
StackCustom,
TextAreaCustom,
TextInputCustom
} from "@/components";
import DIRECTORY_ID from "@/constants/directory-id";
import { useAuth } from "@/hooks/use-auth";
import { apiJobCreate } from "@/service/api-client/api-job";
import { uploadFileService } from "@/service/upload-service";
import pickImage from "@/utils/pickImage";
import { router } from "expo-router";
import { useState } from "react";
import Toast from "react-native-toast-message";
import { Job_ScreenCreate } from "@/screens/Job/ScreenJobCreate";
export default function JobCreate() {
const nextUrl = "/(application)/(user)/job/(tabs)/status?status=review";
const { user } = useAuth();
const [isLoading, setIsLoading] = useState(false);
const [image, setImage] = useState<string | null>(null);
const [data, setData] = useState({
title: "",
content: "",
deskripsi: "",
authorId: "",
});
const handlerOnSubmit = async () => {
let imageId = "";
const newData = {
title: data.title,
content: data.content,
deskripsi: data.deskripsi,
authorId: user?.id,
imageId: "",
};
if (!data.title || !data.content || !data.deskripsi || !user?.id) {
Toast.show({
type: "info",
text1: "Info",
text2: "Harap isi semua data",
});
return;
}
try {
setIsLoading(true);
if (image === null || !image) {
const response = await apiJobCreate(newData);
if (response.success) {
Toast.show({
type: "success",
text1: "Berhasil",
text2: "Lowongan berhasil dibuat",
});
router.replace(nextUrl);
}
return;
}
const responseUploadImage = await uploadFileService({
imageUri: image,
dirId: DIRECTORY_ID.job_image,
});
if (responseUploadImage.success) {
imageId = responseUploadImage.data.id;
}
const fixData = {
...newData,
imageId: imageId,
};
const response = await apiJobCreate(fixData);
if (response.success) {
Toast.show({
type: "success",
text1: "Berhasil",
text2: "Lowongan berhasil dibuat",
});
router.replace(nextUrl);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
const buttonSubmit = () => {
return (
<>
<BoxButtonOnFooter>
<ButtonCustom isLoading={isLoading} onPress={() => handlerOnSubmit()}>
Simpan
</ButtonCustom>
</BoxButtonOnFooter>
</>
);
};
return (
<NewWrapper footerComponent={buttonSubmit()}>
<StackCustom gap={"xs"}>
<InformationBox text="Poster atau gambar lowongan kerja bersifat opsional, tidak wajib untuk dimasukkan dan upload lah gambar yang sesuai dengan deskripsi lowongan kerja." />
{/* <BaseBox>
<Image
source={image ? { uri: image } : DUMMY_IMAGE.dummy_image}
style={{ width: "100%", height: 200 }}
/>
</BaseBox> */}
<LandscapeFrameUploaded image={image as string} />
<ButtonCenteredOnly
onPress={() => {
// router.push("/(application)/(image)/take-picture/123");
pickImage({
setImageUri: setImage,
});
}}
icon="upload"
>
Upload
</ButtonCenteredOnly>
<Spacing />
<TextInputCustom
label="Judul Lowongan"
placeholder="Masukan Judul Lowongan Kerja"
required
value={data.title}
onChangeText={(value) => setData({ ...data, title: value })}
/>
<TextAreaCustom
label="Syarat & Kualifikasi"
placeholder="Masukan Syarat & Kualifikasi Lowongan Kerja"
required
showCount
maxLength={1000}
value={data.content}
onChangeText={(value) => setData({ ...data, content: value })}
/>
<TextAreaCustom
label="Deskripsi Lowongan"
placeholder="Masukan Deskripsi Lowongan Kerja"
required
showCount
maxLength={1000}
value={data.deskripsi}
onChangeText={(value) => setData({ ...data, deskripsi: value })}
/>
</StackCustom>
</NewWrapper>
);
return <Job_ScreenCreate />;
}

View File

@@ -1,365 +1,5 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import {
ActionIcon,
AvatarComp,
BaseBox,
ButtonCenteredOnly,
CenterCustom,
Grid,
InformationBox,
NewWrapper,
SelectCustom,
Spacing,
StackCustom,
TextAreaCustom,
TextCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import { IconPlus } from "@/components/_Icon";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_XLARGE } from "@/constants/constans-value";
import DUMMY_IMAGE from "@/constants/dummy-image-value";
import Portofolio_ButtonCreate from "@/screens/Portofolio/ButtonCreatePortofolio";
import {
apiMasterBidangBisnis,
apiMasterSubBidangBisnis,
} from "@/service/api-client/api-master";
import {
IMasterBidangBisnis,
IMasterSubBidangBisnis,
} from "@/types/Type-Master";
import pickImage from "@/utils/pickImage";
import { Ionicons } from "@expo/vector-icons";
import { Image } from "expo-image";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useEffect, useState } from "react";
import { Text, TouchableOpacity, View } from "react-native";
import PhoneInput, { ICountry } from "react-native-international-phone-number";
import { Avatar } from "react-native-paper";
import { 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 />;
}

View File

@@ -3,7 +3,7 @@ import {
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
ViewWrapper
OS_Wrapper
} from "@/components";
import API_STRORAGE from "@/constants/base-url-api-strorage";
import DIRECTORY_ID from "@/constants/directory-id";
@@ -126,7 +126,7 @@ export default function PortofolioEditLogo() {
return (
<>
<ViewWrapper footerComponent={buttonFooter}>
<OS_Wrapper footerComponent={buttonFooter}>
<BaseBox
style={{
alignItems: "center",
@@ -146,7 +146,7 @@ export default function PortofolioEditLogo() {
>
Upload
</ButtonCenteredOnly>
</ViewWrapper>
</OS_Wrapper>
</>
);
}

View File

@@ -1,8 +1,8 @@
import {
BoxButtonOnFooter,
ButtonCustom,
OS_Wrapper,
TextInputCustom,
ViewWrapper,
} from "@/components";
import {
apiGetOnePortofolio,
@@ -91,7 +91,11 @@ export default function PortofolioEditSocialMedia() {
return (
<>
<ViewWrapper footerComponent={buttonFooter}>
<OS_Wrapper
enableKeyboardHandling
contentPaddingBottom={250}
footerComponent={buttonFooter}
>
<TextInputCustom
value={data.tiktok}
onChangeText={(value) => setData({ ...data, tiktok: value })}
@@ -122,7 +126,7 @@ export default function PortofolioEditSocialMedia() {
label="Youtube"
placeholder="Masukkan youtube"
/>
</ViewWrapper>
</OS_Wrapper>
</>
);
}

View File

@@ -4,7 +4,8 @@ import {
BoxButtonOnFooter,
ButtonCustom,
CenterCustom,
NewWrapper,
OS_Wrapper,
PhoneInputCustom,
SelectCustom,
Spacing,
StackCustom,
@@ -15,6 +16,11 @@ import {
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_XLARGE } from "@/constants/constans-value";
import {
DEFAULT_COUNTRY,
type CountryData,
COUNTRIES,
} from "@/constants/countries";
import {
apiMasterBidangBisnis,
apiMasterSubBidangBisnis,
@@ -32,7 +38,6 @@ import { router, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useEffect, useState } from "react";
import { Text, View } from "react-native";
import PhoneInput, { ICountry } from "react-native-international-phone-number";
import { ActivityIndicator } from "react-native-paper";
import Toast from "react-native-toast-message";
@@ -59,8 +64,9 @@ export default function PortofolioEdit() {
const { id } = useLocalSearchParams();
const [isLoading, setIsLoading] = useState(false);
const [data, setData] = useState<any>({});
const [selectedCountry, setSelectedCountry] = useState<null | ICountry>(null);
const [phoneNumber, setPhoneNumber] = useState<string>("");
const [selectedCountry, setSelectedCountry] =
useState<CountryData>(DEFAULT_COUNTRY);
const [bidangBisnis, setBidangBisnis] = useState<
IMasterBidangBisnis[] | null
>(null);
@@ -72,12 +78,42 @@ export default function PortofolioEdit() {
IListSubBidangSelected[]
>([]);
function handleInputValue(phoneNumber: string) {
setData({ ...data, tlpn: phoneNumber });
function handlePhoneChange(phone: string) {
setPhoneNumber(phone);
// Format phone number for API
const callingCode = selectedCountry.callingCode;
let fixNumber = phone.replace(/\s+/g, "").replace(/^0+/, "");
// Remove country code if already present
if (fixNumber.startsWith(callingCode)) {
fixNumber = fixNumber.substring(callingCode.length);
}
// Remove leading zero
fixNumber = fixNumber.replace(/^0+/, "");
const realNumber = callingCode + fixNumber;
setData({ ...data, tlpn: realNumber });
}
function handleSelectedCountry(country: ICountry) {
function handleCountryChange(country: CountryData) {
setSelectedCountry(country);
// Re-format with new country code
const callingCode = country.callingCode;
let fixNumber = phoneNumber.replace(/\s+/g, "").replace(/^0+/, "");
// Remove country code if already present
if (fixNumber.startsWith(callingCode)) {
fixNumber = fixNumber.substring(callingCode.length);
}
// Remove leading zero
fixNumber = fixNumber.replace(/^0+/, "");
const realNumber = callingCode + fixNumber;
setData({ ...data, tlpn: realNumber });
}
const onLoadMasterBidang = async () => {
@@ -122,8 +158,27 @@ export default function PortofolioEdit() {
const response = await apiGetOnePortofolio({ id: id });
if (response.success) {
const fixNumber = response.data.tlpn.replace("62", "");
setData({ ...response.data, tlpn: fixNumber });
// Extract phone number without country code for display
const fullNumber = response.data.tlpn;
let displayNumber = fullNumber;
let detectedCountry = DEFAULT_COUNTRY;
// Try to detect country from calling code
for (const country of COUNTRIES) {
if (fullNumber.startsWith(country.callingCode)) {
detectedCountry = country;
displayNumber = fullNumber.substring(country.callingCode.length);
break;
}
}
setSelectedCountry(detectedCountry);
// Remove leading zero if present
displayNumber = displayNumber.replace(/^0+/, "");
setPhoneNumber(displayNumber);
setData({ ...response.data, tlpn: displayNumber });
// Cek apakah ada sub bidang bisnis yang terpilih
const prevSubBidang = response.data.Portofolio_BidangDanSubBidangBisnis;
@@ -244,15 +299,11 @@ export default function PortofolioEdit() {
}
const handleSubmitUpdate = async () => {
const callingCode = selectedCountry?.callingCode.replace(/^\+/, "") || "";
let fixNumber = data.tlpn.replace(/\s+/g, "").replace(/^0+/, "");
const realNumber = callingCode + fixNumber;
const newData: IFormData = {
id_Portofolio: data.id_Portofolio,
namaBisnis: data.namaBisnis,
alamatKantor: data.alamatKantor,
tlpn: realNumber,
tlpn: data.tlpn, // Already formatted by PhoneInputCustom
deskripsi: data.deskripsi,
masterBidangBisnisId: data.masterBidangBisnisId,
subBidang: listSubBidangSelected,
@@ -317,162 +368,159 @@ export default function PortofolioEdit() {
</BoxButtonOnFooter>
);
if (!bidangBisnis || !subBidangBisnis) {
return (
<>
<NewWrapper>
<ListSkeletonComponent height={80} />
</NewWrapper>
</>
);
}
return (
<>
<NewWrapper footerComponent={buttonUpdate}>
<StackCustom gap={"xs"}>
<TextInputCustom
required
label="Nama Bisnis"
placeholder="Masukkan nama bisnis"
value={data.namaBisnis}
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: any) => {
handleBidangBisnisChange(value);
}}
/>
{listSubBidangSelected.map((item, index) => {
// Filter data untuk select sub bidang, menghilangkan yang sudah dipilih kecuali untuk item ini sendiri
const selectedIds = listSubBidangSelected
.filter((_, i) => i !== index)
.map((s) => s.MasterSubBidangBisnis?.id)
.filter((id) => id); // Filter hanya yang memiliki id (tidak kosong)
const availableSubBidangOptions = (selectedSubBidang || [])
.filter((sub: any) => {
// Tampilkan jika ini adalah opsi yang dipilih saat ini atau belum dipilih di sub bidang lainnya
return (
sub.id === item.MasterSubBidangBisnis?.id ||
!selectedIds.includes(sub.id)
);
})
.map((sub: any) => ({
value: sub.id,
label: sub.name,
}));
return (
<SelectCustom
key={index}
label="Sub Bidang Usaha"
required
data={availableSubBidangOptions}
value={item.MasterSubBidangBisnis?.id || null}
onChange={(value: any) => {
handleSubBidangChange(value, index);
}}
/>
);
})}
<CenterCustom>
<View
style={{ flexDirection: "row", alignItems: "center", gap: 10 }}
>
<ActionIcon
disabled={
selectedSubBidang.length === listSubBidangSelected.length
}
onPress={() => {
handleAddSubBidang();
}}
icon={
<Ionicons
name="add-circle-outline"
size={ICON_SIZE_XLARGE}
color={MainColor.black}
/>
}
size="xl"
/>
<ActionIcon
disabled={listSubBidangSelected.length <= 1}
onPress={() => {
handleRemoveSubBidang(listSubBidangSelected.length - 1);
}}
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={data.tlpn}
onChangePhoneNumber={handleInputValue}
selectedCountry={selectedCountry}
onChangeSelectedCountry={handleSelectedCountry}
defaultCountry="ID"
placeholder="xxx-xxx-xxx"
<OS_Wrapper
enableKeyboardHandling
contentPaddingBottom={250}
footerComponent={buttonUpdate}
>
{!bidangBisnis || !subBidangBisnis ? (
<ListSkeletonComponent height={80} />
) : (
<StackCustom gap={"xs"}>
<TextInputCustom
required
label="Nama Bisnis"
placeholder="Masukkan nama bisnis"
value={data.namaBisnis}
onChangeText={(value: any) =>
setData({ ...data, namaBisnis: value })
}
/>
</View>
<Spacing />
<TextInputCustom
required
label="Alamat Bisnis"
placeholder="Masukkan alamat bisnis"
value={data.alamatKantor}
onChangeText={(value: any) =>
setData({ ...data, alamatKantor: value })
}
/>
<SelectCustom
label="Bidang Usaha"
required
data={bidangBisnis?.map((item) => ({
label: item.name,
value: item.id,
}))}
value={data.masterBidangBisnisId}
onChange={(value: any) => {
handleBidangBisnisChange(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 />
</StackCustom>
</NewWrapper>
{listSubBidangSelected.map((item, index) => {
// Filter data untuk select sub bidang, menghilangkan yang sudah dipilih kecuali untuk item ini sendiri
const selectedIds = listSubBidangSelected
.filter((_, i) => i !== index)
.map((s) => s.MasterSubBidangBisnis?.id)
.filter((id) => id); // Filter hanya yang memiliki id (tidak kosong)
const availableSubBidangOptions = (selectedSubBidang || [])
.filter((sub: any) => {
// Tampilkan jika ini adalah opsi yang dipilih saat ini atau belum dipilih di sub bidang lainnya
return (
sub.id === item.MasterSubBidangBisnis?.id ||
!selectedIds.includes(sub.id)
);
})
.map((sub: any) => ({
value: sub.id,
label: sub.name,
}));
return (
<SelectCustom
key={index}
label="Sub Bidang Usaha"
required
data={availableSubBidangOptions}
value={item.MasterSubBidangBisnis?.id || null}
onChange={(value: any) => {
handleSubBidangChange(value, index);
}}
/>
);
})}
<CenterCustom>
<View
style={{ flexDirection: "row", alignItems: "center", gap: 10 }}
>
<ActionIcon
disabled={
selectedSubBidang.length === listSubBidangSelected.length
}
onPress={() => {
handleAddSubBidang();
}}
icon={
<Ionicons
name="add-circle-outline"
size={ICON_SIZE_XLARGE}
color={MainColor.black}
/>
}
size="xl"
/>
<ActionIcon
disabled={listSubBidangSelected.length <= 1}
onPress={() => {
handleRemoveSubBidang(listSubBidangSelected.length - 1);
}}
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} />
<PhoneInputCustom
value={phoneNumber}
onChangePhoneNumber={handlePhoneChange}
selectedCountry={selectedCountry}
onChangeCountry={handleCountryChange}
placeholder="xxx-xxx-xxx"
/>
</View>
<Spacing />
<TextInputCustom
required
label="Alamat Bisnis"
placeholder="Masukkan alamat bisnis"
value={data.alamatKantor}
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 />
</StackCustom>
)}
</OS_Wrapper>
</>
);
}

View File

@@ -4,14 +4,15 @@ import {
DrawerCustom,
DummyLandscapeImage,
LoaderCustom,
OS_Wrapper,
Spacing,
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";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { useAuth } from "@/hooks/use-auth";
@@ -72,23 +73,26 @@ 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>
<OS_Wrapper>
{!data || !profileId ? (
<StackCustom>
<CustomSkeleton height={400} />
@@ -121,7 +125,7 @@ export default function Portofolio() {
<Spacing />
</StackCustom>
)}
</ViewWrapper>
</OS_Wrapper>
{/* Drawer Komponen Eksternal */}
<DrawerCustom

View File

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

View File

@@ -3,13 +3,13 @@ import {
BadgeCustom,
ClickableCustom,
Divider,
OS_Wrapper,
SelectCustom,
TextCustom,
} from "@/components";
import ListEmptyComponent from "@/components/_ShareComponent/ListEmptyComponent";
import ListLoaderFooterComponent from "@/components/_ShareComponent/ListLoaderFooterComponent";
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
import { MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import { usePaginatedApi } from "@/hooks/use-paginated-api";
@@ -120,7 +120,7 @@ export default function ProfileBlockedList() {
return (
<>
<NewWrapper
<OS_Wrapper
// headerComponent={renderHeader()}
listData={listData}
renderItem={renderItem}

View File

@@ -5,7 +5,7 @@ import {
BoxButtonOnFooter,
BoxWithHeaderSection,
ButtonCustom,
NewWrapper,
OS_Wrapper,
StackCustom,
TextCustom,
} from "@/components";
@@ -46,7 +46,7 @@ export default function ProfileDetailBlocked() {
return (
<>
<NewWrapper
<OS_Wrapper
footerComponent={
<BoxButtonOnFooter>
<ButtonCustom
@@ -86,7 +86,7 @@ export default function ProfileDetailBlocked() {
</TextCustom>
</StackCustom>
</BoxWithHeaderSection>
</NewWrapper>
</OS_Wrapper>
</>
);
}

View File

@@ -1,11 +1,12 @@
import {
ButtonCustom,
OS_Wrapper,
SelectCustom,
StackCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import BoxButtonOnFooter from "@/components/Box/BoxButtonOnFooter";
import { PADDING_INLINE } from "@/constants/constans-value";
import { apiProfile, apiUpdateProfile } from "@/service/api-client/api-profile";
import { IProfile } from "@/types/Type-Profile";
import { router, useLocalSearchParams } from "expo-router";
@@ -70,7 +71,9 @@ export default function ProfileEdit() {
};
return (
<ViewWrapper
<OS_Wrapper
enableKeyboardHandling
contentPaddingBottom={250}
footerComponent={
<BoxButtonOnFooter>
<ButtonCustom isLoading={isLoading} onPress={handleUpdate}>
@@ -119,6 +122,6 @@ export default function ProfileEdit() {
}}
/>
</StackCustom>
</ViewWrapper>
</OS_Wrapper>
);
}

View File

@@ -1,5 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { NewWrapper, StackCustom } from "@/components";
import { OS_Wrapper, 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,22 +102,24 @@ 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 */}
<NewWrapper
<OS_Wrapper
refreshControl={
<RefreshControl
refreshing={refreshing}
@@ -141,7 +144,7 @@ export default function Profile() {
/>
</>
)}
</NewWrapper>
</OS_Wrapper>
{/* Drawer Komponen Eksternal */}
<DrawerCustom

View File

@@ -3,8 +3,8 @@ import {
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
OS_Wrapper,
} from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import API_STRORAGE from "@/constants/base-url-api-strorage";
import DIRECTORY_ID from "@/constants/directory-id";
import DUMMY_IMAGE from "@/constants/dummy-image-value";
@@ -127,7 +127,7 @@ export default function UpdateBackgroundProfile() {
);
return (
<ViewWrapper footerComponent={buttonFooter}>
<OS_Wrapper footerComponent={buttonFooter}>
<BaseBox
style={{ alignItems: "center", justifyContent: "center", height: 250 }}
>
@@ -144,6 +144,6 @@ export default function UpdateBackgroundProfile() {
>
Update
</ButtonCenteredOnly>
</ViewWrapper>
</OS_Wrapper>
);
}

View File

@@ -3,8 +3,8 @@ import {
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
OS_Wrapper,
} from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import API_STRORAGE from "@/constants/base-url-api-strorage";
import DIRECTORY_ID from "@/constants/directory-id";
import DUMMY_IMAGE from "@/constants/dummy-image-value";
@@ -125,7 +125,7 @@ export default function UpdatePhotoProfile() {
);
return (
<ViewWrapper footerComponent={buttonFooter}>
<OS_Wrapper footerComponent={buttonFooter}>
<BaseBox
style={{ alignItems: "center", justifyContent: "center", height: 250 }}
>
@@ -143,6 +143,6 @@ export default function UpdatePhotoProfile() {
>
Upload
</ButtonCenteredOnly>
</ViewWrapper>
</OS_Wrapper>
);
}

View File

@@ -1,47 +1,43 @@
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={{
header: () => (
<AppHeader title="Tambah Profil" showBack={false} />
),
}}
/>
<Stack.Screen
name="[id]/blocked-list"
options={{ title: "Daftar Blokir", headerLeft: () => <BackButton /> }}
options={{ header: () => <AppHeader title="Daftar Blokir" /> }}
/>
<Stack.Screen
name="[id]/detail-blocked"
options={{ title: "Detail Blokir", headerLeft: () => <BackButton /> }}
options={{ header: () => <AppHeader title="Detail Blokir" /> }}
/>
</Stack>
</>

View File

@@ -2,16 +2,17 @@ import {
BaseBox,
ButtonCenteredOnly,
ButtonCustom,
OS_Wrapper,
SelectCustom,
Spacing,
StackCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import BoxButtonOnFooter from "@/components/Box/BoxButtonOnFooter";
import InformationBox from "@/components/Box/InformationBox";
import DIRECTORY_ID from "@/constants/directory-id";
import DUMMY_IMAGE from "@/constants/dummy-image-value";
import { PADDING_INLINE } from "@/constants/constans-value";
import { useAuth } from "@/hooks/use-auth";
import { apiCreateProfile } from "@/service/api-client/api-profile";
import { apiValidationEmail } from "@/service/api-client/api-validation";
@@ -155,7 +156,11 @@ export default function CreateProfile() {
);
return (
<ViewWrapper footerComponent={footerComponent}>
<OS_Wrapper
enableKeyboardHandling
contentPaddingBottom={250}
footerComponent={footerComponent}
>
<StackCustom>
<InformationBox text="Upload foto profile anda." />
<View style={{ alignItems: "center" }}>
@@ -241,6 +246,6 @@ export default function CreateProfile() {
/>
<Spacing />
</StackCustom>
</ViewWrapper>
</OS_Wrapper>
);
}

View File

@@ -4,64 +4,87 @@ import {
IconHome,
IconStatus,
} from "@/components/_Icon";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import BackButtonFromNotification from "@/components/Button/BackButtonFromNotification";
import { OS_ANDROID_HEIGHT, OS_IOS_HEIGHT } from "@/constants/constans-value";
import { TabsStyles } from "@/styles/tabs-styles";
import { Tabs, useLocalSearchParams, useNavigation, router } from "expo-router";
import { useLayoutEffect } from "react";
export default function VotingTabsLayout() {
const navigation = useNavigation();
import { router, Tabs, useLocalSearchParams } from "expo-router";
import { View } from "react-native";
import { MainColor } from "@/constants/color-palet";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { Platform } from "react-native";
function VotingTabsWrapper() {
const insets = useSafeAreaInsets();
const paddingBottom = Platform.OS === "android" ? insets.bottom : 0;
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.Screen
name="index"
options={{
title: "Beranda",
tabBarIcon: ({ color }) => <IconHome color={color} />,
<View style={{ flex: 1, backgroundColor: MainColor.darkblue }}>
<Tabs
screenOptions={{
...TabsStyles,
tabBarStyle: Platform.select({
ios: {
borderTopWidth: 0,
paddingTop: 12,
height: OS_IOS_HEIGHT,
},
android: {
borderTopWidth: 0,
paddingTop: 5,
height: OS_ANDROID_HEIGHT + paddingBottom,
},
}),
header: () => (
<AppHeader
title="Voting"
left={
<BackButtonFromNotification
from={from || ""}
category={category}
/>
}
/>
),
}}
/>
<Tabs.Screen
name="status"
options={{
title: "Status",
tabBarIcon: ({ color }) => <IconStatus color={color} />,
}}
/>
<Tabs.Screen
name="contribution"
options={{
title: "Kontribusi",
tabBarIcon: ({ color }) => <IconContribution color={color} />,
}}
/>
<Tabs.Screen
name="history"
options={{
title: "Riwayat",
tabBarIcon: ({ color }) => <IconHistory color={color} />,
}}
/>
</Tabs>
>
<Tabs.Screen
name="index"
options={{
title: "Beranda",
tabBarIcon: ({ color }) => <IconHome color={color} />,
}}
/>
<Tabs.Screen
name="status"
options={{
title: "Status",
tabBarIcon: ({ color }) => <IconStatus color={color} />,
}}
/>
<Tabs.Screen
name="contribution"
options={{
title: "Kontribusi",
tabBarIcon: ({ color }) => <IconContribution color={color} />,
}}
/>
<Tabs.Screen
name="history"
options={{
title: "Riwayat",
tabBarIcon: ({ color }) => <IconHistory color={color} />,
}}
/>
</Tabs>
</View>
);
}
export default function VotingTabsLayout() {
return <VotingTabsWrapper />;
}

View File

@@ -7,11 +7,12 @@ import {
DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid,
OS_Wrapper,
Spacing,
StackCustom,
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,17 +104,22 @@ 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>
<OS_Wrapper>
{loadingGetData ? (
<LoaderCustom />
) : (
@@ -152,7 +158,7 @@ export default function VotingDetailStatus() {
<Spacing />
</>
)}
</ViewWrapper>
</OS_Wrapper>
{/* ========= Draft Drawer ========= */}
<DrawerCustom

View File

@@ -6,9 +6,10 @@ import {
DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid,
OS_Wrapper,
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,15 +82,19 @@ 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)} />
}
/>
),
}}
/>
<ViewWrapper>
<OS_Wrapper>
{loadingGetData ? (
<LoaderCustom />
) : (
@@ -111,7 +116,7 @@ export default function VotingDetailContribution() {
<Spacing />
</>
)}
</ViewWrapper>
</OS_Wrapper>
{/* ========= Publish Drawer ========= */}
<DrawerCustom

View File

@@ -5,7 +5,7 @@ import {
ButtonCustom,
CenterCustom,
LoaderCustom,
NewWrapper,
OS_Wrapper,
Spacing,
StackCustom,
TextAreaCustom,
@@ -189,7 +189,11 @@ export default function VotingEdit() {
};
return (
<NewWrapper footerComponent={buttonSubmit()}>
<OS_Wrapper
enableKeyboardHandling
contentPaddingBottom={250}
footerComponent={buttonSubmit()}
>
{loadingGetData ? (
<ListSkeletonComponent />
) : (
@@ -328,6 +332,6 @@ export default function VotingEdit() {
<Spacing />
</StackCustom>
)}
</NewWrapper>
</OS_Wrapper>
);
}

View File

@@ -6,9 +6,10 @@ import {
DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid,
OS_Wrapper,
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,14 +83,18 @@ 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)} />
}
/>
),
}}
/>
<ViewWrapper>
<OS_Wrapper>
{loadingGetData ? (
<LoaderCustom />
) : (
@@ -111,7 +116,7 @@ export default function VotingDetailHistory() {
<Spacing />
</>
)}
</ViewWrapper>
</OS_Wrapper>
{/* ========= Publish Drawer ========= */}
<DrawerCustom

View File

@@ -8,9 +8,10 @@ import {
InformationBox,
LoaderCustom,
MenuDrawerDynamicGrid,
OS_Wrapper,
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";
@@ -31,6 +32,7 @@ import {
useLocalSearchParams,
} from "expo-router";
import React, { useCallback, useEffect, useState } from "react";
import { RefreshControl } from "react-native";
import Toast from "react-native-toast-message";
export default function VotingDetail() {
@@ -132,9 +134,9 @@ export default function VotingDetail() {
if (isEventFinished) {
return (
<ViewWrapper>
<OS_Wrapper>
<CustomSkeleton />
</ViewWrapper>
</OS_Wrapper>
);
}
@@ -142,15 +144,26 @@ 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)} />
}
/>
),
}}
/>
<ViewWrapper>
<OS_Wrapper
refreshControl={
<RefreshControl
refreshing={loadingGetData}
onRefresh={handlerLoadData}
/>
}
>
{loadingGetData ? (
<LoaderCustom />
) : (
@@ -177,7 +190,7 @@ export default function VotingDetail() {
/>
</StackCustom>
)}
</ViewWrapper>
</OS_Wrapper>
{/* ========= Publish Drawer ========= */}
<DrawerCustom

View File

@@ -3,12 +3,11 @@ import {
BoxButtonOnFooter,
ButtonCustom,
CenterCustom,
NewWrapper,
OS_Wrapper,
Spacing,
StackCustom,
TextAreaCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom";
import { MainColor } from "@/constants/color-palet";
@@ -107,7 +106,11 @@ export default function VotingCreate() {
};
return (
<NewWrapper footerComponent={buttonSubmit()}>
<OS_Wrapper
enableKeyboardHandling
contentPaddingBottom={250}
footerComponent={buttonSubmit()}
>
<StackCustom gap={"xs"}>
<TextInputCustom
label="Judul Voting"
@@ -198,6 +201,6 @@ export default function VotingCreate() {
<Spacing />
</StackCustom>
</NewWrapper>
</OS_Wrapper>
);
}

View File

@@ -3,7 +3,7 @@ import {
BoxButtonOnFooter,
ButtonCustom,
InformationBox,
NewWrapper,
OS_Wrapper,
StackCustom
} from "@/components";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
@@ -82,7 +82,7 @@ export default function WaitingRoom() {
return (
<>
<NewWrapper
<OS_Wrapper
footerComponent={logoutButton()}
refreshControl={
<RefreshControl refreshing={isLoading} onRefresh={handleCheck} />
@@ -103,7 +103,7 @@ Silakan tunggu beberapa saat. Untuk memperbarui status, tarik layar ke bawah."
Check
</ButtonCenteredOnly> */}
</StackCustom>
</NewWrapper>
</OS_Wrapper>
</>
);
}

View File

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

Some files were not shown because too many files have changed in this diff Show More