Compare commits
135 Commits
api-collab
...
fixed-admi
| Author | SHA1 | Date | |
|---|---|---|---|
| fb697366fe | |||
| 6d71c3a86f | |||
| e030b8f486 | |||
| 5c931b069c | |||
| b2be7be533 | |||
| 2705f96b01 | |||
| 38a6b424e8 | |||
| 83fa277e03 | |||
| c570a19d84 | |||
| 7415c8c8ce | |||
| 72a3d42013 | |||
| d0abd14047 | |||
| 5b2be20469 | |||
| 60177a1087 | |||
| 771ae45f26 | |||
| 41f4a8ac99 | |||
| 48196cd46b | |||
| ec79a1fbcd | |||
| ed16f1b204 | |||
| d693550a1f | |||
| b3bfbc0f7e | |||
| 71e45d06cc | |||
| 07e64c335e | |||
| 1aebc9b4e8 | |||
| 5665dc88ba | |||
| da82a02a45 | |||
| 14c0f0e499 | |||
| 0262423c50 | |||
| c2682246d6 | |||
| 465e01015e | |||
| 3b15871ad4 | |||
| 9123e73606 | |||
| 6e2046467f | |||
| ca33dd83bb | |||
| ea3fbdc541 | |||
| 33cd47aaed | |||
| 57ac1eb45e | |||
| 145ad73616 | |||
| 7c85e35c61 | |||
| d098b8ca16 | |||
| 73a473cdc7 | |||
| 3f85f330d2 | |||
| 7743a2467c | |||
| 54611ef812 | |||
| 1503707eed | |||
| a01a9bd93f | |||
| 05c1cac10f | |||
| d27c01ed56 | |||
| 34680a4c38 | |||
| 43c8c105cf | |||
| 2c0198b1b7 | |||
| 573b525352 | |||
| 6f9481c7c9 | |||
| cccb44a835 | |||
| 0f5862ce70 | |||
| 624bd49f69 | |||
| 2446e9d51a | |||
| ab5733f336 | |||
| f5e30087ed | |||
| a93f97ed6a | |||
| 858b441a8c | |||
| 98aaa126a1 | |||
| 69452ff4e7 | |||
| 33ec892ec8 | |||
| 8a900e9469 | |||
| d471682ae7 | |||
| 00eea71248 | |||
| 41e648d8f3 | |||
| 0c4deac6e2 | |||
| 676b8a38be | |||
| 0a2aa71013 | |||
| 868e96a54a | |||
| 059b4d053a | |||
| 76debfd6a6 | |||
| 8c3aec8e57 | |||
| 1ade69ff2f | |||
| 97ea6ab799 | |||
| 4e9ce07759 | |||
| 8f659c2b7e | |||
| 61bca7cfe1 | |||
| 5af85c3a8b | |||
| a8807d88ad | |||
| 5d36429aa4 | |||
| ec49999f99 | |||
| 867e82c6fa | |||
| f9f996f195 | |||
| 98394309e6 | |||
| 4625831377 | |||
| ebd6107c36 | |||
| f23cfe1107 | |||
| f9d9b5fbaa | |||
| b3209dc7ee | |||
| 31c1b35173 | |||
| 1e1b18f860 | |||
| de0280367f | |||
| 5d4328a139 | |||
| c8b14b816f | |||
| 125bf16605 | |||
| 73a803f2e8 | |||
| 1bcd1a044f | |||
| 1e0b72de22 | |||
| 36dbfa3296 | |||
| 966e55597c | |||
| 4da55a5a8a | |||
| faf0f36e53 | |||
| 57285e5697 | |||
| 1fd9694ebf | |||
| d31df8c390 | |||
| 90cfb042d8 | |||
| b9f93ff46a | |||
| 0770237fe5 | |||
| 6f4dd79568 | |||
| 9faa0b0f64 | |||
| e05a7c8701 | |||
| f50c5099d8 | |||
| 5f36620988 | |||
| f750d158be | |||
| 0e7b29bb15 | |||
| b293310969 | |||
| a980397640 | |||
| 7c82e8b588 | |||
| 53cdca21fc | |||
| ba878d4d08 | |||
| f3a3acc747 | |||
| a6389174d7 | |||
| 2be4afdcb1 | |||
| aa85e05f79 | |||
| c2acb97a37 | |||
| 250b216a54 | |||
| 5f05d1f7f0 | |||
| 3d8d8568a3 | |||
| ccdd7730b2 | |||
| a474aebb94 | |||
| 29b65aeebf | |||
| 18beb09b42 |
42
.gitignore
vendored
@@ -40,3 +40,45 @@ app-example
|
||||
.qodo
|
||||
|
||||
.env
|
||||
|
||||
# @generated expo-cli sync-8d4afeec25ea8a192358fae2f8e2fc766bdce4ec
|
||||
# The following patterns were generated by expo-cli
|
||||
|
||||
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# Expo
|
||||
.expo/
|
||||
dist/
|
||||
web-build/
|
||||
expo-env.d.ts
|
||||
|
||||
# Native
|
||||
*.orig.*
|
||||
*.jks
|
||||
*.p8
|
||||
*.p12
|
||||
*.key
|
||||
*.mobileprovision
|
||||
|
||||
# Metro
|
||||
.metro-health-check*
|
||||
|
||||
# debug
|
||||
npm-debug.*
|
||||
yarn-debug.*
|
||||
yarn-error.*
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
|
||||
# @end expo-cli
|
||||
169
QWEN.md
Normal file
@@ -0,0 +1,169 @@
|
||||
# HIPMI Mobile Application - Development Context
|
||||
|
||||
## Project Overview
|
||||
|
||||
HIPMI Mobile is a cross-platform mobile application built with Expo and React Native. The application is named "HIPMI Badung Connect" and serves as a platform for the HIPMI (Himpunan Pengusaha dan Pengusaha Indonesia) Badung chapter. It's designed to run on iOS, Android, and web platforms using a single codebase.
|
||||
|
||||
### Key Technologies
|
||||
- **Framework**: Expo (v54.0.0) with React Native (v0.81.4)
|
||||
- **Language**: TypeScript
|
||||
- **Architecture**: File-based routing with Expo Router
|
||||
- **State Management**: Context API
|
||||
- **UI Components**: React Native Paper, custom components
|
||||
- **Maps Integration**: Mapbox Maps for React Native
|
||||
- **Push Notifications**: React Native Firebase Messaging
|
||||
- **Build System**: Metro bundler
|
||||
|
||||
### Project Structure
|
||||
```
|
||||
hipmi-mobile/
|
||||
├── app/ # Main application screens and routing
|
||||
│ ├── _layout.tsx # Root layout component
|
||||
│ ├── index.tsx # Entry point (Login screen)
|
||||
│ └── ...
|
||||
├── components/ # Reusable UI components
|
||||
├── context/ # State management (AuthContext)
|
||||
├── screens/ # Screen components organized by feature
|
||||
│ ├── Admin/ # Admin panel screens
|
||||
│ ├── Authentication/ # Login, registration flows
|
||||
│ ├── Collaboration/ # Collaboration features
|
||||
│ ├── Event/ # Event management
|
||||
│ ├── Forum/ # Forum functionality
|
||||
│ ├── Home/ # Home screen
|
||||
│ ├── Maps/ # Map integration
|
||||
│ ├── Profile/ # User profile
|
||||
│ └── ...
|
||||
├── assets/ # Images, icons, and static assets
|
||||
├── constants/ # Constants and configuration values
|
||||
├── hooks/ # Custom React hooks
|
||||
├── lib/ # Utility libraries
|
||||
├── navigation/ # Navigation configuration
|
||||
├── service/ # API services and business logic
|
||||
├── types/ # TypeScript type definitions
|
||||
└── utils/ # Helper functions
|
||||
```
|
||||
|
||||
## Building and Running
|
||||
|
||||
### Prerequisites
|
||||
- Node.js (with bun as the package manager)
|
||||
- Expo CLI
|
||||
- iOS Simulator or Android Emulator (for native builds)
|
||||
|
||||
### Setup and Development
|
||||
|
||||
1. **Install Dependencies**
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
|
||||
2. **Run Development Server**
|
||||
```bash
|
||||
bun run start
|
||||
```
|
||||
Or use the shorthand:
|
||||
```bash
|
||||
bunx expo start
|
||||
```
|
||||
|
||||
3. **Platform-Specific Commands**
|
||||
- iOS: `bun run ios` or `bunx expo start --ios`
|
||||
- Android: `bun run android` or `bunx expo start --android`
|
||||
- Web: `bun run web` or `bunx expo start --web`
|
||||
|
||||
4. **Linting**
|
||||
```bash
|
||||
bun run lint
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
The application uses environment variables defined in the app.config.js file:
|
||||
- `API_BASE_URL`: Base URL for API endpoints
|
||||
- `BASE_URL`: Base application URL
|
||||
- `DEEP_LINK_URL`: URL for deep linking functionality
|
||||
|
||||
### EAS Build Configuration
|
||||
The project uses Expo Application Services (EAS) for building and deploying:
|
||||
- Development builds with development client
|
||||
- Preview builds for internal distribution
|
||||
- Production builds for app stores
|
||||
|
||||
## Features and Functionality
|
||||
|
||||
The application appears to include several key modules:
|
||||
- **Authentication**: Login, registration, and verification flows
|
||||
- **Admin Panel**: Administrative functions
|
||||
- **Collaboration**: Tools for member collaboration
|
||||
- **Events**: Event management and calendar
|
||||
- **Forum**: Discussion forums
|
||||
- **Maps**: Location-based services with Mapbox integration
|
||||
- **Donations**: Donation functionality
|
||||
- **Job Board**: Employment opportunities
|
||||
- **Investment**: Investment-related features
|
||||
- **Voting**: Voting systems
|
||||
- **Portfolio**: Member portfolio showcase
|
||||
- **Notifications**: Push notifications via Firebase
|
||||
|
||||
## Development Conventions
|
||||
|
||||
### Coding Standards
|
||||
- TypeScript is used throughout the project for type safety
|
||||
- Component-based architecture with reusable components
|
||||
- Context API for state management
|
||||
- File-based routing with Expo Router
|
||||
- Consistent naming conventions using camelCase for variables and PascalCase for components
|
||||
|
||||
### Testing
|
||||
- Linting is configured with ESLint
|
||||
- Standard Expo linting configuration is used
|
||||
|
||||
### Security
|
||||
- Firebase is integrated for authentication and messaging
|
||||
- Camera and location permissions are properly configured
|
||||
- Deep linking is secured with app domain associations
|
||||
|
||||
## Key Dependencies
|
||||
|
||||
### Core Dependencies
|
||||
- `@react-navigation/*`: Navigation solution for React Native
|
||||
- `@react-native-firebase/*`: Firebase integration for React Native
|
||||
- `@rnmapbox/maps`: Mapbox integration for React Native
|
||||
- `expo-router`: File-based routing for Expo applications
|
||||
- `react-native-paper`: Material Design components for React Native
|
||||
- `react-native-toast-message`: Toast notifications
|
||||
- `react-native-otp-entry`: OTP input components
|
||||
- `react-native-qrcode-svg`: QR code generation
|
||||
|
||||
### Development Dependencies
|
||||
- `@types/*`: TypeScript type definitions
|
||||
- `eslint-config-expo`: Expo-specific ESLint configuration
|
||||
- `typescript`: Type checking
|
||||
|
||||
## Platform Support
|
||||
|
||||
The application is configured to support:
|
||||
- **iOS**: With tablet support and proper permissions
|
||||
- **Android**: With adaptive icons and intent filters for deep linking
|
||||
- **Web**: Static output configuration for web deployment
|
||||
|
||||
## Special Configurations
|
||||
|
||||
### iOS Configuration
|
||||
- Bundle identifier: `com.anonymous.hipmi-mobile`
|
||||
- Supports tablets
|
||||
- Google Services integration
|
||||
- Location permission handling
|
||||
- Associated domains for deep linking
|
||||
|
||||
### Android Configuration
|
||||
- Package name: `com.bip.hipmimobileapp`
|
||||
- Adaptive icons
|
||||
- Edge-to-edge display enabled
|
||||
- Intent filters for HTTPS deep linking
|
||||
- Google Services integration
|
||||
|
||||
### Maps Integration
|
||||
The application uses Mapbox for mapping functionality with the `@rnmapbox/maps` plugin.
|
||||
|
||||
### Push Notifications
|
||||
Firebase Cloud Messaging is integrated for push notifications with proper configuration for both iOS and Android platforms.
|
||||
@@ -64,9 +64,9 @@ react {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this to true to Run Proguard on Release builds to minify the Java bytecode.
|
||||
* Set this to true in release builds to optimize the app using [R8](https://developer.android.com/topic/performance/app-optimization/enable-app-optimization).
|
||||
*/
|
||||
def enableProguardInReleaseBuilds = (findProperty('android.enableProguardInReleaseBuilds') ?: false).toBoolean()
|
||||
def enableMinifyInReleaseBuilds = (findProperty('android.enableMinifyInReleaseBuilds') ?: false).toBoolean()
|
||||
|
||||
/**
|
||||
* The preferred build flavor of JavaScriptCore (JSC)
|
||||
@@ -82,6 +82,14 @@ def enableProguardInReleaseBuilds = (findProperty('android.enableProguardInRelea
|
||||
def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'
|
||||
|
||||
android {
|
||||
// @generated begin @rnmapbox/maps-libcpp - expo prebuild (DO NOT MODIFY) sync-e24830a5a3e854b398227dfe9630aabfaa1cadd1
|
||||
packagingOptions {
|
||||
pickFirst 'lib/x86/libc++_shared.so'
|
||||
pickFirst 'lib/x86_64/libc++_shared.so'
|
||||
pickFirst 'lib/arm64-v8a/libc++_shared.so'
|
||||
pickFirst 'lib/armeabi-v7a/libc++_shared.so'
|
||||
}
|
||||
// @generated end @rnmapbox/maps-libcpp
|
||||
ndkVersion rootProject.ext.ndkVersion
|
||||
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
@@ -92,8 +100,10 @@ android {
|
||||
applicationId 'com.bip.hipmimobileapp'
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "1.0.0"
|
||||
versionCode 4
|
||||
versionName "1.0.1"
|
||||
|
||||
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
|
||||
}
|
||||
signingConfigs {
|
||||
debug {
|
||||
@@ -111,15 +121,18 @@ android {
|
||||
// Caution! In production, you need to generate your own keystore file.
|
||||
// see https://reactnative.dev/docs/signed-apk-android.
|
||||
signingConfig signingConfigs.debug
|
||||
shrinkResources (findProperty('android.enableShrinkResourcesInReleaseBuilds')?.toBoolean() ?: false)
|
||||
minifyEnabled enableProguardInReleaseBuilds
|
||||
def enableShrinkResources = findProperty('android.enableShrinkResourcesInReleaseBuilds') ?: 'false'
|
||||
shrinkResources enableShrinkResources.toBoolean()
|
||||
minifyEnabled enableMinifyInReleaseBuilds
|
||||
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
||||
crunchPngs (findProperty('android.enablePngCrunchInReleaseBuilds')?.toBoolean() ?: true)
|
||||
def enablePngCrunchInRelease = findProperty('android.enablePngCrunchInReleaseBuilds') ?: 'true'
|
||||
crunchPngs enablePngCrunchInRelease.toBoolean()
|
||||
}
|
||||
}
|
||||
packagingOptions {
|
||||
jniLibs {
|
||||
useLegacyPackaging (findProperty('expo.useLegacyPackaging')?.toBoolean() ?: false)
|
||||
def enableLegacyPackaging = findProperty('expo.useLegacyPackaging') ?: 'false'
|
||||
useLegacyPackaging enableLegacyPackaging.toBoolean()
|
||||
}
|
||||
}
|
||||
androidResources {
|
||||
@@ -175,3 +188,5 @@ dependencies {
|
||||
implementation jscFlavor
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
29
android/app/google-services.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"project_info": {
|
||||
"project_number": "608461535079",
|
||||
"project_id": "hipmi-badung-connect",
|
||||
"storage_bucket": "hipmi-badung-connect.firebasestorage.app"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:608461535079:android:4ff12ddc283fb3746761c2",
|
||||
"android_client_info": {
|
||||
"package_name": "com.bip.hipmimobileapp"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyBiDtIk3Q9zffFwIdJ5cjqY7e4390JGSkM"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": []
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.CAMERA"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
@@ -13,7 +15,11 @@
|
||||
<data android:scheme="https"/>
|
||||
</intent>
|
||||
</queries>
|
||||
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme" android:supportsRtl="true">
|
||||
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme" android:supportsRtl="true" android:enableOnBackInvokedCallback="false" android:fullBackupContent="@xml/secure_store_backup_rules" android:dataExtractionRules="@xml/secure_store_data_extraction_rules">
|
||||
<meta-data android:name="com.google.firebase.messaging.default_notification_color" android:resource="@color/notification_icon_color" tools:replace="android:resource"/>
|
||||
<meta-data android:name="com.google.firebase.messaging.default_notification_icon" android:resource="@drawable/notification_icon"/>
|
||||
<meta-data android:name="expo.modules.notifications.default_notification_color" android:resource="@color/notification_icon_color"/>
|
||||
<meta-data android:name="expo.modules.notifications.default_notification_icon" android:resource="@drawable/notification_icon"/>
|
||||
<meta-data android:name="expo.modules.updates.ENABLED" android:value="false"/>
|
||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS"/>
|
||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>
|
||||
@@ -29,6 +35,12 @@
|
||||
<data android:scheme="hipmimobile"/>
|
||||
<data android:scheme="exp+hipmi-mobile"/>
|
||||
</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="/"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -5,13 +5,13 @@ import android.content.res.Configuration
|
||||
|
||||
import com.facebook.react.PackageList
|
||||
import com.facebook.react.ReactApplication
|
||||
import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
|
||||
import com.facebook.react.ReactNativeHost
|
||||
import com.facebook.react.ReactPackage
|
||||
import com.facebook.react.ReactHost
|
||||
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
|
||||
import com.facebook.react.common.ReleaseLevel
|
||||
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint
|
||||
import com.facebook.react.defaults.DefaultReactNativeHost
|
||||
import com.facebook.react.soloader.OpenSourceMergedSoMapping
|
||||
import com.facebook.soloader.SoLoader
|
||||
|
||||
import expo.modules.ApplicationLifecycleDispatcher
|
||||
import expo.modules.ReactNativeHostWrapper
|
||||
@@ -19,21 +19,19 @@ import expo.modules.ReactNativeHostWrapper
|
||||
class MainApplication : Application(), ReactApplication {
|
||||
|
||||
override val reactNativeHost: ReactNativeHost = ReactNativeHostWrapper(
|
||||
this,
|
||||
object : DefaultReactNativeHost(this) {
|
||||
override fun getPackages(): List<ReactPackage> {
|
||||
val packages = PackageList(this).packages
|
||||
// Packages that cannot be autolinked yet can be added manually here, for example:
|
||||
// packages.add(MyReactNativePackage())
|
||||
return packages
|
||||
}
|
||||
this,
|
||||
object : DefaultReactNativeHost(this) {
|
||||
override fun getPackages(): List<ReactPackage> =
|
||||
PackageList(this).packages.apply {
|
||||
// Packages that cannot be autolinked yet can be added manually here, for example:
|
||||
// add(MyReactNativePackage())
|
||||
}
|
||||
|
||||
override fun getJSMainModuleName(): String = ".expo/.virtual-metro-entry"
|
||||
|
||||
override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
|
||||
|
||||
override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
|
||||
override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
|
||||
}
|
||||
)
|
||||
|
||||
@@ -42,11 +40,12 @@ class MainApplication : Application(), ReactApplication {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
SoLoader.init(this, OpenSourceMergedSoMapping)
|
||||
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
||||
// If you opted-in for the New Architecture, we load the native entry point for this app.
|
||||
load()
|
||||
DefaultNewArchitectureEntryPoint.releaseLevel = try {
|
||||
ReleaseLevel.valueOf(BuildConfig.REACT_NATIVE_RELEASE_LEVEL.uppercase())
|
||||
} catch (e: IllegalArgumentException) {
|
||||
ReleaseLevel.STABLE
|
||||
}
|
||||
loadReactNative(this)
|
||||
ApplicationLifecycleDispatcher.onApplicationCreate(this)
|
||||
}
|
||||
|
||||
|
||||
BIN
android/app/src/main/res/drawable-hdpi/notification_icon.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 55 KiB |
BIN
android/app/src/main/res/drawable-mdpi/notification_icon.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 30 KiB |
BIN
android/app/src/main/res/drawable-xhdpi/notification_icon.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 84 KiB |
BIN
android/app/src/main/res/drawable-xxhdpi/notification_icon.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 154 KiB |
BIN
android/app/src/main/res/drawable-xxxhdpi/notification_icon.png
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 231 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 30 KiB |
@@ -3,4 +3,5 @@
|
||||
<color name="iconBackground">#ffffff</color>
|
||||
<color name="colorPrimary">#023c69</color>
|
||||
<color name="colorPrimaryDark">#ffffff</color>
|
||||
<color name="notification_icon_color">#ffffff</color>
|
||||
</resources>
|
||||
@@ -1,5 +1,5 @@
|
||||
<resources>
|
||||
<string name="app_name">HIPMI BADUNG</string>
|
||||
<string name="app_name">HIPMI Badung Connect</string>
|
||||
<string name="expo_system_ui_user_interface_style" translatable="false">automatic</string>
|
||||
<string name="expo_splash_screen_resize_mode" translatable="false">contain</string>
|
||||
<string name="expo_splash_screen_status_bar_translucent" translatable="false">false</string>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<style name="AppTheme" parent="Theme.EdgeToEdge">
|
||||
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||
<item name="android:enforceNavigationBarContrast" tools:targetApi="29">true</item>
|
||||
<item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="android:statusBarColor">#ffffff</item>
|
||||
@@ -8,5 +9,6 @@
|
||||
<item name="windowSplashScreenBackground">@color/splashscreen_background</item>
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/splashscreen_logo</item>
|
||||
<item name="postSplashScreenTheme">@style/AppTheme</item>
|
||||
<item name="android:windowSplashScreenBehavior">icon_preferred</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<full-backup-content>
|
||||
<exclude domain="sharedpref" path="SECURESTORE"/>
|
||||
</full-backup-content>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<data-extraction-rules>
|
||||
<cloud-backup>
|
||||
<exclude domain="sharedpref" path="SECURESTORE"/>
|
||||
</cloud-backup>
|
||||
<device-transfer>
|
||||
<exclude domain="sharedpref" path="SECURESTORE"/>
|
||||
</device-transfer>
|
||||
</data-extraction-rules>
|
||||
@@ -6,27 +6,15 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
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')
|
||||
}
|
||||
}
|
||||
|
||||
def reactNativeAndroidDir = new File(
|
||||
providers.exec {
|
||||
workingDir(rootDir)
|
||||
commandLine("node", "--print", "require.resolve('react-native/package.json')")
|
||||
}.standardOutput.asText.get().trim(),
|
||||
"../android"
|
||||
)
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
maven {
|
||||
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
|
||||
url(reactNativeAndroidDir)
|
||||
}
|
||||
|
||||
google()
|
||||
mavenCentral()
|
||||
maven { url 'https://www.jitpack.io' }
|
||||
@@ -35,3 +23,25 @@ allprojects {
|
||||
|
||||
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) }
|
||||
credentials {
|
||||
username = 'mapbox'
|
||||
password = token
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @generated end @rnmapbox/maps-v2-maven
|
||||
@@ -15,7 +15,7 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
org.gradle.parallel=true
|
||||
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app's APK
|
||||
@@ -41,6 +41,11 @@ newArchEnabled=true
|
||||
# If set to false, you will be using JSC instead.
|
||||
hermesEnabled=true
|
||||
|
||||
# Use this property to enable edge-to-edge display support.
|
||||
# This allows your app to draw behind system bars for an immersive UI.
|
||||
# Note: Only works with ReactActivity and should not be used with custom Activity.
|
||||
edgeToEdgeEnabled=true
|
||||
|
||||
# Enable GIF support in React Native images (~200 B increase)
|
||||
expo.gif.enabled=true
|
||||
# Enable webp support in React Native images (~85 KB increase)
|
||||
@@ -55,5 +60,6 @@ EX_DEV_CLIENT_NETWORK_INSPECTOR=true
|
||||
# Use legacy packaging to compress native libraries in the resulting APK.
|
||||
expo.useLegacyPackaging=false
|
||||
|
||||
# Whether the app is configured to use edge-to-edge via the app config or `react-native-edge-to-edge` plugin
|
||||
expo.edgeToEdgeEnabled=true
|
||||
# Specifies whether the app is configured to use edge-to-edge via the app config or plugin
|
||||
# WARNING: This property has been deprecated and will be removed in Expo SDK 55. Use `edgeToEdgeEnabled` or `react.edgeToEdgeEnabled` to determine whether the project is using edge-to-edge.
|
||||
expo.edgeToEdgeEnabled=true
|
||||
|
||||
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
4
android/gradlew
vendored
@@ -114,7 +114,7 @@ case "$( uname )" in #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
CLASSPATH="\\\"\\\""
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
@@ -213,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
|
||||
4
android/gradlew.bat
vendored
@@ -70,11 +70,11 @@ goto fail
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
set CLASSPATH=
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
|
||||
@@ -31,7 +31,7 @@ extensions.configure(com.facebook.react.ReactSettingsExtension) { ex ->
|
||||
}
|
||||
expoAutolinking.useExpoModules()
|
||||
|
||||
rootProject.name = 'HIPMI BADUNG'
|
||||
rootProject.name = 'HIPMI Badung Connect'
|
||||
|
||||
expoAutolinking.useExpoVersionCatalog()
|
||||
|
||||
|
||||
@@ -1,61 +1,92 @@
|
||||
// app.config.js
|
||||
require('dotenv').config();
|
||||
require("dotenv").config();
|
||||
|
||||
export default {
|
||||
name: 'HIPMI BADUNG',
|
||||
slug: 'hipmi-mobile',
|
||||
version: '1.0.0',
|
||||
orientation: 'portrait',
|
||||
icon: './assets/images/icon.png',
|
||||
scheme: 'hipmimobile',
|
||||
userInterfaceStyle: 'automatic',
|
||||
name: "HIPMI Badung Connect",
|
||||
slug: "hipmi-mobile",
|
||||
version: "1.0.1",
|
||||
orientation: "portrait",
|
||||
icon: "./assets/images/icon.png",
|
||||
scheme: "hipmimobile",
|
||||
userInterfaceStyle: "automatic",
|
||||
newArchEnabled: true,
|
||||
|
||||
ios: {
|
||||
supportsTablet: true,
|
||||
bundleIdentifier: 'com.anonymous.hipmi-mobile',
|
||||
bundleIdentifier: "com.anonymous.hipmi-mobile",
|
||||
googleServicesFile: "./ios/HIPMIBadungConnect/GoogleService-Info.plist",
|
||||
infoPlist: {
|
||||
ITSAppUsesNonExemptEncryption: false,
|
||||
NSLocationWhenInUseUsageDescription:
|
||||
"Aplikasi membutuhkan akses lokasi untuk menampilkan peta.",
|
||||
},
|
||||
associatedDomains: ["applinks:cld-dkr-staging-hipmi.wibudev.com"],
|
||||
buildNumber: "20",
|
||||
},
|
||||
|
||||
android: {
|
||||
googleServicesFile: "./google-services.json",
|
||||
adaptiveIcon: {
|
||||
foregroundImage: './assets/images/splash-icon.png',
|
||||
backgroundColor: '#ffffff',
|
||||
foregroundImage: "./assets/images/splash-icon.png",
|
||||
backgroundColor: "#ffffff",
|
||||
},
|
||||
edgeToEdgeEnabled: true,
|
||||
package: 'com.bip.hipmimobileapp',
|
||||
package: "com.bip.hipmimobileapp",
|
||||
versionCode: 4,
|
||||
// softwareKeyboardLayoutMode: 'resize', // option: untuk mengatur keyboard pada room chst collaboration
|
||||
intentFilters: [
|
||||
{
|
||||
action: "VIEW",
|
||||
autoVerify: true, // wajib untuk App Links
|
||||
data: [
|
||||
{
|
||||
scheme: "https",
|
||||
host: "cld-dkr-staging-hipmi.wibudev.com",
|
||||
pathPrefix: "/",
|
||||
},
|
||||
],
|
||||
category: ["BROWSABLE", "DEFAULT"],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
web: {
|
||||
bundler: 'metro',
|
||||
output: 'static',
|
||||
favicon: './assets/images/favicon.png',
|
||||
bundler: "metro",
|
||||
output: "static",
|
||||
favicon: "./assets/images/favicon.png",
|
||||
},
|
||||
|
||||
plugins: [
|
||||
'expo-router',
|
||||
'expo-web-browser',
|
||||
"expo-router",
|
||||
"expo-web-browser",
|
||||
[
|
||||
'expo-splash-screen',
|
||||
"expo-splash-screen",
|
||||
{
|
||||
image: './assets/images/splash-icon.png',
|
||||
image: "./assets/images/splash-icon.png",
|
||||
imageWidth: 200,
|
||||
resizeMode: 'contain',
|
||||
backgroundColor: '#ffffff',
|
||||
resizeMode: "contain",
|
||||
backgroundColor: "#ffffff",
|
||||
},
|
||||
],
|
||||
[
|
||||
'expo-camera',
|
||||
"expo-camera",
|
||||
{
|
||||
cameraPermission: 'Allow $(PRODUCT_NAME) to access your camera',
|
||||
microphonePermission: 'Allow $(PRODUCT_NAME) to access your microphone',
|
||||
cameraPermission: "Allow $(PRODUCT_NAME) to access your camera",
|
||||
microphonePermission: "Allow $(PRODUCT_NAME) to access your microphone",
|
||||
recordAudioAndroid: true,
|
||||
},
|
||||
],
|
||||
'expo-font',
|
||||
"expo-font",
|
||||
"@rnmapbox/maps",
|
||||
"@react-native-firebase/app",
|
||||
[
|
||||
"expo-notifications",
|
||||
{
|
||||
icon: "./assets/images/icon.png",
|
||||
color: "#ffffff",
|
||||
iosDisplayInForeground: true,
|
||||
},
|
||||
],
|
||||
],
|
||||
|
||||
experiments: {
|
||||
@@ -65,9 +96,11 @@ export default {
|
||||
extra: {
|
||||
router: {},
|
||||
eas: {
|
||||
projectId: '5cf15964-4889-4755-b8ed-b99c61d614d1',
|
||||
projectId: "5cf15964-4889-4755-b8ed-b99c61d614d1",
|
||||
},
|
||||
// Tambahkan environment variables ke sini
|
||||
API_BASE_URL: process.env.API_BASE_URL,
|
||||
BASE_URL: process.env.BASE_URL,
|
||||
DEEP_LINK_URL: process.env.DEEP_LINK_URL,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import { BackButton, ViewWrapper } from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { FontAwesome } from "@expo/vector-icons";
|
||||
import { Stack } from "expo-router";
|
||||
import { BackButton } from "@/components";
|
||||
import PdfViewer from "@/components/_ShareComponent/PdfViewer";
|
||||
import API_STRORAGE from "@/constants/base-url-api-strorage";
|
||||
import { Stack, useLocalSearchParams } from "expo-router";
|
||||
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
|
||||
@@ -12,14 +16,9 @@ export default function FileScreen() {
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
<FontAwesome
|
||||
name="file-pdf-o"
|
||||
size={300}
|
||||
style={{ alignSelf: "center" }}
|
||||
color={MainColor.white}
|
||||
/>
|
||||
</ViewWrapper>
|
||||
<SafeAreaView style={{ flex: 1 }} edges={["bottom"]}>
|
||||
<PdfViewer uri={url} />
|
||||
</SafeAreaView>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { CenterCustom, TextCustom, ViewWrapper } from "@/components";
|
||||
import API_STRORAGE from "@/constants/base-url-api-strorage";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { Image } from "expo-image";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import React, { useState } from "react";
|
||||
import { View } from "react-native";
|
||||
|
||||
export default function PreviewImage() {
|
||||
const { id } = useLocalSearchParams();
|
||||
@@ -11,18 +13,48 @@ export default function PreviewImage() {
|
||||
return (
|
||||
<ViewWrapper>
|
||||
{id ? (
|
||||
<Image
|
||||
onLoad={() => {
|
||||
setIsLoading(false);
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
position: "relative",
|
||||
}}
|
||||
source={
|
||||
isLoading
|
||||
? require("@/assets/images/loading.gif")
|
||||
: API_STRORAGE.GET({ fileId: id as string })
|
||||
}
|
||||
contentFit="contain"
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
/>
|
||||
>
|
||||
{/* Main Image */}
|
||||
<Image
|
||||
onLoad={() => {
|
||||
setIsLoading(false);
|
||||
}}
|
||||
source={API_STRORAGE.GET({ fileId: id as string })}
|
||||
contentFit="contain"
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
// placeholder={require("@/assets/images/loading.gif")}
|
||||
/>
|
||||
|
||||
{/* Custom Loader Overlay */}
|
||||
{isLoading && (
|
||||
<View
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
backgroundColor: MainColor.darkblue,
|
||||
zIndex: 1,
|
||||
opacity: 0.5,
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
source={require("@/assets/images/loading.gif")}
|
||||
contentFit="contain"
|
||||
style={{ width: 60, height: 60 }}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
) : (
|
||||
<CenterCustom>
|
||||
<TextCustom>File not found</TextCustom>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { BackButton } from "@/components";
|
||||
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";
|
||||
@@ -10,7 +12,13 @@ export default function UserLayout() {
|
||||
return (
|
||||
<>
|
||||
<Stack screenOptions={HeaderStyles}>
|
||||
|
||||
<Stack.Screen
|
||||
name="delete-account"
|
||||
options={{
|
||||
title: "Hapus Akun",
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="waiting-room"
|
||||
options={{
|
||||
@@ -45,24 +53,35 @@ export default function UserLayout() {
|
||||
/>
|
||||
|
||||
{/* ========== Notification Section ========= */}
|
||||
<Stack.Screen
|
||||
|
||||
{/* DIPINDAH DI FILE NOTIFICATION USER */}
|
||||
{/* <Stack.Screen
|
||||
name="notifications/index"
|
||||
options={{
|
||||
title: "Notifikasi",
|
||||
headerLeft: () => <BackButton />,
|
||||
// headerRight: () => (
|
||||
// <IconPlus
|
||||
// color={MainColor.yellow}
|
||||
// onPress={() => router.push("/test-notifications")}
|
||||
// />
|
||||
// ),
|
||||
}}
|
||||
/>
|
||||
/> */}
|
||||
|
||||
{/* ========== Event Section ========= */}
|
||||
|
||||
<Stack.Screen
|
||||
name="event/(tabs)"
|
||||
options={{
|
||||
title: "Event",
|
||||
headerLeft: () => (
|
||||
<LeftButtonCustom path="/(application)/(user)/home" />
|
||||
),
|
||||
// NOTE: DIPINDAH DI FILE /Event/(Tabs)/_layout.tsx
|
||||
// headerLeft: () => (
|
||||
// <LeftButtonCustom path="/(application)/(user)/home" />
|
||||
// ),
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="event/create"
|
||||
options={{
|
||||
@@ -186,7 +205,7 @@ export default function UserLayout() {
|
||||
name="crowdfunding/index"
|
||||
options={{
|
||||
title: "Crowdfunding",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerLeft: () => <BackButton path="/home" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -449,7 +468,7 @@ export default function UserLayout() {
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="donation/[id]/(transaction-flow)/[transaction]/invoice"
|
||||
name="donation/[id]/(transaction-flow)/[invoiceId]/invoice"
|
||||
options={{
|
||||
title: "Invoice",
|
||||
headerLeft: () => (
|
||||
@@ -463,7 +482,7 @@ export default function UserLayout() {
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="donation/[id]/(transaction-flow)/[transaction]/process"
|
||||
name="donation/[id]/(transaction-flow)/[invoiceId]/process"
|
||||
options={{
|
||||
title: "Proses",
|
||||
headerLeft: () => (
|
||||
@@ -477,14 +496,14 @@ export default function UserLayout() {
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="donation/[id]/(transaction-flow)/[transaction]/success"
|
||||
name="donation/[id]/(transaction-flow)/[invoiceId]/success"
|
||||
options={{
|
||||
title: "Donasi Berhasil",
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="donation/[id]/(transaction-flow)/[transaction]/failed"
|
||||
name="donation/[id]/(transaction-flow)/[invoiceId]/failed"
|
||||
options={{
|
||||
title: "Donasi Gagal",
|
||||
headerLeft: () => <BackButton />,
|
||||
@@ -505,7 +524,8 @@ export default function UserLayout() {
|
||||
name="job/(tabs)"
|
||||
options={{
|
||||
title: "Job Vacancy",
|
||||
headerLeft: () => <BackButton path="/home" />,
|
||||
// headerLeft: () => <BackButton path="/home" />,
|
||||
// NOTE: headerLeft di pindahkan ke Tabs Layout
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
@@ -589,6 +609,27 @@ export default function UserLayout() {
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="forum/terms"
|
||||
options={{
|
||||
title: "Syarat & Ketentuan Forum",
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="forum/[id]/preview-report-posting"
|
||||
options={{
|
||||
title: "Laporan Postingan",
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="forum/[id]/preview-report-comment"
|
||||
options={{
|
||||
title: "Laporan Komentar",
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* ========== Maps Section ========= */}
|
||||
<Stack.Screen
|
||||
|
||||
@@ -19,7 +19,6 @@ import Toast from "react-native-toast-message";
|
||||
|
||||
export default function CollaborationEdit() {
|
||||
const { id } = useLocalSearchParams();
|
||||
console.log("id :", id);
|
||||
const [data, setData] = useState<any>();
|
||||
const [listMaster, setListMaster] = useState<any[]>([]);
|
||||
const [loadingData, setLoadingData] = useState(false);
|
||||
|
||||
@@ -155,7 +155,7 @@ export default function CollaborationCreate() {
|
||||
<TextAreaCustom
|
||||
required
|
||||
label="Keuntungan Proyek"
|
||||
placeholder="Masukan keuntungan proyek"
|
||||
placeholder="Masukan keuntungan proyek, contoh: Meningkatkan relasi bisnis , menjamin kualitas produk, meningkatkan kinerja dan lain lain"
|
||||
showCount
|
||||
maxLength={1000}
|
||||
value={data?.benefit}
|
||||
|
||||
111
app/(application)/(user)/delete-account.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
import {
|
||||
AlertDefaultSystem,
|
||||
BaseBox,
|
||||
ButtonCustom,
|
||||
CenterCustom,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiDeleteUser } from "@/service/api-client/api-user";
|
||||
import { Image } from "expo-image";
|
||||
import { useLocalSearchParams } from "expo-router/build/hooks";
|
||||
import { useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function DeleteAccount() {
|
||||
const { token, logout, user } = useAuth();
|
||||
const { phone } = useLocalSearchParams();
|
||||
const [text, setText] = useState("");
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
const deleteAccount = async () => {
|
||||
if (text !== "Delete Account") {
|
||||
return Toast.show({
|
||||
type: "error",
|
||||
text1: "Ketik 'Delete Account' untuk menghapus akun",
|
||||
});
|
||||
}
|
||||
|
||||
AlertDefaultSystem({
|
||||
title: "Anda yakin akan menghapus akun ini?",
|
||||
message:
|
||||
"Semua data yang pernah anda buat akan terhapus secara permanen !",
|
||||
textLeft: "Batal",
|
||||
textRight: "Ya",
|
||||
onPressRight: async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await apiDeleteUser({ id: user?.id as string });
|
||||
|
||||
if (response.success) {
|
||||
console.log("RESPONSE >> ", response);
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Akun berhasil dihapus",
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
logout();
|
||||
setLoading(false);
|
||||
}, 2000);
|
||||
} else {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal menghapus akun",
|
||||
});
|
||||
setLoading(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("ERROR >> ", error);
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
<StackCustom>
|
||||
<BaseBox>
|
||||
<StackCustom>
|
||||
<CenterCustom>
|
||||
<Image
|
||||
source={require("@/assets/images/constants/logo-hipmi.png")}
|
||||
style={{
|
||||
width: 150,
|
||||
height: 150,
|
||||
}}
|
||||
/>
|
||||
</CenterCustom>
|
||||
<TextCustom align="center">
|
||||
Anda akan menghapus akun dengan nomor +{phone}
|
||||
</TextCustom>
|
||||
<TextCustom align="center">
|
||||
Ketik 'Delete Account' untuk menghapus akun
|
||||
</TextCustom>
|
||||
<TextInputCustom
|
||||
value={text}
|
||||
onChangeText={setText}
|
||||
placeholder="Ketik 'Delete Account'"
|
||||
/>
|
||||
|
||||
<ButtonCustom
|
||||
backgroundColor="red"
|
||||
textColor="white"
|
||||
onPress={deleteAccount}
|
||||
isLoading={isLoading}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Submit
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,21 +1,9 @@
|
||||
import {
|
||||
FloatingButton,
|
||||
ViewWrapper
|
||||
} from "@/components";
|
||||
import Donation_BoxPublish from "@/screens/Donation/BoxPublish";
|
||||
import { router } from "expo-router";
|
||||
import Donation_ScreenBeranda from "@/screens/Donation/ScreenBeranda";
|
||||
|
||||
export default function DonationBeranda() {
|
||||
return (
|
||||
<ViewWrapper
|
||||
hideFooter
|
||||
floatingButton={
|
||||
<FloatingButton onPress={() => router.push("/donation/create")} />
|
||||
}
|
||||
>
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<Donation_BoxPublish key={index} id={index.toString()}/>
|
||||
))}
|
||||
</ViewWrapper>
|
||||
<>
|
||||
<Donation_ScreenBeranda />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,76 +1,5 @@
|
||||
import {
|
||||
BadgeCustom,
|
||||
BaseBox,
|
||||
DummyLandscapeImage,
|
||||
Grid,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { dummyMasterStatusTransaction } from "@/lib/dummy-data/_master/status-transaction";
|
||||
import { router } from "expo-router";
|
||||
import { View } from "react-native";
|
||||
import Donation_ScreenMyDonation from "@/screens/Donation/ScreenMyDonation";
|
||||
|
||||
export default function DonationMyDonation() {
|
||||
const randomStatusData = Array.from({ length: 10 }, () => {
|
||||
const randomIndex = Math.floor(
|
||||
Math.random() * dummyMasterStatusTransaction.length
|
||||
);
|
||||
return dummyMasterStatusTransaction[randomIndex];
|
||||
});
|
||||
|
||||
const handlePress = (value: string) => {
|
||||
if (value === "menunggu") {
|
||||
router.push(`/donation/${value}/(transaction-flow)/123/invoice`);
|
||||
} else if (value === "proses") {
|
||||
router.push(`/donation/${value}/(transaction-flow)/123/process`);
|
||||
} else if (value === "berhasil") {
|
||||
router.push(`/donation/${value}/(transaction-flow)/123/success`);
|
||||
} else if (value === "gagal") {
|
||||
router.push(`/donation/${value}/(transaction-flow)/123/failed`);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper hideFooter>
|
||||
{randomStatusData.map((item, index) => (
|
||||
<BaseBox
|
||||
key={index}
|
||||
paddingTop={7}
|
||||
paddingBottom={7}
|
||||
onPress={() => {
|
||||
handlePress(item.value);
|
||||
}}
|
||||
>
|
||||
<Grid>
|
||||
<Grid.Col span={5}>
|
||||
<DummyLandscapeImage height={100} unClickPath />
|
||||
</Grid.Col>
|
||||
<Grid.Col span={1}>
|
||||
<View />
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
<StackCustom gap={"sm"}>
|
||||
<View>
|
||||
<TextCustom truncate>
|
||||
Judul Donasi: Lorem ipsum dolor sit amet consectetur
|
||||
adipisicing elit.
|
||||
</TextCustom>
|
||||
</View>
|
||||
<View>
|
||||
<TextCustom>Donasi Saya</TextCustom>
|
||||
<TextCustom bold color="yellow">
|
||||
Rp. 7.500.000
|
||||
</TextCustom>
|
||||
</View>
|
||||
<BadgeCustom variant="light" color={item.color} fullWidth>
|
||||
{item.label}
|
||||
</BadgeCustom>
|
||||
</StackCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
))}
|
||||
</ViewWrapper>
|
||||
);
|
||||
return <Donation_ScreenMyDonation />;
|
||||
}
|
||||
|
||||
@@ -1,38 +1,10 @@
|
||||
import { ScrollableCustom, ViewWrapper } from "@/components";
|
||||
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
|
||||
import Donasi_BoxStatus from "@/screens/Donation/BoxStatus";
|
||||
import { useState } from "react";
|
||||
import Donation_ScreenStatus from "@/screens/Donation/ScreenStatus";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
|
||||
export default function DonationStatus() {
|
||||
const [activeCategory, setActiveCategory] = useState<string | null>(
|
||||
"publish"
|
||||
);
|
||||
|
||||
const handlePress = (item: any) => {
|
||||
setActiveCategory(item.value);
|
||||
// tambahkan logika lain seperti filter dsb.
|
||||
};
|
||||
|
||||
const scrollComponent = (
|
||||
<ScrollableCustom
|
||||
data={dummyMasterStatus.map((e, i) => ({
|
||||
id: i,
|
||||
label: e.label,
|
||||
value: e.value,
|
||||
}))}
|
||||
onButtonPress={handlePress}
|
||||
activeId={activeCategory as any}
|
||||
/>
|
||||
);
|
||||
const { status } = useLocalSearchParams<{ status?: string }>();
|
||||
|
||||
return (
|
||||
<ViewWrapper hideFooter headerComponent={scrollComponent}>
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<Donasi_BoxStatus
|
||||
key={index}
|
||||
id={index.toString()}
|
||||
status={activeCategory as string}
|
||||
/>
|
||||
))}
|
||||
</ViewWrapper>
|
||||
<Donation_ScreenStatus initialStatus={status || "publish"} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
InformationBox,
|
||||
@@ -9,17 +11,134 @@ import {
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { router } from "expo-router";
|
||||
import API_STRORAGE from "@/constants/base-url-api-strorage";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
import {
|
||||
apiDonationGetNewsById,
|
||||
apiDonationUpdateNews,
|
||||
} from "@/service/api-client/api-donation";
|
||||
import { uploadFileService } from "@/service/upload-service";
|
||||
import pickFile, { IFileData } from "@/utils/pickFile";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function DonationEditNews() {
|
||||
const { news } = useLocalSearchParams();
|
||||
const [data, setData] = useState<any>(null);
|
||||
const [image, setImage] = useState<IFileData | null>(null);
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [news]),
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiDonationGetNewsById({
|
||||
id: news as string,
|
||||
category: "get-one",
|
||||
});
|
||||
|
||||
setData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerSubmitUpdate = async () => {
|
||||
let newData;
|
||||
if (!data.title || !data.deskripsi) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Judul dan deskripsi harus diisi",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
newData = {
|
||||
title: data?.title,
|
||||
deskripsi: data?.deskripsi,
|
||||
};
|
||||
|
||||
if (image && image?.uri) {
|
||||
const uploadNewImage = await uploadFileService({
|
||||
dirId: DIRECTORY_ID.donasi_kabar,
|
||||
imageUri: image?.uri,
|
||||
});
|
||||
|
||||
newData = {
|
||||
title: data?.title,
|
||||
deskripsi: data?.deskripsi,
|
||||
newImageId: uploadNewImage.data.id,
|
||||
};
|
||||
}
|
||||
|
||||
const response = await apiDonationUpdateNews({
|
||||
id: news as string,
|
||||
data: newData,
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal mengupdate berita",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Berita berhasil diperbarui",
|
||||
});
|
||||
router.back();
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<ViewWrapper
|
||||
footerComponent={
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
disabled={!data?.title || !data?.deskripsi}
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
handlerSubmitUpdate();
|
||||
}}
|
||||
>
|
||||
Update
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
}
|
||||
>
|
||||
<StackCustom gap={"xs"}>
|
||||
<InformationBox text="Upload gambar bersifat opsional untuk melengkapi kabar terkait donasi Anda." />
|
||||
<LandscapeFrameUploaded />
|
||||
<LandscapeFrameUploaded
|
||||
image={
|
||||
image
|
||||
? image.uri
|
||||
: data && data.imageId
|
||||
? API_STRORAGE.GET({ fileId: data.imageId })
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<ButtonCenteredOnly
|
||||
onPress={() => {
|
||||
router.push("/(application)/(image)/take-picture/123");
|
||||
pickFile({
|
||||
allowedType: "image",
|
||||
setImageUri(file) {
|
||||
setImage(file);
|
||||
},
|
||||
});
|
||||
}}
|
||||
icon="upload"
|
||||
>
|
||||
@@ -30,6 +149,8 @@ export default function DonationEditNews() {
|
||||
label="Judul Berita"
|
||||
placeholder="Masukan judul berita"
|
||||
required
|
||||
value={data?.title}
|
||||
onChangeText={(value) => setData({ ...data, title: value })}
|
||||
/>
|
||||
<TextAreaCustom
|
||||
label="Deskripsi Berita"
|
||||
@@ -37,16 +158,11 @@ export default function DonationEditNews() {
|
||||
required
|
||||
showCount
|
||||
maxLength={1000}
|
||||
value={data?.deskripsi}
|
||||
onChangeText={(value) => setData({ ...data, deskripsi: value })}
|
||||
/>
|
||||
|
||||
<Spacing />
|
||||
<ButtonCustom
|
||||
onPress={() => {
|
||||
router.back();
|
||||
}}
|
||||
>
|
||||
Update
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AlertDefaultSystem,
|
||||
BackButton,
|
||||
@@ -12,13 +13,45 @@ import {
|
||||
} from "@/components";
|
||||
import { IconEdit } from "@/components/_Icon";
|
||||
import { IconTrash } from "@/components/_Icon/IconTrash";
|
||||
import dayjs from "dayjs";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import {
|
||||
apiDonationDeleteNews,
|
||||
apiDonationGetNewsById,
|
||||
} from "@/service/api-client/api-donation";
|
||||
import { formatChatTime } from "@/utils/formatChatTime";
|
||||
import {
|
||||
router,
|
||||
Stack,
|
||||
useFocusEffect,
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function DonationNews() {
|
||||
const { id, news } = useLocalSearchParams();
|
||||
const { user } = useAuth();
|
||||
const { news } = useLocalSearchParams();
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [data, setData] = useState<any>(null);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [news])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiDonationGetNewsById({
|
||||
id: news as string,
|
||||
category: "get-one",
|
||||
});
|
||||
|
||||
setData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -26,28 +59,28 @@ export default function DonationNews() {
|
||||
options={{
|
||||
title: "Detail Kabar",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
|
||||
headerRight: () =>
|
||||
user?.id === data?.authorId && (
|
||||
<DotButton onPress={() => setOpenDrawer(true)} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
<BaseBox>
|
||||
<StackCustom>
|
||||
<TextCustom style={{ alignSelf: "flex-end" }}>
|
||||
{dayjs().format("DD MMM YYYY")}
|
||||
{formatChatTime(data?.createdAt)}
|
||||
</TextCustom>
|
||||
|
||||
<DummyLandscapeImage />
|
||||
{data && data.imageId && (
|
||||
<DummyLandscapeImage imageId={data.imageId} />
|
||||
)}
|
||||
|
||||
<TextCustom bold size="large" align="center">
|
||||
Judul Berita
|
||||
{data?.title || "-"}
|
||||
</TextCustom>
|
||||
|
||||
<TextCustom>
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Sapiente
|
||||
est id temporibus perferendis eos reiciendis reprehenderit tempora
|
||||
ut quibusdam dolores facilis rerum exercitationem recusandae quis
|
||||
neque, adipisci dolorum, aspernatur labore?
|
||||
</TextCustom>
|
||||
<TextCustom>{data?.deskripsi || "-"}</TextCustom>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
</ViewWrapper>
|
||||
@@ -62,12 +95,12 @@ export default function DonationNews() {
|
||||
{
|
||||
icon: <IconEdit />,
|
||||
label: "Edit Berita",
|
||||
path: `/donation/${id}/(news)/${news}/edit-news` as any,
|
||||
path: `/donation/[id]/(news)/${news}/edit-news` as any,
|
||||
},
|
||||
{
|
||||
icon: <IconTrash />,
|
||||
label: "Hapus Berita",
|
||||
path: `` as any,
|
||||
path: "",
|
||||
color: "red",
|
||||
},
|
||||
]}
|
||||
@@ -79,7 +112,23 @@ export default function DonationNews() {
|
||||
message: "Apakah Anda yakin ingin menghapus berita ini?",
|
||||
textLeft: "Batal",
|
||||
textRight: "Hapus",
|
||||
onPressRight: () => {
|
||||
onPressRight: async () => {
|
||||
const response = await apiDonationDeleteNews({
|
||||
id: news as string,
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal menghapus berita",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Berita berhasil dihapus",
|
||||
});
|
||||
router.back();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,25 +1,103 @@
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
InformationBox,
|
||||
LandscapeFrameUploaded,
|
||||
NewWrapper,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextAreaCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { router } from "expo-router";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
import { apiDonationCreateNews } from "@/service/api-client/api-donation";
|
||||
import { uploadFileService } from "@/service/upload-service";
|
||||
import pickFile, { IFileData } from "@/utils/pickFile";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function DonationAddNews() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [data, setData] = useState({
|
||||
title: "",
|
||||
deskripsi: "",
|
||||
});
|
||||
const [image, setImage] = useState<IFileData | null>(null);
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
const handlerSubmit = async () => {
|
||||
let newData: any = { ...data };
|
||||
try {
|
||||
setLoading(true);
|
||||
if (image) {
|
||||
const responseUploadImage = await uploadFileService({
|
||||
dirId: DIRECTORY_ID.donasi_kabar,
|
||||
imageUri: image?.uri,
|
||||
});
|
||||
|
||||
newData = {
|
||||
...newData,
|
||||
imageId: responseUploadImage.data.id,
|
||||
};
|
||||
}
|
||||
|
||||
const response = await apiDonationCreateNews({
|
||||
id: id as string,
|
||||
data: newData,
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal menambah berita",
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Berita berhasil ditambahkan",
|
||||
});
|
||||
|
||||
router.back();
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<NewWrapper
|
||||
footerComponent={
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
disabled={!data.title || !data.deskripsi}
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
handlerSubmit();
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
}
|
||||
>
|
||||
<StackCustom gap={"xs"}>
|
||||
<InformationBox text="Upload gambar bersifat opsional untuk melengkapi kabar terkait donasi Anda." />
|
||||
<LandscapeFrameUploaded />
|
||||
<LandscapeFrameUploaded image={image?.uri} />
|
||||
<ButtonCenteredOnly
|
||||
onPress={() => {
|
||||
router.push("/(application)/(image)/take-picture/123");
|
||||
pickFile({
|
||||
allowedType: "image",
|
||||
setImageUri(file) {
|
||||
setImage(file);
|
||||
},
|
||||
});
|
||||
}}
|
||||
icon="upload"
|
||||
>
|
||||
@@ -30,6 +108,13 @@ export default function DonationAddNews() {
|
||||
label="Judul Berita"
|
||||
placeholder="Masukan judul berita"
|
||||
required
|
||||
value={data.title}
|
||||
onChangeText={(value) => {
|
||||
setData({
|
||||
...data,
|
||||
title: value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<TextAreaCustom
|
||||
label="Deskripsi Berita"
|
||||
@@ -37,18 +122,17 @@ export default function DonationAddNews() {
|
||||
required
|
||||
showCount
|
||||
maxLength={1000}
|
||||
/>
|
||||
|
||||
<Spacing />
|
||||
<ButtonCustom
|
||||
onPress={() => {
|
||||
router.back();
|
||||
value={data.deskripsi}
|
||||
onChangeText={(value) => {
|
||||
setData({
|
||||
...data,
|
||||
deskripsi: value,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</ButtonCustom>
|
||||
/>
|
||||
|
||||
<Spacing />
|
||||
</StackCustom>
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
</NewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,67 +1,8 @@
|
||||
import {
|
||||
BackButton,
|
||||
BaseBox,
|
||||
DrawerCustom,
|
||||
Grid,
|
||||
MenuDrawerDynamicGrid,
|
||||
TextCustom,
|
||||
ViewWrapper
|
||||
} from "@/components";
|
||||
import { IconPlus } from "@/components/_Icon";
|
||||
import dayjs from "dayjs";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import Donation_ScreenListOfNews from "@/screens/Donation/ScreenListOfNews";
|
||||
|
||||
export default function DonationRecapOfNews() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Daftar Kabar",
|
||||
headerLeft: () => <BackButton />, }}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
{Array.from({ length: 15 }).map((_, index) => (
|
||||
<BaseBox key={index} href={`/donation/${id}/(news)/${index}`}>
|
||||
<Grid>
|
||||
<Grid.Col span={8}>
|
||||
<TextCustom truncate bold>
|
||||
Lorem ipsum dolor, sit amet consectetur adipisicing elit.
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={4} style={{ alignItems: "flex-end" }}>
|
||||
<TextCustom size="small">
|
||||
{dayjs().format("DD MMM YYYY")}
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
))}
|
||||
</ViewWrapper>
|
||||
|
||||
<DrawerCustom
|
||||
isVisible={openDrawer}
|
||||
closeDrawer={() => setOpenDrawer(false)}
|
||||
height={"auto"}
|
||||
>
|
||||
<MenuDrawerDynamicGrid
|
||||
data={[
|
||||
{
|
||||
icon: <IconPlus />,
|
||||
label: "Tambah Berita",
|
||||
path: `/donation/${id}/(news)/add-news`,
|
||||
},
|
||||
]}
|
||||
onPressItem={(item) => {
|
||||
console.log("PATH ", item.path);
|
||||
router.navigate(item.path as any);
|
||||
setOpenDrawer(false);
|
||||
}}
|
||||
/>
|
||||
</DrawerCustom>
|
||||
</>
|
||||
);
|
||||
|
||||
return <Donation_ScreenListOfNews donationId={id as string} />;
|
||||
}
|
||||
|
||||
@@ -1,68 +1,8 @@
|
||||
import {
|
||||
BackButton,
|
||||
BaseBox,
|
||||
DotButton,
|
||||
DrawerCustom,
|
||||
Grid,
|
||||
MenuDrawerDynamicGrid,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { IconPlus } from "@/components/_Icon";
|
||||
import dayjs from "dayjs";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import Donation_ScreenRecapOfNews from "@/screens/Donation/ScreenRecapOfNews";
|
||||
|
||||
export default function DonationRecapOfNews() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Rekap Kabar",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
{Array.from({ length: 15 }).map((_, index) => (
|
||||
<BaseBox key={index} href={`/donation/${id}/(news)/${index}`}>
|
||||
<Grid>
|
||||
<Grid.Col span={8}>
|
||||
<TextCustom truncate bold>
|
||||
Lorem ipsum dolor, sit amet consectetur adipisicing elit.
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={4} style={{ alignItems: "flex-end" }}>
|
||||
<TextCustom size="small">{dayjs().format("DD MMM YYYY")}</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
))}
|
||||
</ViewWrapper>
|
||||
|
||||
<DrawerCustom
|
||||
isVisible={openDrawer}
|
||||
closeDrawer={() => setOpenDrawer(false)}
|
||||
height={"auto"}
|
||||
>
|
||||
<MenuDrawerDynamicGrid
|
||||
data={[
|
||||
{
|
||||
icon: <IconPlus />,
|
||||
label: "Tambah Berita",
|
||||
path: `/donation/${id}/(news)/add-news`,
|
||||
},
|
||||
]}
|
||||
onPressItem={(item) => {
|
||||
console.log("PATH ", item.path);
|
||||
router.navigate(item.path as any);
|
||||
setOpenDrawer(false);
|
||||
}}
|
||||
/>
|
||||
</DrawerCustom>
|
||||
</>
|
||||
);
|
||||
|
||||
return <Donation_ScreenRecapOfNews donationId={id as string} />;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
Grid,
|
||||
@@ -7,11 +8,60 @@ import {
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { apiDonationGetInvoiceById } from "@/service/api-client/api-donation";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import { dateTimeView } from "@/utils/dateTimeView";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import { FontAwesome6 } from "@expo/vector-icons";
|
||||
import dayjs from "dayjs";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function DonasiFailed() {
|
||||
const { id, invoiceId } = useLocalSearchParams();
|
||||
const [data, setData] = useState<any>(null);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id, invoiceId])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiDonationGetInvoiceById({
|
||||
id: invoiceId as string,
|
||||
});
|
||||
|
||||
console.log("[DATA]", JSON.stringify(response.data, null, 2));
|
||||
setData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const listData = [
|
||||
{
|
||||
label: "Bank",
|
||||
value: (data && data?.MasterBank?.namaBank) || "-",
|
||||
},
|
||||
{
|
||||
label: "Rekening Penerima",
|
||||
value: (data && data?.MasterBank?.namaAkun) || "-",
|
||||
},
|
||||
{
|
||||
label: "No Rekening",
|
||||
value: (data && data?.MasterBank?.norek) || "-",
|
||||
},
|
||||
{
|
||||
label: "Jumlah Donasi",
|
||||
value: (data && formatCurrencyDisplay(data?.nominal)) || "-",
|
||||
},
|
||||
{
|
||||
label: "Tanggal",
|
||||
value: (data && dateTimeView({ date: data?.createdAt })) || "-",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<StackCustom>
|
||||
@@ -58,26 +108,3 @@ export default function DonasiFailed() {
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
const listData = [
|
||||
{
|
||||
label: "Bank",
|
||||
value: " BCA",
|
||||
},
|
||||
{
|
||||
label: "Rekening Penerima",
|
||||
value: "Himpunan Pengusaha Muda Indonesia",
|
||||
},
|
||||
{
|
||||
label: "No Rekening",
|
||||
value: "2304235678854332",
|
||||
},
|
||||
{
|
||||
label: "Jumlah Donasi",
|
||||
value: "Rp. 750.000",
|
||||
},
|
||||
{
|
||||
label: "Tanggal",
|
||||
value: `${dayjs(new Date()).format("DD/MM/YYYY")}`,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,228 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
BoxButtonOnFooter,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
Grid,
|
||||
InformationBox,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import CopyButton from "@/components/Button/CoyButton";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
import {
|
||||
apiDonationGetInvoiceById,
|
||||
apiDonationUpdateInvoice,
|
||||
} from "@/service/api-client/api-donation";
|
||||
import { uploadFileService } from "@/service/upload-service";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import pickFile from "@/utils/pickFile";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function DonationInvoice() {
|
||||
const { invoiceId } = useLocalSearchParams();
|
||||
console.log("invoiceId", invoiceId);
|
||||
const [data, setData] = useState<any>(null);
|
||||
const [image, setImage] = useState<any>(null);
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [invoiceId]),
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiDonationGetInvoiceById({
|
||||
id: invoiceId as string,
|
||||
});
|
||||
console.log("[RESPONSE]", JSON.stringify(response, null, 2));
|
||||
setData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerUpdateInvoice = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const responseUploadImage = await uploadFileService({
|
||||
dirId: DIRECTORY_ID.donasi_bukti_transfer,
|
||||
imageUri: image?.uri,
|
||||
});
|
||||
|
||||
console.log("[RESPONSE UPLOAD IMAGE]", responseUploadImage);
|
||||
|
||||
if (!responseUploadImage?.data?.id) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal mengunggah bukti transfer",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const fileId = responseUploadImage?.data?.id;
|
||||
|
||||
const response = await apiDonationUpdateInvoice({
|
||||
id: invoiceId as string,
|
||||
fileId: fileId,
|
||||
status: "proses",
|
||||
});
|
||||
|
||||
console.log("[RESPONSE UPDATE]", JSON.stringify(response, null, 2));
|
||||
|
||||
if (!response.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal mengunggah bukti transfer",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Berhasil mengunggah bukti transfer",
|
||||
});
|
||||
router.replace(`/donation/[id]/(transaction-flow)/${invoiceId}/process`);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper
|
||||
hideFooter
|
||||
footerComponent={
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
disabled={!image}
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
handlerUpdateInvoice();
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
}
|
||||
>
|
||||
<StackCustom>
|
||||
<InformationBox
|
||||
text={`Mohon transfer donasi anda ke rekening dibawah`}
|
||||
/>
|
||||
<BaseBox>
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextCustom bold>
|
||||
BANK: {data?.DonasiMaster_Bank?.name}
|
||||
</TextCustom>
|
||||
{/* <TextCustom>{data?.DonasiMaster_Bank?.accountName}</TextCustom> */}
|
||||
<Spacing height={10} />
|
||||
|
||||
<BaseBox backgroundColor={MainColor.soft_darkblue}>
|
||||
<Grid containerStyle={{ justifyContent: "center" }}>
|
||||
<Grid.Col
|
||||
span={8}
|
||||
style={{
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<TextCustom size="xlarge" bold color="yellow">
|
||||
{data?.MasterBank?.norek}
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col
|
||||
span={4}
|
||||
style={{
|
||||
alignItems: "flex-end",
|
||||
}}
|
||||
>
|
||||
<CopyButton textToCopy={data?.MasterBank?.norek} />
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
|
||||
<BaseBox>
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextCustom>Jumlah Transaksi</TextCustom>
|
||||
|
||||
<Spacing height={10} />
|
||||
|
||||
<BaseBox backgroundColor={MainColor.soft_darkblue}>
|
||||
<Grid containerStyle={{ justifyContent: "center" }}>
|
||||
<Grid.Col
|
||||
span={8}
|
||||
style={{
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<TextCustom size="xlarge" bold color="yellow">
|
||||
Rp. {formatCurrencyDisplay(data?.nominal) || "-"}
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col
|
||||
span={4}
|
||||
style={{
|
||||
alignItems: "flex-end",
|
||||
}}
|
||||
>
|
||||
<CopyButton textToCopy={data?.nominal} />
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
|
||||
<BaseBox>
|
||||
<StackCustom>
|
||||
<TextCustom bold align="center" size={"small"} color="gray">
|
||||
Upload bukti transfer anda.
|
||||
</TextCustom>
|
||||
{image ? (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: 10,
|
||||
paddingInline: 20,
|
||||
}}
|
||||
>
|
||||
<TextCustom bold align="center" truncate>
|
||||
{image?.name}
|
||||
</TextCustom>
|
||||
</View>
|
||||
) : null}
|
||||
<ButtonCenteredOnly
|
||||
onPress={() => {
|
||||
pickFile({
|
||||
allowedType: "image",
|
||||
setImageUri(file) {
|
||||
setImage(file);
|
||||
},
|
||||
});
|
||||
}}
|
||||
icon="upload"
|
||||
>
|
||||
Upload
|
||||
</ButtonCenteredOnly>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
</StackCustom>
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,13 +1,6 @@
|
||||
import {
|
||||
BaseBox,
|
||||
Grid,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { ActivityIndicator } from "react-native";
|
||||
import { BaseBox, StackCustom, TextCustom, ViewWrapper } from "@/components";
|
||||
import MoneyTransferAnimation from "@/components/_ShareComponent/MoneyTransferAnimation";
|
||||
import { View } from "react-native";
|
||||
|
||||
export default function DonationProcess() {
|
||||
return (
|
||||
@@ -16,13 +9,16 @@ export default function DonationProcess() {
|
||||
<BaseBox>
|
||||
<StackCustom>
|
||||
<TextCustom align="center" bold>
|
||||
Admin sedang memproses transaksi donasimu
|
||||
Admin sedang memvalidasi data dan bukti transfer anda. Mohon
|
||||
tunggu proses ini selesai.
|
||||
</TextCustom>
|
||||
<ActivityIndicator size="large" color={MainColor.yellow} />
|
||||
<View style={{ alignItems: "center", justifyContent: "center" }}>
|
||||
<MoneyTransferAnimation />
|
||||
</View>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
|
||||
<BaseBox>
|
||||
{/* <BaseBox>
|
||||
<Grid>
|
||||
<Grid.Col span={10} style={{ justifyContent: "center" }}>
|
||||
<TextCustom size="small">
|
||||
@@ -38,7 +34,7 @@ export default function DonationProcess() {
|
||||
/>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
</BaseBox> */}
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
Grid,
|
||||
@@ -7,11 +8,60 @@ import {
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { apiDonationGetInvoiceById } from "@/service/api-client/api-donation";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import { dateTimeView } from "@/utils/dateTimeView";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import { FontAwesome6 } from "@expo/vector-icons";
|
||||
import dayjs from "dayjs";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function DonationSuccess() {
|
||||
const { id, invoiceId } = useLocalSearchParams();
|
||||
const [data, setData] = useState<any>(null);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id, invoiceId])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiDonationGetInvoiceById({
|
||||
id: invoiceId as string,
|
||||
});
|
||||
|
||||
console.log("[DATA]", JSON.stringify(response.data, null, 2));
|
||||
setData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const listData = [
|
||||
{
|
||||
label: "Bank",
|
||||
value: (data && data?.MasterBank?.namaBank) || "-",
|
||||
},
|
||||
{
|
||||
label: "Rekening Penerima",
|
||||
value: (data && data?.MasterBank?.namaAkun) || "-",
|
||||
},
|
||||
{
|
||||
label: "No Rekening",
|
||||
value: (data && data?.MasterBank?.norek) || "-",
|
||||
},
|
||||
{
|
||||
label: "Jumlah Donasi",
|
||||
value: (data && formatCurrencyDisplay(data?.nominal)) || "-",
|
||||
},
|
||||
{
|
||||
label: "Tanggal",
|
||||
value: (data && dateTimeView({ date: data?.createdAt })) || "-",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<StackCustom>
|
||||
@@ -59,25 +109,4 @@ export default function DonationSuccess() {
|
||||
);
|
||||
}
|
||||
|
||||
const listData = [
|
||||
{
|
||||
label: "Bank",
|
||||
value: " BCA",
|
||||
},
|
||||
{
|
||||
label: "Rekening Penerima",
|
||||
value: "Himpunan Pengusaha Muda Indonesia",
|
||||
},
|
||||
{
|
||||
label: "No Rekening",
|
||||
value: "2304235678854332",
|
||||
},
|
||||
{
|
||||
label: "Jumlah Donasi",
|
||||
value: "Rp. 750.000",
|
||||
},
|
||||
{
|
||||
label: "Tanggal",
|
||||
value: `${dayjs(new Date()).format("DD/MM/YYYY")}`,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
import {
|
||||
BaseBox,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
Grid,
|
||||
InformationBox,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
|
||||
export default function DonationInvoice() {
|
||||
const { id, transaction } = useLocalSearchParams();
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
<StackCustom>
|
||||
<InformationBox text={`Mohon transfer donasi anda ke rekening dibawah dengan Id: ${transaction}`} />
|
||||
<BaseBox>
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextCustom>Nama BANK</TextCustom>
|
||||
<TextCustom>Nama Penerima</TextCustom>
|
||||
<Spacing height={10} />
|
||||
|
||||
<BaseBox backgroundColor={MainColor.soft_darkblue}>
|
||||
<Grid containerStyle={{ justifyContent: "center" }}>
|
||||
<Grid.Col
|
||||
span={8}
|
||||
style={{
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<TextCustom size="xlarge" bold color="yellow">
|
||||
4567898765433567
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col
|
||||
span={4}
|
||||
style={{
|
||||
alignItems: "flex-end",
|
||||
}}
|
||||
>
|
||||
<ButtonCustom>Salin</ButtonCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
|
||||
<BaseBox>
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextCustom>Jumlah Transaksi</TextCustom>
|
||||
|
||||
<Spacing height={10} />
|
||||
|
||||
<BaseBox backgroundColor={MainColor.soft_darkblue}>
|
||||
<Grid containerStyle={{ justifyContent: "center" }}>
|
||||
<Grid.Col
|
||||
span={8}
|
||||
style={{
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<TextCustom size="xlarge" bold color="yellow">
|
||||
Rp. 1.000.000
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col
|
||||
span={4}
|
||||
style={{
|
||||
alignItems: "flex-end",
|
||||
}}
|
||||
>
|
||||
<ButtonCustom>Salin</ButtonCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
|
||||
<BaseBox>
|
||||
<StackCustom>
|
||||
<TextCustom>Upload bukti transfer anda.</TextCustom>
|
||||
<ButtonCenteredOnly
|
||||
onPress={() => {
|
||||
router.push("/(application)/(image)/take-picture/123");
|
||||
}}
|
||||
icon="upload"
|
||||
>
|
||||
Upload
|
||||
</ButtonCenteredOnly>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
|
||||
<ButtonCustom
|
||||
onPress={() => {
|
||||
router.push(`/donation/${id}/(transaction-flow)/process`);
|
||||
}}
|
||||
>
|
||||
Saya Sudah Transfer
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -9,15 +9,54 @@ import {
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
|
||||
import { LOCAL_STORAGE_KEY } from "@/constants/local-storage-key";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function InvestmentInputDonation() {
|
||||
const { user } = useAuth();
|
||||
const { id } = useLocalSearchParams();
|
||||
const [nominal, setNominal] = useState<number>(0);
|
||||
|
||||
const handlerSubmit = async () => {
|
||||
if (!user?.id) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "User tidak ditemukan",
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await AsyncStorage.setItem(
|
||||
LOCAL_STORAGE_KEY.transactionDonation,
|
||||
JSON.stringify({ nominal: nominal.toString() }),
|
||||
);
|
||||
router.replace(`/donation/${id}/select-bank`);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const displayJumlah = formatCurrencyDisplay(nominal);
|
||||
|
||||
const handleChangeCurrency = (text: string) => {
|
||||
const numeric = text.replace(/\D/g, "");
|
||||
setNominal(Number(numeric));
|
||||
};
|
||||
|
||||
const bottomComponent = (
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
onPress={() => router.replace(`/donation/${id}/select-bank`)}
|
||||
disabled={nominal < 10000 || nominal === 0}
|
||||
onPress={() => {
|
||||
handlerSubmit();
|
||||
}}
|
||||
>
|
||||
Lanjutan
|
||||
</ButtonCustom>
|
||||
@@ -27,7 +66,7 @@ export default function InvestmentInputDonation() {
|
||||
<>
|
||||
<ViewWrapper footerComponent={bottomComponent}>
|
||||
{listData.map((item, i) => (
|
||||
<BaseBox key={i}>
|
||||
<BaseBox key={i} onPress={() => setNominal(item.value)}>
|
||||
<Grid>
|
||||
<Grid.Col span={8}>
|
||||
<TextCustom bold size="large">
|
||||
@@ -48,9 +87,12 @@ export default function InvestmentInputDonation() {
|
||||
|
||||
<BaseBox>
|
||||
<TextInputCustom
|
||||
keyboardType="numeric"
|
||||
label="Nominal lainnya"
|
||||
placeholder="0"
|
||||
iconLeft="Rp."
|
||||
value={displayJumlah}
|
||||
onChangeText={(value) => handleChangeCurrency(value)}
|
||||
/>
|
||||
<TextCustom size="small" color="gray">
|
||||
Minimal donasi Rp. 10.000
|
||||
|
||||
@@ -5,24 +5,84 @@ import {
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { RadioCustom, RadioGroup } from "@/components/Radio/RadioCustom";
|
||||
import { dummyMasterBank } from "@/lib/dummy-data/_master/bank";
|
||||
import { LOCAL_STORAGE_KEY } from "@/constants/local-storage-key";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiDonationCreateInvoice } from "@/service/api-client/api-donation";
|
||||
import { apiMasterBank } from "@/service/api-client/api-master";
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import _ from "lodash";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function DonationSelectBank() {
|
||||
const { id, transaction } = useLocalSearchParams();
|
||||
const [value, setValue] = useState<any | number>("");
|
||||
const { user } = useAuth();
|
||||
const { id } = useLocalSearchParams();
|
||||
const [select, setSelect] = useState<any | number>("");
|
||||
const [listBank, setListBank] = useState<any>([]);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
loadListBank();
|
||||
}, []);
|
||||
|
||||
const loadListBank = async () => {
|
||||
try {
|
||||
const response = await apiMasterBank();
|
||||
|
||||
setListBank(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
setListBank([]);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerSubmit = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const dataStorage = await AsyncStorage.getItem(
|
||||
LOCAL_STORAGE_KEY.transactionDonation
|
||||
);
|
||||
|
||||
if (dataStorage) {
|
||||
const storage = JSON.parse(dataStorage);
|
||||
const newData = {
|
||||
...storage,
|
||||
bankId: select,
|
||||
authorId: user?.id,
|
||||
};
|
||||
|
||||
const response = await apiDonationCreateInvoice({
|
||||
id: id as string,
|
||||
data: newData,
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
const invoiceId = response.data.id;
|
||||
|
||||
await AsyncStorage.removeItem(LOCAL_STORAGE_KEY.transactionDonation);
|
||||
|
||||
router.replace(
|
||||
`/(application)/(user)/donation/[id]/(transaction-flow)/${invoiceId}/invoice`
|
||||
);
|
||||
} else {
|
||||
console.log("[FAILED]", response);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const buttonSubmit = () => {
|
||||
return (
|
||||
<>
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
onPress={() =>
|
||||
router.replace(
|
||||
`/(application)/(user)/donation/${id}/(transaction-flow)/${transaction}/invoice`
|
||||
)
|
||||
}
|
||||
isLoading={isLoading}
|
||||
disabled={!select}
|
||||
onPress={() => handlerSubmit()}
|
||||
>
|
||||
Pilih
|
||||
</ButtonCustom>
|
||||
@@ -32,12 +92,14 @@ export default function DonationSelectBank() {
|
||||
};
|
||||
return (
|
||||
<ViewWrapper footerComponent={buttonSubmit()}>
|
||||
<RadioGroup value={value} onChange={setValue}>
|
||||
{dummyMasterBank.map((item) => (
|
||||
<BaseBox key={item.name}>
|
||||
<RadioCustom label={item.name} value={item.code} />
|
||||
</BaseBox>
|
||||
))}
|
||||
<RadioGroup value={select} onChange={setSelect}>
|
||||
{_.isEmpty(listBank)
|
||||
? []
|
||||
: listBank?.map((item: any, index: number) => (
|
||||
<BaseBox key={index}>
|
||||
<RadioCustom label={item.namaBank} value={item.id} />
|
||||
</BaseBox>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</ViewWrapper>
|
||||
);
|
||||
|
||||
@@ -1,28 +1,59 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BackButton,
|
||||
DotButton,
|
||||
DrawerCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
NewWrapper,
|
||||
Spacing,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { IconEdit, IconNews } from "@/components/_Icon";
|
||||
import { IMenuDrawerItem } from "@/components/_Interface/types";
|
||||
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
|
||||
import Donation_ButtonStatusSection from "@/screens/Donation/ButtonStatusSection";
|
||||
import Donation_ComponentBoxDetailData from "@/screens/Donation/ComponentBoxDetailData";
|
||||
import Donation_ComponentStoryFunrising from "@/screens/Donation/ComponentStoryFunrising";
|
||||
import Donation_ProgressSection from "@/screens/Donation/ProgressSection";
|
||||
import { apiDonationGetOne } from "@/service/api-client/api-donation";
|
||||
import { countDownAndCondition } from "@/utils/countDownAndCondition";
|
||||
import { FontAwesome6 } from "@expo/vector-icons";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import {
|
||||
router,
|
||||
Stack,
|
||||
useFocusEffect,
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { RefreshControl } from "react-native";
|
||||
|
||||
export default function DonasiDetailStatus() {
|
||||
const { id, status } = useLocalSearchParams();
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [openDrawerPublish, setOpenDrawerPublish] = useState(false);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [data, setData] = useState<any | null>(null);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id]),
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiDonationGetOne({
|
||||
id: id as string,
|
||||
category: "permanent",
|
||||
});
|
||||
|
||||
setData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePress = (item: IMenuDrawerItem) => {
|
||||
console.log("PATH ", item.path);
|
||||
@@ -30,6 +61,38 @@ export default function DonasiDetailStatus() {
|
||||
setOpenDrawer(false);
|
||||
};
|
||||
|
||||
const [value, setValue] = useState({
|
||||
sisa: 0,
|
||||
reminder: false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
updateCountDown();
|
||||
}, [data]);
|
||||
|
||||
const updateCountDown = () => {
|
||||
const countDown = countDownAndCondition({
|
||||
duration: data?.DonasiMaster_Durasi?.name,
|
||||
publishTime: data?.publishTime,
|
||||
});
|
||||
|
||||
setValue({
|
||||
sisa: countDown.durationDay,
|
||||
reminder: countDown.reminder,
|
||||
});
|
||||
};
|
||||
|
||||
const onRefresh = useCallback(() => {
|
||||
try {
|
||||
setRefreshing(true);
|
||||
onLoadData();
|
||||
} catch (error) {
|
||||
console.log("Error refresh");
|
||||
} finally {
|
||||
setRefreshing(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
@@ -44,17 +107,50 @@ export default function DonasiDetailStatus() {
|
||||
) : null,
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
<Donation_ComponentBoxDetailData
|
||||
bottomSection={
|
||||
status === "publish" && <Donation_ProgressSection id={id as string} />
|
||||
}
|
||||
/>
|
||||
<Donation_ComponentStoryFunrising id={id as string} />
|
||||
<Spacing />
|
||||
<Donation_ButtonStatusSection status={status as string} />
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
<NewWrapper
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={onRefresh}
|
||||
tintColor={MainColor.yellow}
|
||||
colors={[MainColor.yellow]}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{!data ? (
|
||||
<CustomSkeleton height={400} />
|
||||
) : (
|
||||
<>
|
||||
<Donation_ComponentBoxDetailData
|
||||
sisaHari={value.sisa}
|
||||
reminder={value.reminder}
|
||||
data={data}
|
||||
showSisaHari={status === "publish" ? true : false}
|
||||
bottomSection={
|
||||
status === "publish" && (
|
||||
<Donation_ProgressSection
|
||||
id={id as string}
|
||||
progres={Number(data?.progres) || 0}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Donation_ComponentStoryFunrising
|
||||
id={id as string}
|
||||
dataStory={data?.CeritaDonasi}
|
||||
/>
|
||||
<Spacing />
|
||||
{data && (
|
||||
<Donation_ButtonStatusSection
|
||||
id={id as string}
|
||||
status={status as string}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Spacing />
|
||||
</>
|
||||
)}
|
||||
</NewWrapper>
|
||||
|
||||
<DrawerCustom
|
||||
isVisible={openDrawer}
|
||||
|
||||
@@ -1,27 +1,42 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
DummyLandscapeImage,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import { apiDonationGetOne } from "@/service/api-client/api-donation";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function DonationDetailStory() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [data, setData] = useState<any>();
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiDonationGetOne({
|
||||
id: id as string,
|
||||
category: "permanent",
|
||||
});
|
||||
|
||||
setData(response.data.CeritaDonasi);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<StackCustom>
|
||||
<TextCustom>
|
||||
Lorem {id} ipsum dolor, sit amet consectetur adipisicing elit. Fuga
|
||||
quasi nam nesciunt nisi corporis alias modi, pariatur sit totam rem
|
||||
fugiat ex similique magni, aliquam maiores officiis iure at adipisci.
|
||||
</TextCustom>
|
||||
<DummyLandscapeImage />
|
||||
<TextCustom>
|
||||
Lorem {id} ipsum dolor, sit amet consectetur adipisicing elit. Fuga
|
||||
quasi nam nesciunt nisi corporis alias modi, pariatur sit totam rem
|
||||
fugiat ex similique magni, aliquam maiores officiis iure at adipisci.
|
||||
</TextCustom>
|
||||
<TextCustom>{data?.pembukaan || "-"}</TextCustom>
|
||||
<DummyLandscapeImage imageId={data?.imageId} />
|
||||
<TextCustom>{data?.cerita || "-"}</TextCustom>
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,80 @@
|
||||
import { ViewWrapper, StackCustom, InformationBox, TextInputCustom, Spacing, ButtonCustom } from "@/components";
|
||||
import { router } from "expo-router";
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
ViewWrapper,
|
||||
StackCustom,
|
||||
InformationBox,
|
||||
TextInputCustom,
|
||||
Spacing,
|
||||
ButtonCustom,
|
||||
} from "@/components";
|
||||
import {
|
||||
apiDonationGetOne,
|
||||
apiDonationUpdateData,
|
||||
} from "@/service/api-client/api-donation";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import { useEffect, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function DonationEditRekening() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [data, setData] = useState({
|
||||
namaBank: "",
|
||||
rekening: "",
|
||||
});
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
onLoadData();
|
||||
}, [id]);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiDonationGetOne({
|
||||
id: id as string,
|
||||
category: "permanent",
|
||||
});
|
||||
|
||||
const resData = response.data;
|
||||
console.log("[RESPONSE]", JSON.stringify(resData, null, 2));
|
||||
|
||||
setData({
|
||||
namaBank: resData.namaBank,
|
||||
rekening: resData.rekening,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerSubmitUpdate = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
const response = await apiDonationUpdateData({
|
||||
id: id as string,
|
||||
data: data,
|
||||
category: "edit-bank-account",
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal mengupdate data bank",
|
||||
});
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Data bank berhasil diupdate",
|
||||
});
|
||||
router.back();
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<StackCustom gap={"xs"}>
|
||||
@@ -10,17 +83,22 @@ export default function DonationEditRekening() {
|
||||
label="Nama Bank"
|
||||
placeholder="Masukkan nama bank"
|
||||
required
|
||||
value={data.namaBank}
|
||||
onChangeText={(value) => setData({ ...data, namaBank: value })}
|
||||
/>
|
||||
<TextInputCustom
|
||||
label="Nomor Rekening"
|
||||
placeholder="Masukkan nomor rekening"
|
||||
required
|
||||
value={data.rekening}
|
||||
onChangeText={(value) => setData({ ...data, rekening: value })}
|
||||
/>
|
||||
|
||||
<Spacing />
|
||||
<ButtonCustom
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
router.back();
|
||||
handlerSubmitUpdate();
|
||||
}}
|
||||
>
|
||||
Update
|
||||
@@ -29,4 +107,4 @@ export default function DonationEditRekening() {
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,97 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
InformationBox,
|
||||
LandscapeFrameUploaded,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextAreaCustom,
|
||||
ViewWrapper
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
InformationBox,
|
||||
LandscapeFrameUploaded,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextAreaCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { router } from "expo-router";
|
||||
import API_IMAGE from "@/constants/api-storage";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
import {
|
||||
apiDonationGetOne,
|
||||
apiDonationUpdateData,
|
||||
} from "@/service/api-client/api-donation";
|
||||
import { uploadFileService } from "@/service/upload-service";
|
||||
import pickFile from "@/utils/pickFile";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import { useEffect, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function DonationEditStory() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [data, setData] = useState<any>();
|
||||
const [imageStory, setImageStory] = useState<string | null>(null);
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
onLoadData();
|
||||
}, [id]);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiDonationGetOne({
|
||||
id: id as string,
|
||||
category: "permanent",
|
||||
});
|
||||
|
||||
setData(response.data.CeritaDonasi);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerSubmitUpdate = async () => {
|
||||
let newData;
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
newData = {
|
||||
...data,
|
||||
};
|
||||
|
||||
if (imageStory) {
|
||||
const responseUploadImageDonasi = await uploadFileService({
|
||||
imageUri: imageStory,
|
||||
dirId: DIRECTORY_ID.donasi_cerita_image,
|
||||
});
|
||||
|
||||
newData = {
|
||||
...data,
|
||||
newImageId: responseUploadImageDonasi.data.id,
|
||||
};
|
||||
}
|
||||
|
||||
const response = await apiDonationUpdateData({
|
||||
id: id as string,
|
||||
data: newData,
|
||||
category: "edit-story",
|
||||
});
|
||||
|
||||
console.log("[RESPONSE]", JSON.stringify(response, null, 2));
|
||||
|
||||
if (!response.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal membuat donasi",
|
||||
});
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Donasi berhasil disimpan",
|
||||
});
|
||||
router.back();
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<StackCustom gap={"xs"}>
|
||||
@@ -21,12 +102,23 @@ export default function DonationEditStory() {
|
||||
required
|
||||
showCount
|
||||
maxLength={1000}
|
||||
value={data?.pembukaan}
|
||||
onChangeText={(value) => setData({ ...data, pembukaan: value })}
|
||||
/>
|
||||
|
||||
<LandscapeFrameUploaded />
|
||||
<LandscapeFrameUploaded
|
||||
image={
|
||||
imageStory ? imageStory : API_IMAGE.GET({ fileId: data?.imageId })
|
||||
}
|
||||
/>
|
||||
<ButtonCenteredOnly
|
||||
onPress={() => {
|
||||
router.push("/(application)/(image)/take-picture/123");
|
||||
pickFile({
|
||||
allowedType: "image",
|
||||
setImageUri: ({ uri }) => {
|
||||
setImageStory(uri);
|
||||
},
|
||||
});
|
||||
}}
|
||||
icon="upload"
|
||||
>
|
||||
@@ -39,12 +131,15 @@ export default function DonationEditStory() {
|
||||
required
|
||||
showCount
|
||||
maxLength={1000}
|
||||
value={data?.cerita}
|
||||
onChangeText={(value) => setData({ ...data, cerita: value })}
|
||||
/>
|
||||
|
||||
<Spacing height={40} />
|
||||
<ButtonCustom
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
router.back();
|
||||
handlerSubmitUpdate();
|
||||
}}
|
||||
>
|
||||
Update
|
||||
|
||||
@@ -1,79 +1,284 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
InformationBox,
|
||||
LandscapeFrameUploaded,
|
||||
SelectCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
BoxButtonOnFooter,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
InformationBox,
|
||||
LandscapeFrameUploaded,
|
||||
LoaderCustom,
|
||||
NewWrapper,
|
||||
SelectCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { dummyDonasiDurasi } from "@/lib/dummy-data/donasi/durasi";
|
||||
import { dummyDonasiKategori } from "@/lib/dummy-data/donasi/kategori";
|
||||
import { router } from "expo-router";
|
||||
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
|
||||
import API_IMAGE from "@/constants/api-storage";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
import {
|
||||
apiDonationGetOne,
|
||||
apiDonationUpdateData,
|
||||
} from "@/service/api-client/api-donation";
|
||||
import { apiMasterDonation } from "@/service/api-client/api-master";
|
||||
import { uploadFileService } from "@/service/upload-service";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import pickFile from "@/utils/pickFile";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
interface IEditDonation {
|
||||
donasiMaster_KategoriId: string;
|
||||
donasiMaster_DurasiId: string;
|
||||
title: string;
|
||||
target: string;
|
||||
imageId: string;
|
||||
}
|
||||
|
||||
export default function DonationEdit() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [data, setData] = useState<IEditDonation>({
|
||||
donasiMaster_DurasiId: "",
|
||||
donasiMaster_KategoriId: "",
|
||||
title: "",
|
||||
target: "",
|
||||
imageId: "",
|
||||
});
|
||||
|
||||
const [image, setImage] = useState<string | null>(null);
|
||||
const [listCategory, setListCategory] = useState<any[]>([]);
|
||||
const [listDuration, setListDuration] = useState<any[]>([]);
|
||||
const [loadList, setLoadList] = useState<boolean>(false);
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
const displayTarget = formatCurrencyDisplay(data?.target);
|
||||
const handleChangeCurrency = (field: keyof typeof data) => (text: string) => {
|
||||
const numeric = text.replace(/\D/g, "");
|
||||
setData((prev: any) => ({ ...prev, [field]: numeric }));
|
||||
};
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
onLoadList();
|
||||
}, [id]),
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiDonationGetOne({
|
||||
id: id as string,
|
||||
category: "permanent",
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
setData({
|
||||
donasiMaster_DurasiId: response.data.donasiMaster_DurasiId,
|
||||
donasiMaster_KategoriId: response.data.donasiMaster_KategoriId,
|
||||
title: response.data.title,
|
||||
target: response.data.target,
|
||||
imageId: response.data.imageId,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const onLoadList = async () => {
|
||||
try {
|
||||
setLoadList(true);
|
||||
const response = await apiMasterDonation({ category: "" });
|
||||
|
||||
setListCategory(response.data.category);
|
||||
setListDuration(response.data.duration);
|
||||
} catch (error) {
|
||||
console.log(["ERROR"], error);
|
||||
setListCategory([]);
|
||||
setListDuration([]);
|
||||
} finally {
|
||||
setLoadList(false);
|
||||
}
|
||||
};
|
||||
|
||||
const validateData = async () => {
|
||||
if (
|
||||
!data.donasiMaster_DurasiId ||
|
||||
!data.donasiMaster_KategoriId ||
|
||||
!data.title ||
|
||||
!data.target ||
|
||||
!data.imageId
|
||||
) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Harap lengkapi data",
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const handlerSubmitUpdate = async () => {
|
||||
const isValid = await validateData();
|
||||
if (!isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
let newData;
|
||||
|
||||
newData = {
|
||||
...data,
|
||||
};
|
||||
setLoading(true);
|
||||
|
||||
if (image && image) {
|
||||
const uploadNewImage = await uploadFileService({
|
||||
dirId: DIRECTORY_ID.donasi_image,
|
||||
imageUri: image,
|
||||
});
|
||||
|
||||
if (!uploadFileService) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal mengunggah gambar",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
newData = {
|
||||
...data,
|
||||
newImageId: uploadNewImage.data.id,
|
||||
};
|
||||
}
|
||||
|
||||
const response = await apiDonationUpdateData({
|
||||
id: id as string,
|
||||
data: newData,
|
||||
category: "edit-donation",
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: response.message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Donasi berhasil diperbarui",
|
||||
});
|
||||
|
||||
router.back();
|
||||
} catch (error) {
|
||||
console.log("[ERROR UPDATE DONASI]", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<StackCustom gap={"xs"}>
|
||||
<InformationBox text="Lengkapi semua data di bawah untuk selanjutnya mengisi cerita penggalangan dana." />
|
||||
<NewWrapper
|
||||
hideFooter
|
||||
footerComponent={
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
handlerSubmitUpdate();
|
||||
}}
|
||||
>
|
||||
Update
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
}
|
||||
>
|
||||
<InformationBox text="Lengkapi semua data di bawah untuk selanjutnya mengisi cerita penggalangan dana." />
|
||||
{!data || loadList ? (
|
||||
<ListSkeletonComponent />
|
||||
) : (
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextInputCustom
|
||||
label="Judul Donasi"
|
||||
placeholder="Masukkan Judul Donasi"
|
||||
required
|
||||
value={data?.title}
|
||||
onChangeText={(value) => setData({ ...data, title: value })}
|
||||
/>
|
||||
<TextInputCustom
|
||||
iconLeft="Rp."
|
||||
label="Target Donasi"
|
||||
placeholder="Masukkan Target Donasi"
|
||||
required
|
||||
keyboardType="numeric"
|
||||
value={displayTarget}
|
||||
onChangeText={handleChangeCurrency("target")}
|
||||
/>
|
||||
|
||||
<TextInputCustom
|
||||
label="Judul Donasi"
|
||||
placeholder="Masukkan Judul Donasi"
|
||||
required
|
||||
/>
|
||||
<TextInputCustom
|
||||
label="Target Donasi"
|
||||
placeholder="Masukkan Target Donasi"
|
||||
required
|
||||
keyboardType="numeric"
|
||||
/>
|
||||
<LandscapeFrameUploaded
|
||||
image={image ? image : API_IMAGE.GET({ fileId: data?.imageId })}
|
||||
/>
|
||||
<ButtonCenteredOnly
|
||||
onPress={() => {
|
||||
pickFile({
|
||||
setImageUri: ({ uri }) => {
|
||||
setImage(uri);
|
||||
},
|
||||
allowedType: "image",
|
||||
});
|
||||
}}
|
||||
icon="upload"
|
||||
>
|
||||
Upload
|
||||
</ButtonCenteredOnly>
|
||||
<Spacing />
|
||||
|
||||
<LandscapeFrameUploaded />
|
||||
<ButtonCenteredOnly
|
||||
onPress={() => {
|
||||
router.push("/(application)/(image)/take-picture/123");
|
||||
}}
|
||||
icon="upload"
|
||||
>
|
||||
Upload
|
||||
</ButtonCenteredOnly>
|
||||
<Spacing />
|
||||
<SelectCustom
|
||||
data={
|
||||
_.isEmpty(listCategory)
|
||||
? []
|
||||
: listCategory?.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))
|
||||
}
|
||||
label="Pilih Kategori Donasi"
|
||||
placeholder="Pilih Kategori Donasi"
|
||||
required
|
||||
value={data?.donasiMaster_KategoriId}
|
||||
onChange={(value: any) =>
|
||||
setData({ ...data, donasiMaster_KategoriId: value })
|
||||
}
|
||||
/>
|
||||
|
||||
<SelectCustom
|
||||
data={dummyDonasiKategori.map((item) => ({
|
||||
label: item.label,
|
||||
value: item.value,
|
||||
}))}
|
||||
onChange={(value) => console.log(value)}
|
||||
label="Pilih Kategori Donasi"
|
||||
placeholder="Pilih Kategori Donasi"
|
||||
required
|
||||
/>
|
||||
<SelectCustom
|
||||
data={
|
||||
_.isEmpty(listDuration)
|
||||
? []
|
||||
: listDuration?.map((item) => ({
|
||||
label: item.name + " hari",
|
||||
value: item.id,
|
||||
}))
|
||||
}
|
||||
label="Pilih Durasi Donasi"
|
||||
placeholder="Pilih Durasi Donasi"
|
||||
required
|
||||
value={data?.donasiMaster_DurasiId}
|
||||
onChange={(value: any) =>
|
||||
setData({ ...data, donasiMaster_DurasiId: value })
|
||||
}
|
||||
/>
|
||||
|
||||
<SelectCustom
|
||||
data={dummyDonasiDurasi.map((item) => ({
|
||||
label: item.label,
|
||||
value: item.value,
|
||||
}))}
|
||||
onChange={(value) => console.log(value)}
|
||||
label="Pilih Durasi Donasi"
|
||||
placeholder="Pilih Durasi Donasi"
|
||||
required
|
||||
/>
|
||||
|
||||
<Spacing />
|
||||
<ButtonCustom
|
||||
onPress={() => {
|
||||
router.back();
|
||||
}}
|
||||
>
|
||||
Update
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
<Spacing />
|
||||
</StackCustom>
|
||||
)}
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
</NewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,67 +1,8 @@
|
||||
import {
|
||||
BaseBox,
|
||||
ButtonCenteredOnly,
|
||||
Grid,
|
||||
InformationBox,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import dayjs from "dayjs";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import Donation_ScreenFundDisbursement from "@/screens/Donation/ScreenFundDisbursement";
|
||||
|
||||
export default function DonationFundDisbursement() {
|
||||
const { id } = useLocalSearchParams();
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
<InformationBox text="Pencairan dana akan dilakukan oleh Admin HIPMI tanpa campur tangan pihak manapun, jika berita pencairan dana dibawah tidak sesuai dengan kabar yang diberikan oleh PENGGALANG DANA. Maka pegguna lain dapat melaporkannya pada Admin HIPMI !" />
|
||||
<BaseBox>
|
||||
<Grid>
|
||||
<Grid.Col span={6}>
|
||||
<TextCustom bold color="yellow">
|
||||
Rp. 0
|
||||
</TextCustom>
|
||||
<TextCustom size="small">Total Pencairan Dana</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
<TextCustom bold color="yellow">
|
||||
0 kali
|
||||
</TextCustom>
|
||||
<TextCustom size="small">Akumulasi Pencairan</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<BaseBox key={index}>
|
||||
<StackCustom>
|
||||
<Grid>
|
||||
<Grid.Col span={8}>
|
||||
<TextCustom bold>Pencairan ke - {index + 1}</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={4} style={{ alignItems: "flex-end" }}>
|
||||
<TextCustom>{dayjs().format("DD MMM YYYY")}</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
<TextCustom>
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit.
|
||||
Nesciunt dolor ad sit? Eaque rem nihil natus, id, esse possimus
|
||||
perferendis provident velit illo consectetur distinctio ab
|
||||
accusantium quis earum omnis!
|
||||
</TextCustom>
|
||||
<ButtonCenteredOnly
|
||||
onPress={() => {
|
||||
router.navigate(`/(application)/(file)/${id}`);
|
||||
}}
|
||||
icon="file-text"
|
||||
>
|
||||
Bukti Transaksi
|
||||
</ButtonCenteredOnly>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
))}
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
|
||||
return <Donation_ScreenFundDisbursement donationId={id as string} />;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BackButton,
|
||||
BoxButtonOnFooter,
|
||||
@@ -5,30 +6,81 @@ import {
|
||||
DotButton,
|
||||
DrawerCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
NewWrapper,
|
||||
StackCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { IconNews } from "@/components/_Icon";
|
||||
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import Donation_ComponentBoxDetailData from "@/screens/Donation/ComponentBoxDetailData";
|
||||
import Donation_ComponentInfoFundrising from "@/screens/Donation/ComponentInfoFundrising";
|
||||
import Donation_ComponentStoryFunrising from "@/screens/Donation/ComponentStoryFunrising";
|
||||
import Donation_ProgressSection from "@/screens/Donation/ProgressSection";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { apiDonationGetOne } from "@/service/api-client/api-donation";
|
||||
import { countDownAndCondition } from "@/utils/countDownAndCondition";
|
||||
import {
|
||||
router,
|
||||
Stack,
|
||||
useFocusEffect,
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
export default function DonasiDetailBeranda() {
|
||||
const { user } = useAuth();
|
||||
const { id } = useLocalSearchParams();
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [data, setData] = useState<any>();
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id]),
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiDonationGetOne({
|
||||
id: id as string,
|
||||
category: "permanent",
|
||||
});
|
||||
|
||||
setData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const [value, setValue] = useState({
|
||||
sisa: 0,
|
||||
reminder: false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
updateCountDown();
|
||||
}, [data]);
|
||||
|
||||
const updateCountDown = () => {
|
||||
const countDown = countDownAndCondition({
|
||||
duration: data?.DonasiMaster_Durasi?.name,
|
||||
publishTime: data?.publishTime,
|
||||
});
|
||||
|
||||
setValue({
|
||||
sisa: countDown.durationDay,
|
||||
reminder: countDown.reminder,
|
||||
});
|
||||
};
|
||||
|
||||
const buttonSection = (
|
||||
<>
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
onPress={() =>
|
||||
router.navigate(`/donation/${id}/(transaction-flow)`)
|
||||
}
|
||||
disabled={value?.reminder || !data}
|
||||
onPress={() => router.navigate(`/donation/${id}/(transaction-flow)`)}
|
||||
>
|
||||
Donasi
|
||||
{!data ? "Loading..." : value?.reminder ? "Waktu berakhir" : "Donasi"}
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
</>
|
||||
@@ -40,18 +92,36 @@ export default function DonasiDetailBeranda() {
|
||||
options={{
|
||||
title: `Detail Donasi`,
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
|
||||
headerRight: () =>
|
||||
user?.id === data?.Author?.id ? (
|
||||
<DotButton onPress={() => setOpenDrawer(true)} />
|
||||
) : null,
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper footerComponent={buttonSection}>
|
||||
<StackCustom>
|
||||
<Donation_ComponentBoxDetailData
|
||||
bottomSection={<Donation_ProgressSection id={id as string} />}
|
||||
/>
|
||||
<Donation_ComponentInfoFundrising id={id as string} />
|
||||
<Donation_ComponentStoryFunrising id={id as string} />
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
<NewWrapper footerComponent={buttonSection}>
|
||||
{!data ? (
|
||||
<CustomSkeleton height={400} />
|
||||
) : (
|
||||
<StackCustom>
|
||||
<Donation_ComponentBoxDetailData
|
||||
sisaHari={value.sisa}
|
||||
reminder={value.reminder}
|
||||
data={data}
|
||||
bottomSection={
|
||||
<Donation_ProgressSection
|
||||
id={id as string}
|
||||
progres={Number(data?.progres) || 0}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Donation_ComponentInfoFundrising dataAuthor={data?.Author} />
|
||||
<Donation_ComponentStoryFunrising
|
||||
id={id as string}
|
||||
dataStory={data?.CeritaDonasi}
|
||||
/>
|
||||
</StackCustom>
|
||||
)}
|
||||
</NewWrapper>
|
||||
|
||||
<DrawerCustom
|
||||
isVisible={openDrawer}
|
||||
|
||||
@@ -1,32 +1,68 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AvatarCustom,
|
||||
AvatarComp,
|
||||
BaseBox,
|
||||
ButtonCustom,
|
||||
CenterCustom,
|
||||
Grid,
|
||||
LoaderCustom,
|
||||
Spacing,
|
||||
TextCustom,
|
||||
ViewWrapper
|
||||
} from "@/components";
|
||||
import Donation_BoxPublish from "@/screens/Donation/BoxPublish";
|
||||
import React from "react";
|
||||
import { apiDonationFundrising } from "@/service/api-client/api-donation";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
|
||||
export default function DonationInformationFunrising() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [data, setData] = useState<any>();
|
||||
const [list, setList] = useState<any[] | null>(null);
|
||||
const [loadList, setLoadList] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setLoadList(true);
|
||||
const response = await apiDonationFundrising({ id: id as string });
|
||||
|
||||
setData(response?.data?.user);
|
||||
setList(response?.data?.donasi);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadList(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
<BaseBox>
|
||||
<Grid>
|
||||
<Grid.Col span={6} style={{ justifyContent: "center" }}>
|
||||
<CenterCustom>
|
||||
<AvatarCustom size="lg" />
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
gap: 10,
|
||||
}}
|
||||
>
|
||||
<AvatarComp size="lg" fileId={data?.Profile?.imageId} />
|
||||
<TextCustom bold size="large" truncate>
|
||||
@Username
|
||||
@{data?.username}
|
||||
</TextCustom>
|
||||
</CenterCustom>
|
||||
</View>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6} style={{ justifyContent: "center" }}>
|
||||
<ButtonCustom href={`/profile/1234`}>
|
||||
<ButtonCustom href={`/profile/${data?.Profile?.id}`}>
|
||||
Kunjungi Profile
|
||||
</ButtonCustom>
|
||||
</Grid.Col>
|
||||
@@ -35,9 +71,15 @@ export default function DonationInformationFunrising() {
|
||||
|
||||
<Spacing />
|
||||
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<Donation_BoxPublish key={index} id={index.toString()} />
|
||||
))}
|
||||
{loadList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(list) ? (
|
||||
<TextCustom align="center" color="gray" size="small">Belum ada data</TextCustom>
|
||||
) : (
|
||||
list?.map((item: any, index: number) => (
|
||||
<Donation_BoxPublish key={index} id={item?.id} data={item} />
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,47 +1,8 @@
|
||||
import {
|
||||
BaseBox,
|
||||
Grid,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { FontAwesome6 } from "@expo/vector-icons";
|
||||
import dayjs from "dayjs";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import Donation_ScreenListOfDonatur from "@/screens/Donation/ScreenListOfDonatur";
|
||||
|
||||
export default function Donation_ListOfDonatur() {
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<BaseBox key={index}>
|
||||
<Grid>
|
||||
<Grid.Col
|
||||
span={3}
|
||||
style={{ alignItems: "center", justifyContent: "center" }}
|
||||
>
|
||||
<FontAwesome6
|
||||
name="face-smile-wink"
|
||||
size={50}
|
||||
style={{ color: MainColor.yellow }}
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={9}>
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextCustom bold size="large">
|
||||
Username
|
||||
</TextCustom>
|
||||
<TextCustom>Berdonas sebesar </TextCustom>
|
||||
<TextCustom bold size="large" color="yellow">
|
||||
Rp. 100.000
|
||||
</TextCustom>
|
||||
<TextCustom>{dayjs().format("DD MMM YYYY")}</TextCustom>
|
||||
</StackCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
))}
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
export default function DonationListOfDonatur() {
|
||||
const { id } = useLocalSearchParams();
|
||||
|
||||
return <Donation_ScreenListOfDonatur donationId={id as string} />;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
InformationBox,
|
||||
@@ -7,13 +9,127 @@ import {
|
||||
StackCustom,
|
||||
TextAreaCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { router } from "expo-router";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import {
|
||||
apiDonationCreate,
|
||||
apiDonationGetOne,
|
||||
} from "@/service/api-client/api-donation";
|
||||
import { uploadFileService } from "@/service/upload-service";
|
||||
import pickFile from "@/utils/pickFile";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useEffect, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function DonationCreateStory() {
|
||||
const { user } = useAuth();
|
||||
const { id } = useLocalSearchParams();
|
||||
const [temporary, setTemporary] = useState<any>();
|
||||
const [data, setData] = useState({
|
||||
pembukaan: "",
|
||||
cerita: "",
|
||||
namaBank: "",
|
||||
rekening: "",
|
||||
});
|
||||
const [imageStory, setImageStory] = useState<string | null>(null);
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
onLoadData();
|
||||
}, [id]);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiDonationGetOne({
|
||||
id: id as string,
|
||||
category: "temporary",
|
||||
});
|
||||
|
||||
setTemporary(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerSubmit = async () => {
|
||||
if (_.values(data).includes("")) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Harap isi semua data",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
const responseUploadImageDonasi = await uploadFileService({
|
||||
imageUri: imageStory,
|
||||
dirId: DIRECTORY_ID.donasi_cerita_image,
|
||||
});
|
||||
|
||||
const newData = {
|
||||
// Data Donasi
|
||||
temporaryId: temporary?.id,
|
||||
authorId: user?.id,
|
||||
title: temporary?.title,
|
||||
target: temporary?.target,
|
||||
donasiMaster_KategoriId: temporary?.donasiMaster_KategoriId,
|
||||
donasiMaster_DurasiId: temporary?.donasiMaster_DurasiId,
|
||||
imageId: temporary?.imageId,
|
||||
// Data Bank
|
||||
namaBank: data.namaBank,
|
||||
rekening: data.rekening,
|
||||
// Data Cerita
|
||||
imageCeritaId: responseUploadImageDonasi.data.id,
|
||||
pembukaan: data.pembukaan,
|
||||
cerita: data.cerita,
|
||||
};
|
||||
|
||||
const response = await apiDonationCreate({
|
||||
data: newData,
|
||||
category: "permanent",
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal membuat donasi",
|
||||
});
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Donasi berhasil disimpan",
|
||||
});
|
||||
router.replace("/donation/status?status=review");
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<NewWrapper
|
||||
hideFooter
|
||||
footerComponent={
|
||||
<>
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
handlerSubmit();
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<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
|
||||
@@ -22,6 +138,8 @@ export default function DonationCreateStory() {
|
||||
required
|
||||
showCount
|
||||
maxLength={1000}
|
||||
value={data.pembukaan}
|
||||
onChangeText={(value) => setData({ ...data, pembukaan: value })}
|
||||
/>
|
||||
<TextAreaCustom
|
||||
label="Tujuan Donasi"
|
||||
@@ -29,12 +147,19 @@ export default function DonationCreateStory() {
|
||||
required
|
||||
showCount
|
||||
maxLength={1000}
|
||||
value={data.cerita}
|
||||
onChangeText={(value) => setData({ ...data, cerita: value })}
|
||||
/>
|
||||
|
||||
<LandscapeFrameUploaded />
|
||||
<LandscapeFrameUploaded image={imageStory || ""} />
|
||||
<ButtonCenteredOnly
|
||||
onPress={() => {
|
||||
router.push("/(application)/(image)/take-picture/123");
|
||||
pickFile({
|
||||
allowedType: "image",
|
||||
setImageUri: ({ uri }) => {
|
||||
setImageStory(uri);
|
||||
},
|
||||
});
|
||||
}}
|
||||
icon="upload"
|
||||
>
|
||||
@@ -47,23 +172,19 @@ export default function DonationCreateStory() {
|
||||
label="Nama Bank"
|
||||
placeholder="Masukkan nama bank"
|
||||
required
|
||||
value={data.namaBank}
|
||||
onChangeText={(value) => setData({ ...data, namaBank: value })}
|
||||
/>
|
||||
<TextInputCustom
|
||||
label="Nomor Rekening"
|
||||
placeholder="Masukkan nomor rekening"
|
||||
required
|
||||
keyboardType="numeric"
|
||||
value={data.rekening}
|
||||
onChangeText={(value) => setData({ ...data, rekening: value })}
|
||||
/>
|
||||
|
||||
<Spacing />
|
||||
<ButtonCustom
|
||||
onPress={() => {
|
||||
router.replace(`/donation/(tabs)/status`);
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
</NewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,149 @@
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
InformationBox,
|
||||
LandscapeFrameUploaded,
|
||||
LoaderCustom,
|
||||
SelectCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { dummyDonasiDurasi } from "@/lib/dummy-data/donasi/durasi";
|
||||
import { dummyDonasiKategori } from "@/lib/dummy-data/donasi/kategori";
|
||||
import { router } from "expo-router";
|
||||
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";
|
||||
import { uploadFileService } from "@/service/upload-service";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import pickFile from "@/utils/pickFile";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function DonationCreate() {
|
||||
const [listCategory, setListCategory] = useState<any[]>([]);
|
||||
const [listDuration, setListDuration] = useState<any[]>([]);
|
||||
const [loadList, setLoadList] = useState<boolean>(false);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [image, setImage] = useState<string | null>(null);
|
||||
const [data, setData] = useState({
|
||||
kategoriId: "",
|
||||
title: "",
|
||||
target: "",
|
||||
durasiId: "",
|
||||
});
|
||||
|
||||
const displayTarget = formatCurrencyDisplay(data.target);
|
||||
const handleChangeCurrency = (field: keyof typeof data) => (text: string) => {
|
||||
const numeric = text.replace(/\D/g, "");
|
||||
setData((prev) => ({ ...prev, [field]: numeric }));
|
||||
};
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadList();
|
||||
}, []),
|
||||
);
|
||||
|
||||
const onLoadList = async () => {
|
||||
try {
|
||||
setLoadList(true);
|
||||
const response = await apiMasterDonation({ category: "" });
|
||||
|
||||
setListCategory(response.data.category);
|
||||
setListDuration(response.data.duration);
|
||||
} catch (error) {
|
||||
console.log(["ERROR"], error);
|
||||
setListCategory([]);
|
||||
setListDuration([]);
|
||||
} finally {
|
||||
setLoadList(false);
|
||||
}
|
||||
};
|
||||
|
||||
const validateData = () => {
|
||||
if (!data.title || !data.target || !data.durasiId || !data.kategoriId) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Harap isi semua data",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const handlerSubmit = async () => {
|
||||
if (!validateData()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const responseUploadImage = await uploadFileService({
|
||||
imageUri: image,
|
||||
dirId: DIRECTORY_ID.donasi_image,
|
||||
});
|
||||
|
||||
if (!responseUploadImage.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal mengunggah gambar",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const imageId = responseUploadImage.data.id;
|
||||
|
||||
const newData = {
|
||||
title: data.title,
|
||||
target: data.target,
|
||||
durasiId: data.durasiId,
|
||||
kategoriId: data.kategoriId,
|
||||
imageId: imageId,
|
||||
};
|
||||
|
||||
const response = await apiDonationCreate({
|
||||
data: newData,
|
||||
category: "temporary",
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal membuat donasi",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
router.push(`/donation/create-story?id=${response.data.id}`);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<NewWrapper
|
||||
hideFooter
|
||||
footerComponent={
|
||||
<>
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
handlerSubmit();
|
||||
// router.push(`/donation/create-story?id=${"dasdsadsa"}`);
|
||||
}}
|
||||
>
|
||||
Selanjutnya
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<StackCustom gap={"xs"}>
|
||||
<InformationBox text="Lengkapi semua data di bawah untuk selanjutnya mengisi cerita penggalangan dana." />
|
||||
|
||||
@@ -23,18 +151,28 @@ export default function DonationCreate() {
|
||||
label="Judul Donasi"
|
||||
placeholder="Masukkan Judul Donasi"
|
||||
required
|
||||
value={data.title}
|
||||
onChangeText={(value) => setData({ ...data, title: value })}
|
||||
/>
|
||||
<TextInputCustom
|
||||
iconLeft="Rp."
|
||||
label="Target Donasi"
|
||||
placeholder="Masukkan Target Donasi"
|
||||
required
|
||||
keyboardType="numeric"
|
||||
value={displayTarget}
|
||||
onChangeText={handleChangeCurrency("target")}
|
||||
/>
|
||||
|
||||
<LandscapeFrameUploaded />
|
||||
<LandscapeFrameUploaded image={image || ""} />
|
||||
<ButtonCenteredOnly
|
||||
onPress={() => {
|
||||
router.push("/(application)/(image)/take-picture/123");
|
||||
pickFile({
|
||||
allowedType: "image",
|
||||
setImageUri: ({ uri }) => {
|
||||
setImage(uri);
|
||||
},
|
||||
});
|
||||
}}
|
||||
icon="upload"
|
||||
>
|
||||
@@ -42,38 +180,47 @@ export default function DonationCreate() {
|
||||
</ButtonCenteredOnly>
|
||||
<Spacing />
|
||||
|
||||
<SelectCustom
|
||||
data={dummyDonasiKategori.map((item) => ({
|
||||
label: item.label,
|
||||
value: item.value,
|
||||
}))}
|
||||
onChange={(value) => console.log(value)}
|
||||
label="Pilih Kategori Donasi"
|
||||
placeholder="Pilih Kategori Donasi"
|
||||
required
|
||||
/>
|
||||
{loadList ? (
|
||||
<LoaderCustom />
|
||||
) : (
|
||||
<SelectCustom
|
||||
data={
|
||||
_.isEmpty(listCategory)
|
||||
? []
|
||||
: listCategory?.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))
|
||||
}
|
||||
label="Pilih Kategori Donasi"
|
||||
placeholder="Pilih Kategori Donasi"
|
||||
required
|
||||
value={data.kategoriId}
|
||||
onChange={(value: any) => setData({ ...data, kategoriId: value })}
|
||||
/>
|
||||
)}
|
||||
|
||||
<SelectCustom
|
||||
data={dummyDonasiDurasi.map((item) => ({
|
||||
label: item.label,
|
||||
value: item.value,
|
||||
}))}
|
||||
onChange={(value) => console.log(value)}
|
||||
label="Pilih Durasi Donasi"
|
||||
placeholder="Pilih Durasi Donasi"
|
||||
required
|
||||
/>
|
||||
<Spacing />
|
||||
<ButtonCustom
|
||||
onPress={() => {
|
||||
router.replace("/donation/create-story");
|
||||
}}
|
||||
>
|
||||
Selanjutnya
|
||||
</ButtonCustom>
|
||||
<Spacing />
|
||||
{loadList ? (
|
||||
<LoaderCustom />
|
||||
) : (
|
||||
<SelectCustom
|
||||
data={
|
||||
_.isEmpty(listDuration)
|
||||
? []
|
||||
: listDuration?.map((item: any) => ({
|
||||
label: item.name + `${" hari"}`,
|
||||
value: item.id,
|
||||
}))
|
||||
}
|
||||
label="Pilih Durasi Donasi"
|
||||
placeholder="Pilih Durasi Donasi"
|
||||
required
|
||||
value={data.durasiId}
|
||||
onChange={(value: any) => setData({ ...data, durasiId: value })}
|
||||
/>
|
||||
)}
|
||||
</StackCustom>
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
</NewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,10 +4,34 @@ import {
|
||||
IconHome,
|
||||
IconStatus,
|
||||
} from "@/components/_Icon";
|
||||
import BackButtonFromNotification from "@/components/Button/BackButtonFromNotification";
|
||||
import { TabsStyles } from "@/styles/tabs-styles";
|
||||
import { Tabs } from "expo-router";
|
||||
import { router, Tabs, useLocalSearchParams, useNavigation } from "expo-router";
|
||||
import { useLayoutEffect } from "react";
|
||||
|
||||
export default function EventTabsLayout() {
|
||||
const navigation = useNavigation();
|
||||
|
||||
const { from, category } = useLocalSearchParams<{
|
||||
from?: string;
|
||||
category?: string;
|
||||
}>();
|
||||
|
||||
console.log("from", from);
|
||||
console.log("category", category);
|
||||
|
||||
// Atur header secara dinamis
|
||||
useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerLeft: () => (
|
||||
<BackButtonFromNotification
|
||||
from={from as string}
|
||||
category={category as string}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}, [from, router, navigation]);
|
||||
|
||||
return (
|
||||
<Tabs screenOptions={TabsStyles}>
|
||||
<Tabs.Screen
|
||||
|
||||
@@ -1,115 +1,10 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AvatarUsernameAndOtherComponent,
|
||||
BoxWithHeaderSection,
|
||||
LoaderCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper
|
||||
} from "@/components";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import {
|
||||
apiEventGetAll
|
||||
} from "@/service/api-client/api-event";
|
||||
import { dateTimeView } from "@/utils/dateTimeView";
|
||||
import { useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import Event_ScreenContribution from "@/screens/Event/ScreenContribution";
|
||||
|
||||
export default function EventContribution() {
|
||||
const { user } = useAuth();
|
||||
const [listData, setListData] = useState<any>([]);
|
||||
const [isLoadList, setIsLoadList] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [user?.id])
|
||||
);
|
||||
|
||||
async function onLoadData() {
|
||||
try {
|
||||
setIsLoadList(true);
|
||||
const response = await apiEventGetAll({
|
||||
category: "contribution",
|
||||
userId: user?.id,
|
||||
});
|
||||
console.log("[DATA] ", JSON.stringify(response.data, null, 2));
|
||||
if (response.success) {
|
||||
setListData(response.data);
|
||||
|
||||
// const responseListParticipants = await apiEventListOfParticipants({
|
||||
// id: response?.data?.Event?.id,
|
||||
// });
|
||||
// console.log(
|
||||
// "[LIST PARTICIPANTS]",
|
||||
// JSON.stringify(responseListParticipants.data, null, 2)
|
||||
// );
|
||||
// if (responseListParticipants.success) {
|
||||
// setListParticipants(responseListParticipants.data);
|
||||
// }
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setIsLoadList(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ViewWrapper hideFooter>
|
||||
{isLoadList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom align="center">Belum ada kontribusi</TextCustom>
|
||||
) : (
|
||||
listData.map((item: any, index: number) => (
|
||||
<BoxWithHeaderSection
|
||||
key={index}
|
||||
href={`/event/${item?.Event?.id}/contribution`}
|
||||
>
|
||||
<StackCustom>
|
||||
<AvatarUsernameAndOtherComponent
|
||||
avatar={item?.Event?.Author?.Profile?.imageId}
|
||||
avatarHref={`/profile/${item?.Event?.Author?.Profile?.id}`}
|
||||
name={item?.Event?.Author?.username}
|
||||
rightComponent={
|
||||
<TextCustom truncate>
|
||||
{dateTimeView({
|
||||
date: item?.Event?.tanggal,
|
||||
withoutTime: true,
|
||||
})}
|
||||
</TextCustom>
|
||||
}
|
||||
/>
|
||||
|
||||
<TextCustom bold align="center" size="xlarge" truncate={2}>
|
||||
{item?.Event?.title}
|
||||
</TextCustom>
|
||||
<Spacing height={0} />
|
||||
|
||||
{/* <Grid>
|
||||
{item?.Event?.Event_Peserta?.map(
|
||||
(item2: any, index2: number) => (
|
||||
<Grid.Col
|
||||
style={{ alignItems: "center" }}
|
||||
span={12 / item?.Event?.Event_Peserta?.length}
|
||||
key={index2}
|
||||
>
|
||||
<AvatarComp
|
||||
size="base"
|
||||
href={`/profile/${item2?.User?.Profile?.id}`}
|
||||
fileId={item2?.User?.Profile?.imageId}
|
||||
/>
|
||||
</Grid.Col>
|
||||
)
|
||||
)}
|
||||
</Grid> */}
|
||||
</StackCustom>
|
||||
</BoxWithHeaderSection>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
<>
|
||||
<Event_ScreenContribution />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,104 +1,10 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { ButtonCustom, LoaderCustom, Spacing, TextCustom } from "@/components";
|
||||
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
|
||||
import { AccentColor, MainColor } from "@/constants/color-palet";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import Event_BoxPublishSection from "@/screens/Event/BoxPublishSection";
|
||||
import { apiEventGetAll } from "@/service/api-client/api-event";
|
||||
import { dateTimeView } from "@/utils/dateTimeView";
|
||||
import _ from "lodash";
|
||||
import { useEffect, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import Event_ScreenHistory from "@/screens/Event/ScreenHistory";
|
||||
|
||||
export default function EventHistory() {
|
||||
const [activeCategory, setActiveCategory] = useState<string | null>("all");
|
||||
const { user } = useAuth();
|
||||
const [listData, setListData] = useState<any>([]);
|
||||
const [isLoadList, setIsLoadList] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
onLoadData({ userId: user?.id });
|
||||
}, [user?.id, activeCategory]);
|
||||
|
||||
async function onLoadData({ userId }: { userId?: string }) {
|
||||
try {
|
||||
setIsLoadList(true);
|
||||
const response = await apiEventGetAll({
|
||||
category: activeCategory === "all" ? "all-history" : "my-history",
|
||||
userId: userId,
|
||||
});
|
||||
if (response.success) {
|
||||
setListData(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setIsLoadList(false);
|
||||
}
|
||||
}
|
||||
|
||||
const handlePress = (item: any) => {
|
||||
setActiveCategory(item);
|
||||
// tambahkan logika lain seperti filter dsb.
|
||||
};
|
||||
|
||||
const headerComponent = (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
padding: 5,
|
||||
backgroundColor: MainColor.soft_darkblue,
|
||||
borderRadius: 50,
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<ButtonCustom
|
||||
backgroundColor={
|
||||
activeCategory === "all" ? MainColor.yellow : AccentColor.blue
|
||||
}
|
||||
textColor={activeCategory === "all" ? MainColor.black : MainColor.white}
|
||||
style={{ width: "49%" }}
|
||||
onPress={() => handlePress("all")}
|
||||
>
|
||||
Semua Riwayat
|
||||
</ButtonCustom>
|
||||
<Spacing width={"2%"} />
|
||||
<ButtonCustom
|
||||
backgroundColor={
|
||||
activeCategory === "main" ? MainColor.yellow : AccentColor.blue
|
||||
}
|
||||
textColor={
|
||||
activeCategory === "main" ? MainColor.black : MainColor.white
|
||||
}
|
||||
style={{ width: "49%" }}
|
||||
onPress={() => handlePress("main")}
|
||||
>
|
||||
Riwayat Saya
|
||||
</ButtonCustom>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<ViewWrapper headerComponent={headerComponent} hideFooter>
|
||||
{isLoadList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom align="center">Belum ada riwayat</TextCustom>
|
||||
) : (
|
||||
listData.map((item: any, index: number) => (
|
||||
<Event_BoxPublishSection
|
||||
key={index.toString()}
|
||||
data={item}
|
||||
rightComponentAvatar={
|
||||
<TextCustom>
|
||||
{dateTimeView({ date: item?.tanggal, withoutTime: true })}
|
||||
</TextCustom>
|
||||
}
|
||||
href={`/event/${item.id}/history`}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
<>
|
||||
<Event_ScreenHistory />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,61 +1,9 @@
|
||||
import { LoaderCustom, TextCustom } from "@/components";
|
||||
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
|
||||
import FloatingButton from "@/components/Button/FloatingButton";
|
||||
import Event_BoxPublishSection from "@/screens/Event/BoxPublishSection";
|
||||
import { apiEventGetAll } from "@/service/api-client/api-event";
|
||||
import { dateTimeView } from "@/utils/dateTimeView";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import Event_ScreenBeranda from "@/screens/Event/ScreenBeranda";
|
||||
|
||||
export default function EventBeranda() {
|
||||
const [listData, setListData] = useState([]);
|
||||
const [isLoadData, setIsLoadData] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setIsLoadData(true);
|
||||
const response = await apiEventGetAll({category: "beranda"});
|
||||
// console.log("Response", JSON.stringify(response.data, null, 2));
|
||||
setListData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setIsLoadData(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper
|
||||
hideFooter
|
||||
floatingButton={
|
||||
<FloatingButton onPress={() => router.push("/event/create")} />
|
||||
}
|
||||
>
|
||||
{isLoadData ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom align="center">Belum ada event</TextCustom>
|
||||
) : (
|
||||
listData.map((item: any, index) => (
|
||||
<Event_BoxPublishSection
|
||||
key={index}
|
||||
href={`/event/${item.id}/publish`}
|
||||
data={item}
|
||||
rightComponentAvatar={
|
||||
<TextCustom>
|
||||
{dateTimeView({ date: item?.tanggal, withoutTime: true })}
|
||||
</TextCustom>
|
||||
}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
<>
|
||||
<Event_ScreenBeranda />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,99 +1,10 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BoxWithHeaderSection,
|
||||
Grid,
|
||||
LoaderCustom,
|
||||
ScrollableCustom,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
|
||||
import { apiEventGetByStatus } from "@/service/api-client/api-event";
|
||||
import { useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import Event_ScreenStatus from "@/screens/Event/ScreenStatus";
|
||||
|
||||
export default function EventStatus() {
|
||||
const { user } = useAuth();
|
||||
const id = user?.id || "";
|
||||
const [activeCategory, setActiveCategory] = useState<string | null>(
|
||||
"publish"
|
||||
);
|
||||
const [listData, setListData] = useState([]);
|
||||
const [loadingGetData, setLoadingGetData] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [activeCategory, id])
|
||||
);
|
||||
|
||||
async function onLoadData() {
|
||||
try {
|
||||
setLoadingGetData(true);
|
||||
const response = await apiEventGetByStatus({
|
||||
id: id!,
|
||||
status: activeCategory!,
|
||||
});
|
||||
// console.log("Response", JSON.stringify(response.data, null, 2));
|
||||
setListData(response.data);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setLoadingGetData(false);
|
||||
}
|
||||
}
|
||||
|
||||
const handlePress = (item: any) => {
|
||||
setActiveCategory(item.value);
|
||||
// tambahkan logika lain seperti filter dsb.
|
||||
};
|
||||
|
||||
const tabsComponent = (
|
||||
<ScrollableCustom
|
||||
data={dummyMasterStatus.map((e, i) => ({
|
||||
id: i,
|
||||
label: e.label,
|
||||
value: e.value,
|
||||
}))}
|
||||
onButtonPress={handlePress}
|
||||
activeId={activeCategory as any}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<ViewWrapper headerComponent={tabsComponent}>
|
||||
{loadingGetData ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom align="center">Tidak ada data {activeCategory}</TextCustom>
|
||||
) : (
|
||||
listData.map((item: any, i) => (
|
||||
<BoxWithHeaderSection
|
||||
key={i}
|
||||
href={`/event/${item.id }/${activeCategory}/detail-event`}
|
||||
>
|
||||
<StackCustom gap={"xs"}>
|
||||
<Grid>
|
||||
<Grid.Col span={8}>
|
||||
<TextCustom truncate bold>
|
||||
{item?.title}
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={4} style={{ alignItems: "flex-end" }}>
|
||||
<TextCustom>
|
||||
{new Date(item?.tanggal).toLocaleDateString()}
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
|
||||
<TextCustom truncate={2}>{item?.deskripsi}</TextCustom>
|
||||
</StackCustom>
|
||||
</BoxWithHeaderSection>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
<>
|
||||
<Event_ScreenStatus />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
554
app/(application)/(user)/event/[id]/confirmation.tsx
Normal file
@@ -0,0 +1,554 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
ButtonCustom,
|
||||
CenterCustom,
|
||||
LoaderCustom,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { AccentColor, MainColor } from "@/constants/color-palet";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import {
|
||||
apiEventConfirmationAction,
|
||||
apiEventGetConfirmation,
|
||||
apiEventJoin,
|
||||
} from "@/service/api-client/api-event";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import dayjs from "dayjs";
|
||||
import isBetween from "dayjs/plugin/isBetween";
|
||||
import {
|
||||
Redirect,
|
||||
router,
|
||||
Stack,
|
||||
useFocusEffect,
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
// Extend Day.js dengan plugin isBetween
|
||||
dayjs.extend(isBetween);
|
||||
|
||||
interface DataEvent {
|
||||
id: string;
|
||||
title: string;
|
||||
tanggal: Date;
|
||||
tanggalSelesai: Date;
|
||||
lokasi: string;
|
||||
Author: {
|
||||
id: string;
|
||||
username: string;
|
||||
Profile: {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export default function UserEventConfirmation() {
|
||||
const { token } = useAuth();
|
||||
const { id, userId: authorId } = useLocalSearchParams();
|
||||
const { user } = useAuth();
|
||||
const [data, setData] = useState<DataEvent | null>(null);
|
||||
const [peserta, setPeserta] = useState<boolean | null>(null);
|
||||
const [konfirmasi, setKonfirmasi] = useState<boolean | null>(null);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
checkTokenAndDataParticipants() || console.log("Token is null");
|
||||
}, [token, id, user?.id])
|
||||
);
|
||||
|
||||
const checkTokenAndDataParticipants = async () => {
|
||||
if (!token) {
|
||||
return <Redirect href={`/`} />;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await apiEventGetConfirmation({
|
||||
id: id as string,
|
||||
userId: user?.id as string,
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
setData(response.data?.dataEvent);
|
||||
setPeserta(response.data?.peserta);
|
||||
setKonfirmasi(response.data?.kehadiran);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR CONFIRMATION]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerReturn = () => {
|
||||
const now = dayjs(); // asumsi: UTC, sesuai dengan API
|
||||
|
||||
// --- [1] Loading & Data tidak ditemukan ---
|
||||
if (data === undefined || data === null) {
|
||||
if (peserta === null && konfirmasi === null) {
|
||||
return <LoaderCustom />;
|
||||
}
|
||||
return (
|
||||
<BaseBox>
|
||||
<TextCustom bold align="center" size={"large"}>
|
||||
Data Tidak Ditemukan
|
||||
</TextCustom>
|
||||
<BackToOtherPath path="home" />
|
||||
</BaseBox>
|
||||
);
|
||||
}
|
||||
|
||||
// --- [2] Ambil waktu event dari `data` ---
|
||||
const eventStart = dayjs(data.tanggal);
|
||||
const eventEnd = dayjs(data.tanggalSelesai);
|
||||
|
||||
// --- [3] Definisikan jendela konfirmasi: 1 jam sebelum mulai → 1 jam setelah selesai ---
|
||||
const confirmationStart = eventStart.subtract(1, "hour");
|
||||
const confirmationEnd = eventEnd.add(1, "hour");
|
||||
const isWithinConfirmationWindow = now.isBetween(
|
||||
confirmationStart,
|
||||
confirmationEnd,
|
||||
null,
|
||||
"[]"
|
||||
);
|
||||
|
||||
// --- [4] Status waktu event (untuk pesan UI) ---
|
||||
const isBeforeEvent = now.isBefore(eventStart);
|
||||
const isAfterEvent = now.isAfter(eventEnd);
|
||||
const isDuringEvent = !isBeforeEvent && !isAfterEvent;
|
||||
|
||||
// --- [5] Handle berdasarkan waktu dan status peserta/konfirmasi ---
|
||||
|
||||
// 🟢 Acara sudah selesai
|
||||
if (isAfterEvent) {
|
||||
if (peserta === false) {
|
||||
return (
|
||||
<TamplateBox data={data}>
|
||||
<TamplateText text="Event telah selesai, sehingga konfirmasi kehadiran sudah tidak dapat dilakukan. Terima kasih atas perhatian dan minat Anda. Kami berharap dapat bertemu di acara kami berikutnya." />
|
||||
<BackToOtherPath
|
||||
path="event"
|
||||
id={data.id}
|
||||
isAfterEvent={isAfterEvent}
|
||||
/>
|
||||
</TamplateBox>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<TamplateBox data={data}>
|
||||
<TamplateText
|
||||
text={`Event telah selesai, anda terdaftar sebagai peserta dan ${
|
||||
konfirmasi
|
||||
? "Anda telah mengonfirmasi kehadiran."
|
||||
: "Anda tidak mengonfirmasi kehadiran."
|
||||
} Terima kasih atas perhatian dan minat Anda. Kami berharap dapat bertemu di acara kami berikutnya.`}
|
||||
/>
|
||||
<BackToOtherPath
|
||||
path="event"
|
||||
id={data.id}
|
||||
isAfterEvent={isAfterEvent}
|
||||
/>
|
||||
</TamplateBox>
|
||||
);
|
||||
}
|
||||
|
||||
// 🔵 Acara belum mulai & belum terdaftar
|
||||
if (isBeforeEvent) {
|
||||
if (peserta === false) {
|
||||
return (
|
||||
<NotStarted_And_UserNotParticipan
|
||||
id={data.id}
|
||||
userId={user?.id as string}
|
||||
data={data}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// Peserta sudah daftar → cek apakah sudah boleh konfirmasi
|
||||
if (isWithinConfirmationWindow && peserta === true) {
|
||||
if (konfirmasi === false) {
|
||||
return (
|
||||
<UserParticipan_And_DuringEvent
|
||||
id={data.id}
|
||||
userId={user?.id as string}
|
||||
data={data}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<TamplateBox data={data}>
|
||||
<TamplateText text="Terimakasih telah mengonfirmasi kehadiran. Silahkan lihat peserta lain pada halaman event atau kembali ke halaman home. Selamat menikmati acara dan selamat berpartisipasi." />
|
||||
<BackToOtherPath
|
||||
path="event"
|
||||
id={data.id}
|
||||
isAfterEvent={isAfterEvent}
|
||||
/>
|
||||
</TamplateBox>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TamplateBox data={data}>
|
||||
<TamplateText text="Anda telah terdaftar sebagai peserta pada Event ini. Konfirmasi kehadiran dibuka 1 jam sebelum acara dimulai." />
|
||||
<BackToOtherPath
|
||||
path="event"
|
||||
id={data.id}
|
||||
isAfterEvent={isAfterEvent}
|
||||
/>
|
||||
</TamplateBox>
|
||||
);
|
||||
}
|
||||
|
||||
// 🟡 Acara sedang berlangsung & belum terdaftar
|
||||
if (isDuringEvent) {
|
||||
if (peserta === false) {
|
||||
return (
|
||||
<UserNotParticipan_And_DuringEvent
|
||||
id={data.id}
|
||||
userId={user?.id as string}
|
||||
data={data}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (peserta === true) {
|
||||
if (isWithinConfirmationWindow) {
|
||||
if (konfirmasi === false) {
|
||||
return (
|
||||
<TamplateBox data={data}>
|
||||
<TamplateText text="Konfirmasi Kehadiran" />
|
||||
</TamplateBox>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<TamplateBox data={data}>
|
||||
<TamplateText text="Anda telah mengonfirmasi kehadiran dalam event ini. Terima kasih dan selamat menikmati acara. Have fun!" />
|
||||
<BackToOtherPath
|
||||
path="event"
|
||||
id={data.id}
|
||||
isAfterEvent={isAfterEvent}
|
||||
/>
|
||||
</TamplateBox>
|
||||
);
|
||||
}
|
||||
|
||||
// Ini sangat jarang terjadi selama event berlangsung, tapi aman
|
||||
return (
|
||||
<TamplateBox data={data}>
|
||||
<TamplateText text="Konfirmasi kehadiran tidak tersedia saat ini." />
|
||||
<BackToOtherPath path="home" />
|
||||
</TamplateBox>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 🛑 Fallback aman
|
||||
return (
|
||||
<BaseBox>
|
||||
<StackCustom>
|
||||
<TamplateText text="Terjadi kesalahan tak terduga pada logika waktu." />
|
||||
<BackToOtherPath path="home" />
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Konfirmasi Event",
|
||||
// headerLeft: () => (
|
||||
// <Ionicons
|
||||
// name="arrow-back"
|
||||
// size={20}
|
||||
// color={MainColor.yellow}
|
||||
// onPress={() =>
|
||||
// router.navigate("/(application)/(user)/event/create")
|
||||
// }
|
||||
// />
|
||||
// ),
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>{handlerReturn()}</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const TamplateBox = ({
|
||||
data,
|
||||
children,
|
||||
}: {
|
||||
data: DataEvent;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<CenterCustom>
|
||||
<BaseBox>
|
||||
<StackCustom gap={"lg"}>
|
||||
<StackCustom gap={"sm"}>
|
||||
<TextCustom bold align="center" size={"large"}>
|
||||
{data?.title}
|
||||
</TextCustom>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
// backgroundColor: AccentColor.blue,
|
||||
// borderColor: AccentColor.blue,
|
||||
// borderWidth: 1,
|
||||
// borderRadius: 4,
|
||||
// padding: 3
|
||||
}}
|
||||
>
|
||||
<TextCustom align="center" size="small" bold>
|
||||
{dayjs(data?.tanggal).format("DD MMM YYYY: HH:mm")}
|
||||
</TextCustom>
|
||||
<TextCustom align="center" size="small" bold>
|
||||
{" "}
|
||||
-{" "}
|
||||
</TextCustom>
|
||||
<TextCustom align="center" size="small" bold>
|
||||
{dayjs(data?.tanggalSelesai).format("DD MMM YYYY: HH:mm")}
|
||||
</TextCustom>
|
||||
</View>
|
||||
</StackCustom>
|
||||
|
||||
{children}
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
</CenterCustom>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const TamplateText = ({ text }: { text: React.ReactNode }) => {
|
||||
return (
|
||||
<>
|
||||
<TextCustom align="center">{text}</TextCustom>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
type BackToOtherPathProps =
|
||||
| { path: "home" | "beranda-event"; id?: never; isAfterEvent?: never }
|
||||
| { path: "event"; id: string; isAfterEvent: boolean };
|
||||
|
||||
const BackToOtherPath = ({ path, id, isAfterEvent }: BackToOtherPathProps) => {
|
||||
return (
|
||||
<>
|
||||
{path === "home" ? (
|
||||
<ButtonCustom
|
||||
onPress={() => {
|
||||
router.replace("/(application)/home");
|
||||
}}
|
||||
>
|
||||
Home
|
||||
</ButtonCustom>
|
||||
) : (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
gap: 10,
|
||||
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<ButtonCustom
|
||||
onPress={() => {
|
||||
router.replace("/(application)/home");
|
||||
}}
|
||||
>
|
||||
Home
|
||||
</ButtonCustom>
|
||||
<ButtonCustom
|
||||
onPress={() => {
|
||||
if (path === "event") {
|
||||
if (isAfterEvent) {
|
||||
router.push(`/(application)/(user)/event/${id}/history`);
|
||||
} else {
|
||||
router.push(`/(application)/(user)/event/${id}/publish`);
|
||||
}
|
||||
} else if (path === "beranda-event") {
|
||||
router.push(`/(application)/(user)/event`);
|
||||
} else {
|
||||
console.log("[PATH]", path);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Lihat {path === "event" ? "Event" : "Beranda Event"}
|
||||
</ButtonCustom>
|
||||
</View>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// 🔵 Acara belum mulai & belum terdaftar
|
||||
const NotStarted_And_UserNotParticipan = ({
|
||||
id,
|
||||
userId,
|
||||
data,
|
||||
}: {
|
||||
id: string;
|
||||
userId: string;
|
||||
data: DataEvent;
|
||||
}) => {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
const handlerJoinEvent = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const response = await apiEventJoin({
|
||||
id: id as string,
|
||||
userId: userId as string,
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Anda gagal join",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Anda berhasil join",
|
||||
});
|
||||
router.navigate(`/(application)/(user)/event/${id}/publish`);
|
||||
} catch (error) {
|
||||
console.log("[ERROR JOIN EVENT]", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<TamplateBox data={data}>
|
||||
<TamplateText text="Anda belum terdaftar sebagai peserta & Event belum dimulai. Silahkan daftarkan diri anda terlebih dahulu" />
|
||||
<ButtonCustom onPress={handlerJoinEvent} isLoading={isLoading}>
|
||||
Join
|
||||
</ButtonCustom>
|
||||
</TamplateBox>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// 🟡 ZONA ACARA BERLANGSUNG
|
||||
// Acara sedang berlangsung & belum terdaftar & user harus join dan konfirmasi
|
||||
const UserNotParticipan_And_DuringEvent = ({
|
||||
id,
|
||||
userId,
|
||||
data,
|
||||
}: {
|
||||
id: string;
|
||||
userId: string;
|
||||
data: DataEvent;
|
||||
}) => {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
const handlerSubmit = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const response = await apiEventConfirmationAction({
|
||||
id: id as string,
|
||||
userId: userId as string,
|
||||
category: "join_and_confirm",
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Anda gagal join & konfirmasi",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Anda berhasil join & konfirmasi",
|
||||
});
|
||||
router.navigate(`/(application)/(user)/event/${id}/publish`);
|
||||
} catch (error) {
|
||||
console.log("[ERROR JOIN & CONFIRMATION EVENT]", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<TamplateBox data={data}>
|
||||
<TamplateText text="Anda belum terdaftar sebagai peserta & Event sedang berlangsung. Silahkan daftarkan diri anda & Konfirmasi kehadiran" />
|
||||
|
||||
<ButtonCustom onPress={() => handlerSubmit()} isLoading={isLoading}>
|
||||
Join & Konfirmasi
|
||||
</ButtonCustom>
|
||||
</TamplateBox>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
// 🟡 ZONA ACARA BERLANGSUN
|
||||
// User sudah terdaftar & Event sedang berlangsung & user harus konfirmasi
|
||||
const UserParticipan_And_DuringEvent = ({
|
||||
id,
|
||||
userId,
|
||||
data,
|
||||
}: {
|
||||
id: string;
|
||||
userId: string;
|
||||
data: DataEvent;
|
||||
}) => {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
const handlerSubmit = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const response = await apiEventConfirmationAction({
|
||||
id: id as string,
|
||||
userId: userId as string,
|
||||
category: "confirmation",
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Anda gagal konfirmasi",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Anda berhasil konfirmasi",
|
||||
});
|
||||
router.navigate(`/(application)/(user)/event/${id}/publish`);
|
||||
} catch (error) {
|
||||
console.log("[ERROR JOIN & CONFIRMATION EVENT]", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<TamplateBox data={data}>
|
||||
<TamplateText text="Anda sudah terdaftar sebagai peserta & Event sedang berlangsung. Silahkan konfirmasi kehadiran" />
|
||||
|
||||
<ButtonCustom onPress={() => handlerSubmit()} isLoading={isLoading}>
|
||||
Konfirmasi
|
||||
</ButtonCustom>
|
||||
</TamplateBox>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,7 +1,9 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCustom,
|
||||
LoaderCustom,
|
||||
NewWrapper,
|
||||
SelectCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
@@ -10,6 +12,7 @@ import {
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
|
||||
import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom";
|
||||
import {
|
||||
apiEventGetOne,
|
||||
@@ -18,7 +21,7 @@ import {
|
||||
import { apiMasterEventType } from "@/service/api-client/api-master";
|
||||
import { DateTimePickerEvent } from "@react-native-community/datetimepicker";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function EventEdit() {
|
||||
@@ -48,13 +51,14 @@ export default function EventEdit() {
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id])
|
||||
}, [id]),
|
||||
);
|
||||
|
||||
async function onLoadData() {
|
||||
try {
|
||||
setIsLoadData(true);
|
||||
const response = await apiEventGetOne({ id: id as string });
|
||||
|
||||
if (response.success) {
|
||||
setData(response.data);
|
||||
setSelectedDate(new Date(response.data.tanggal));
|
||||
@@ -99,6 +103,15 @@ export default function EventEdit() {
|
||||
const startDate = new Date(selectedDate as any);
|
||||
const endDate = new Date(selectedEndDate as any);
|
||||
|
||||
if (!startDate) {
|
||||
Toast.show({
|
||||
type: "info",
|
||||
text1: "Info",
|
||||
text2: "Tanggal mulai tidak valid",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (startDate >= endDate) {
|
||||
Toast.show({
|
||||
type: "info",
|
||||
@@ -145,7 +158,7 @@ export default function EventEdit() {
|
||||
|
||||
const validateDateRange = (
|
||||
selectedDate: string | Date,
|
||||
selectedEndDate: string | Date
|
||||
selectedEndDate: string | Date,
|
||||
): { isValid: boolean; error?: string } => {
|
||||
const startDate = new Date(selectedDate);
|
||||
const endDate = new Date(selectedEndDate);
|
||||
@@ -173,9 +186,19 @@ export default function EventEdit() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
<NewWrapper
|
||||
footerComponent={
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
isLoading={isLoading}
|
||||
title="Update"
|
||||
onPress={handlerSubmit}
|
||||
/>
|
||||
</BoxButtonOnFooter>
|
||||
}
|
||||
>
|
||||
{isLoadData ? (
|
||||
<LoaderCustom />
|
||||
<ListSkeletonComponent />
|
||||
) : (
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextInputCustom
|
||||
@@ -185,31 +208,20 @@ export default function EventEdit() {
|
||||
value={data?.title}
|
||||
onChangeText={(value) => setData({ ...data, title: value })}
|
||||
/>
|
||||
<SelectCustom
|
||||
label="Tipe Event"
|
||||
placeholder="Pilih tipe event"
|
||||
data={listTypeEvent.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))}
|
||||
value={data?.eventMaster_TipeAcaraId || ""}
|
||||
onChange={(value) => {
|
||||
console.log(value);
|
||||
setData({ ...data, eventMaster_TipeAcaraId: value });
|
||||
}}
|
||||
/>
|
||||
<TextInputCustom
|
||||
label="Lokasi"
|
||||
placeholder="Masukkan lokasi event"
|
||||
<TextAreaCustom
|
||||
label="Deskripsi"
|
||||
placeholder="Masukkan deskripsi event"
|
||||
required
|
||||
value={data?.lokasi}
|
||||
onChangeText={(value) => setData({ ...data, lokasi: value })}
|
||||
showCount
|
||||
value={data?.deskripsi}
|
||||
onChangeText={(value) => setData({ ...data, deskripsi: value })}
|
||||
/>
|
||||
|
||||
<DateTimePickerCustom
|
||||
minimumDate={new Date(Date.now())}
|
||||
label="Tanggal & Waktu Mulai"
|
||||
required
|
||||
value={selectedDate as any}
|
||||
value={selectedDate}
|
||||
onChange={(date: any) => {
|
||||
setSelectedDate(date as any);
|
||||
}}
|
||||
@@ -232,7 +244,7 @@ export default function EventEdit() {
|
||||
{
|
||||
validateDateRange(
|
||||
selectedDate as any,
|
||||
selectedEndDate as any
|
||||
selectedEndDate as any,
|
||||
).error
|
||||
}
|
||||
</TextCustom>
|
||||
@@ -241,32 +253,37 @@ export default function EventEdit() {
|
||||
{
|
||||
validateDateRange(
|
||||
selectedDate as any,
|
||||
selectedEndDate as any
|
||||
selectedEndDate as any,
|
||||
).error
|
||||
}
|
||||
</TextCustom>
|
||||
)}
|
||||
<Spacing />
|
||||
{/* <Spacing /> */}
|
||||
</StackCustom>
|
||||
|
||||
<TextAreaCustom
|
||||
label="Deskripsi"
|
||||
placeholder="Masukkan deskripsi event"
|
||||
required
|
||||
showCount
|
||||
maxLength={100}
|
||||
value={data?.deskripsi}
|
||||
onChangeText={(value) => setData({ ...data, deskripsi: value })}
|
||||
<SelectCustom
|
||||
label="Tipe Event"
|
||||
placeholder="Pilih tipe event"
|
||||
data={listTypeEvent.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))}
|
||||
value={data?.eventMaster_TipeAcaraId || ""}
|
||||
onChange={(value) => {
|
||||
console.log(value);
|
||||
setData({ ...data, eventMaster_TipeAcaraId: value });
|
||||
}}
|
||||
/>
|
||||
|
||||
<ButtonCustom
|
||||
isLoading={isLoading}
|
||||
title="Update"
|
||||
onPress={handlerSubmit}
|
||||
<TextInputCustom
|
||||
label="Lokasi"
|
||||
placeholder="Masukkan lokasi event"
|
||||
required
|
||||
value={data?.lokasi}
|
||||
onChangeText={(value) => setData({ ...data, lokasi: value })}
|
||||
/>
|
||||
</StackCustom>
|
||||
)}
|
||||
</ViewWrapper>
|
||||
</NewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ export default function EventDetailHistory() {
|
||||
<DrawerCustom
|
||||
isVisible={openDrawer}
|
||||
closeDrawer={() => setOpenDrawer(false)}
|
||||
height={250}
|
||||
height={"auto"}
|
||||
>
|
||||
<MenuDrawerDynamicGrid
|
||||
data={menuDrawerPublishEvent({ id: id as string })}
|
||||
|
||||
@@ -1,102 +1,10 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AvatarUsernameAndOtherComponent,
|
||||
BadgeCustom,
|
||||
BaseBox,
|
||||
LoaderCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import {
|
||||
apiEventGetOne,
|
||||
apiEventListOfParticipants,
|
||||
} from "@/service/api-client/api-event";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import Event_ScreenListOfParticipants from "@/screens/Event/ScreenListOfParticipants";
|
||||
|
||||
export default function EventListOfParticipants() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [startDate, setStartDate] = useState();
|
||||
const [listData, setListData] = useState([]);
|
||||
const [isLoadData, setIsLoadData] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
handlerLoadData();
|
||||
}, [id]);
|
||||
|
||||
const handlerLoadData = () => {
|
||||
try {
|
||||
setIsLoadData(true);
|
||||
onLoadData();
|
||||
onLoadList();
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setIsLoadData(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiEventGetOne({ id: id as string });
|
||||
if (response.success) {
|
||||
setStartDate(response.data.tanggal);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const onLoadList = async () => {
|
||||
try {
|
||||
const response = await apiEventListOfParticipants({ id: id as string });
|
||||
if (response.success) {
|
||||
setListData(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
{isLoadData ? (
|
||||
<LoaderCustom />
|
||||
) : listData.length === 0 ? (
|
||||
<TextCustom align="center">Belum ada peserta</TextCustom>
|
||||
) : (
|
||||
listData.map((item: any, index: number) => (
|
||||
<BaseBox key={index}>
|
||||
<AvatarUsernameAndOtherComponent
|
||||
avatar={item?.User?.Profile?.imageId}
|
||||
name={item?.User?.username}
|
||||
avatarHref={`/profile/${item?.User?.Profile?.id}`}
|
||||
rightComponent={
|
||||
new Date().getTime() > new Date(startDate as any).getTime() ? (
|
||||
<View
|
||||
style={{
|
||||
justifyContent: "flex-end",
|
||||
}}
|
||||
>
|
||||
<BadgeCustom color={item?.isPresent ? "green" : "red"}>
|
||||
{item?.isPresent ? "Hadir" : "Tidak Hadir"}
|
||||
</BadgeCustom>
|
||||
</View>
|
||||
) : (
|
||||
<View
|
||||
style={{
|
||||
justifyContent: "flex-end",
|
||||
}}
|
||||
>
|
||||
<BadgeCustom color="gray">-</BadgeCustom>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</BaseBox>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
<>
|
||||
<Event_ScreenListOfParticipants />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AlertDefaultSystem,
|
||||
BackButton,
|
||||
ButtonCustom,
|
||||
DotButton,
|
||||
DrawerCustom,
|
||||
LoaderCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { IMenuDrawerItem } from "@/components/_Interface/types";
|
||||
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
|
||||
import LeftButtonCustom from "@/components/Button/BackButton";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import Event_BoxDetailPublishSection from "@/screens/Event/BoxDetailPublishSection";
|
||||
@@ -18,23 +19,26 @@ import {
|
||||
apiEventGetOne,
|
||||
apiEventJoin,
|
||||
} from "@/service/api-client/api-event";
|
||||
import dayjs from "dayjs";
|
||||
import {
|
||||
Redirect,
|
||||
router,
|
||||
Stack,
|
||||
useFocusEffect,
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function EventDetailPublish() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const now = new Date().toISOString();
|
||||
const { user } = useAuth();
|
||||
const { id } = useLocalSearchParams();
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [isLoadingData, setIsLoadingData] = useState(false);
|
||||
const [isLoadingJoin, setIsLoadingJoin] = useState(false);
|
||||
|
||||
const [data, setData] = useState();
|
||||
const [data, setData] = useState<any>();
|
||||
const [isParticipant, setIsParticipant] = useState<boolean | null>(null);
|
||||
|
||||
useFocusEffect(
|
||||
@@ -106,7 +110,24 @@ export default function EventDetailPublish() {
|
||||
}
|
||||
};
|
||||
|
||||
const footerButton = () => {
|
||||
const isEventFinished =
|
||||
id && data?.tanggalSelesai && dayjs(data.tanggalSelesai).isBefore(now);
|
||||
|
||||
useEffect(() => {
|
||||
if (isEventFinished) {
|
||||
router.replace(`/(application)/(user)/event/${id}/history`);
|
||||
}
|
||||
}, [isEventFinished, id]);
|
||||
|
||||
if (isEventFinished) {
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<CustomSkeleton />
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
const FooterButton = () => {
|
||||
return (
|
||||
<>
|
||||
<ButtonCustom
|
||||
@@ -135,18 +156,18 @@ export default function EventDetailPublish() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: `Event publish`,
|
||||
headerLeft: () => <LeftButtonCustom />,
|
||||
title: `Event Publish`,
|
||||
headerLeft: () => <BackButton onPress={() => router.back()} />,
|
||||
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
{isLoadingData ? (
|
||||
<LoaderCustom />
|
||||
<CustomSkeleton height={400} />
|
||||
) : (
|
||||
<Event_BoxDetailPublishSection
|
||||
data={data}
|
||||
footerButton={footerButton()}
|
||||
footerButton={FooterButton()}
|
||||
/>
|
||||
)}
|
||||
</ViewWrapper>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCustom,
|
||||
NewWrapper,
|
||||
SelectCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextAreaCustom,
|
||||
TextCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
@@ -14,7 +15,7 @@ import { apiEventCreate } from "@/service/api-client/api-event";
|
||||
import { apiMasterEventType } from "@/service/api-client/api-master";
|
||||
import { DateTimePickerEvent } from "@react-native-community/datetimepicker";
|
||||
import { router } from "expo-router";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
interface EventCreateProps {
|
||||
@@ -78,23 +79,6 @@ export default function EventCreate() {
|
||||
return;
|
||||
}
|
||||
|
||||
// if (selectedDate) {
|
||||
// console.log("Tanggal yang dipilih:", selectedDate);
|
||||
// console.log(`ISO Format ${Platform.OS}:`, selectedDate.toString());
|
||||
|
||||
// // Kirim ke API atau proses lanjutan
|
||||
// } else {
|
||||
// console.log("Tanggal belum dipilih");
|
||||
// }
|
||||
|
||||
// if (selectedEndDate) {
|
||||
// console.log("Tanggal yang dipilih:", selectedEndDate);
|
||||
// console.log(`ISO Format ${Platform.OS}:`, selectedEndDate.toString());
|
||||
// // Kirim ke API atau proses lanjutan
|
||||
// } else {
|
||||
// console.log("Tanggal berakhir belum dipilih");
|
||||
// }
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
@@ -110,7 +94,7 @@ export default function EventCreate() {
|
||||
const response = await apiEventCreate(newData);
|
||||
console.log("Response", JSON.stringify(response, null, 2));
|
||||
|
||||
router.navigate("/event/status");
|
||||
router.replace("/event/status?status=review");
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
@@ -128,7 +112,9 @@ export default function EventCreate() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
<NewWrapper
|
||||
footerComponent={<BoxButtonOnFooter>{buttonSubmit}</BoxButtonOnFooter>}
|
||||
>
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextInputCustom
|
||||
placeholder="Masukkan nama event"
|
||||
@@ -137,24 +123,15 @@ export default function EventCreate() {
|
||||
onChangeText={(value: any) => setData({ ...data, title: value })}
|
||||
/>
|
||||
|
||||
<SelectCustom
|
||||
label="Tipe Event"
|
||||
placeholder="Pilih tipe event"
|
||||
data={listTypeEvent.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))}
|
||||
value={data?.eventMaster_TipeAcaraId || ""}
|
||||
onChange={(value: any) =>
|
||||
setData({ ...data, eventMaster_TipeAcaraId: value })
|
||||
}
|
||||
/>
|
||||
|
||||
<TextInputCustom
|
||||
label="Lokasi"
|
||||
placeholder="Masukkan lokasi event"
|
||||
<TextAreaCustom
|
||||
label="Deskripsi"
|
||||
placeholder="Masukkan deskripsi event"
|
||||
required
|
||||
onChangeText={(value: any) => setData({ ...data, lokasi: value })}
|
||||
showCount
|
||||
value={data?.deskripsi || ""}
|
||||
onChangeText={(value: any) =>
|
||||
setData({ ...data, deskripsi: value })
|
||||
}
|
||||
/>
|
||||
|
||||
<DateTimePickerCustom
|
||||
@@ -184,22 +161,28 @@ export default function EventCreate() {
|
||||
</TextCustom>
|
||||
)}
|
||||
<Spacing />
|
||||
<SelectCustom
|
||||
label="Tipe Event"
|
||||
placeholder="Pilih tipe event"
|
||||
data={listTypeEvent.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))}
|
||||
value={data?.eventMaster_TipeAcaraId || null}
|
||||
onChange={(value: any) =>
|
||||
setData({ ...data, eventMaster_TipeAcaraId: value })
|
||||
}
|
||||
/>
|
||||
|
||||
<TextInputCustom
|
||||
label="Lokasi"
|
||||
placeholder="Masukkan lokasi event"
|
||||
required
|
||||
onChangeText={(value: any) => setData({ ...data, lokasi: value })}
|
||||
/>
|
||||
</StackCustom>
|
||||
|
||||
<TextAreaCustom
|
||||
label="Deskripsi"
|
||||
placeholder="Masukkan deskripsi event"
|
||||
required
|
||||
showCount
|
||||
maxLength={100}
|
||||
onChangeText={(value: any) =>
|
||||
setData({ ...data, deskripsi: value })
|
||||
}
|
||||
/>
|
||||
|
||||
{buttonSubmit}
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
</NewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,37 +1,107 @@
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCustom,
|
||||
LoaderCustom,
|
||||
TextAreaCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { router } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import AlertWarning from "@/components/Alert/AlertWarning";
|
||||
import { apiForumGetOne, apiForumUpdate } from "@/service/api-client/api-forum";
|
||||
import { isBadContent } from "@/utils/badWordsIndonesia";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import { Alert } from "react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function ForumEdit() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [text, setText] = useState("");
|
||||
const [loadingGetData, setLoadingGetData] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const buttonFooter = (
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
onPress={() => {
|
||||
console.log("Posting", text);
|
||||
router.back();
|
||||
}}
|
||||
>
|
||||
Update
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData(id as string);
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadData = async (id: string) => {
|
||||
try {
|
||||
setLoadingGetData(true);
|
||||
const response = await apiForumGetOne({ id });
|
||||
|
||||
setText(response.data.diskusi);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadingGetData(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerUpdateData = async () => {
|
||||
if (!text) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Harap masukkan diskusi",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (isBadContent(text)) {
|
||||
AlertWarning({});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await apiForumUpdate({
|
||||
id: id as string,
|
||||
data: text,
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Berhasil diupdate",
|
||||
});
|
||||
router.back();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const buttonFooter = () => {
|
||||
return (
|
||||
<>
|
||||
{!loadingGetData && (
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom isLoading={isLoading} onPress={handlerUpdateData}>
|
||||
Update
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper footerComponent={buttonFooter}>
|
||||
<TextAreaCustom
|
||||
placeholder="Ketik diskusi anda..."
|
||||
maxLength={1000}
|
||||
showCount
|
||||
value={text}
|
||||
onChangeText={setText}
|
||||
/>
|
||||
<ViewWrapper footerComponent={buttonFooter()}>
|
||||
{!loadingGetData ? (
|
||||
<TextAreaCustom
|
||||
placeholder="Ketik diskusi anda..."
|
||||
maxLength={1000}
|
||||
showCount
|
||||
value={text}
|
||||
onChangeText={(value) => {
|
||||
setText(value);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<LoaderCustom />
|
||||
)}
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,114 +1,12 @@
|
||||
import {
|
||||
AlertCustom,
|
||||
AvatarCustom,
|
||||
ButtonCustom,
|
||||
CenterCustom,
|
||||
DrawerCustom,
|
||||
Grid,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection";
|
||||
import { listDummyDiscussionForum } from "@/screens/Forum/list-data-dummy";
|
||||
import Forum_MenuDrawerBerandaSection from "@/screens/Forum/MenuDrawerSection.tsx/MenuBeranda";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import View_Forumku from "@/screens/Forum/ViewForumku";
|
||||
import View_Forumku2 from "@/screens/Forum/ViewForumku2";
|
||||
|
||||
export default function Forumku() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [status, setStatus] = useState("");
|
||||
const [alertStatus, setAlertStatus] = useState(false);
|
||||
const [deleteAlert, setDeleteAlert] = useState(false);
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
<StackCustom>
|
||||
<CenterCustom>
|
||||
<AvatarCustom
|
||||
href={`/(application)/(image)/preview-image/${id}`}
|
||||
size="xl"
|
||||
/>
|
||||
</CenterCustom>
|
||||
|
||||
<Grid>
|
||||
<Grid.Col span={6}>
|
||||
<TextCustom bold truncate>
|
||||
@bagas_banuna
|
||||
</TextCustom>
|
||||
<TextCustom>1 postingan</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6} style={{ alignItems: "flex-end" }}>
|
||||
<ButtonCustom href={`/profile/${id}`}>
|
||||
Kunjungi Profile
|
||||
</ButtonCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
{listDummyDiscussionForum.map((e, i) => (
|
||||
<Forum_BoxDetailSection
|
||||
key={i}
|
||||
data={e}
|
||||
setOpenDrawer={setOpenDrawer}
|
||||
setStatus={setStatus}
|
||||
isTruncate={true}
|
||||
href={`/forum/${id}`}
|
||||
/>
|
||||
))}
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
|
||||
{/* Drawer Komponen Eksternal */}
|
||||
<DrawerCustom
|
||||
height={350}
|
||||
isVisible={openDrawer}
|
||||
closeDrawer={() => setOpenDrawer(false)}
|
||||
>
|
||||
<Forum_MenuDrawerBerandaSection
|
||||
id={id as string}
|
||||
status={status}
|
||||
setIsDrawerOpen={() => {
|
||||
setOpenDrawer(false);
|
||||
}}
|
||||
setShowDeleteAlert={setDeleteAlert}
|
||||
setShowAlertStatus={setAlertStatus}
|
||||
/>
|
||||
</DrawerCustom>
|
||||
|
||||
{/* Alert Komponen Eksternal */}
|
||||
<AlertCustom
|
||||
isVisible={alertStatus}
|
||||
onLeftPress={() => setAlertStatus(false)}
|
||||
onRightPress={() => {
|
||||
setOpenDrawer(false);
|
||||
setAlertStatus(false);
|
||||
console.log("Ubah status forum");
|
||||
}}
|
||||
title="Ubah Status Forum"
|
||||
message="Apakah Anda yakin ingin mengubah status forum ini?"
|
||||
textLeft="Batal"
|
||||
textRight="Ubah"
|
||||
colorRight={MainColor.green}
|
||||
/>
|
||||
|
||||
{/* Alert Delete */}
|
||||
<AlertCustom
|
||||
isVisible={deleteAlert}
|
||||
onLeftPress={() => setDeleteAlert(false)}
|
||||
onRightPress={() => {
|
||||
setOpenDrawer(false);
|
||||
setDeleteAlert(false);
|
||||
console.log("Hapus forum");
|
||||
}}
|
||||
title="Hapus Forum"
|
||||
message="Apakah Anda yakin ingin menghapus forum ini?"
|
||||
textLeft="Batal"
|
||||
textRight="Hapus"
|
||||
colorRight={MainColor.red}
|
||||
/>
|
||||
{/* <View_Forumku /> */}
|
||||
<View_Forumku2 />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,176 +1,11 @@
|
||||
import {
|
||||
AlertCustom,
|
||||
ButtonCustom,
|
||||
DrawerCustom,
|
||||
Spacing,
|
||||
TextAreaCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import Forum_CommentarBoxSection from "@/screens/Forum/CommentarBoxSection";
|
||||
import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection";
|
||||
import { listDummyCommentarForum } from "@/screens/Forum/list-data-dummy";
|
||||
import Forum_MenuDrawerBerandaSection from "@/screens/Forum/MenuDrawerSection.tsx/MenuBeranda";
|
||||
import Forum_MenuDrawerCommentar from "@/screens/Forum/MenuDrawerSection.tsx/MenuCommentar";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import DetailForum from "@/screens/Forum/DetailForum";
|
||||
import DetailForum2 from "@/screens/Forum/DetailForum2";
|
||||
|
||||
export default function ForumDetail() {
|
||||
const { id } = useLocalSearchParams();
|
||||
console.log(id);
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [status, setStatus] = useState("");
|
||||
const [alertStatus, setAlertStatus] = useState(false);
|
||||
const [deleteAlert, setDeleteAlert] = useState(false);
|
||||
const [text, setText] = useState("");
|
||||
|
||||
// Comentar
|
||||
const [openDrawerCommentar, setOpenDrawerCommentar] = useState(false);
|
||||
const [alertDeleteCommentar, setAlertDeleteCommentar] = useState(false);
|
||||
|
||||
const dataDummy = {
|
||||
name: "Bagas",
|
||||
status: "Open",
|
||||
date: "14/07/2025",
|
||||
deskripsi:
|
||||
"Lorem ipsum dolor sit amet consectetur adipisicing elit. Vitae inventore iure pariatur, libero omnis excepturi. Ullam ad officiis deleniti quos esse odit nesciunt, ipsam adipisci cumque aliquam corporis culpa fugit?",
|
||||
jumlahBalas: 2,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
{/* <StackCustom>
|
||||
</StackCustom> */}
|
||||
<Forum_BoxDetailSection
|
||||
data={dataDummy}
|
||||
setOpenDrawer={setOpenDrawer}
|
||||
setStatus={setStatus}
|
||||
/>
|
||||
|
||||
<TextAreaCustom
|
||||
placeholder="Ketik diskusi anda..."
|
||||
maxLength={1000}
|
||||
showCount
|
||||
value={text}
|
||||
onChangeText={setText}
|
||||
style={{
|
||||
marginBottom: 0,
|
||||
}}
|
||||
/>
|
||||
<ButtonCustom
|
||||
style={{
|
||||
alignSelf: "flex-end",
|
||||
}}
|
||||
onPress={() => {
|
||||
console.log("Posting", text);
|
||||
router.back();
|
||||
}}
|
||||
>
|
||||
Balas
|
||||
</ButtonCustom>
|
||||
|
||||
<Spacing height={40} />
|
||||
|
||||
{listDummyCommentarForum.map((e, i) => (
|
||||
<Forum_CommentarBoxSection
|
||||
key={i}
|
||||
data={e}
|
||||
setOpenDrawer={setOpenDrawerCommentar}
|
||||
/>
|
||||
))}
|
||||
</ViewWrapper>
|
||||
|
||||
<DrawerCustom
|
||||
height={350}
|
||||
isVisible={openDrawer}
|
||||
closeDrawer={() => setOpenDrawer(false)}
|
||||
>
|
||||
<Forum_MenuDrawerBerandaSection
|
||||
id={id as string}
|
||||
status={status}
|
||||
setIsDrawerOpen={() => {
|
||||
setOpenDrawer(false);
|
||||
}}
|
||||
setShowDeleteAlert={setDeleteAlert}
|
||||
setShowAlertStatus={setAlertStatus}
|
||||
/>
|
||||
</DrawerCustom>
|
||||
|
||||
{/* Alert Status */}
|
||||
<AlertCustom
|
||||
isVisible={alertStatus}
|
||||
title="Ubah Status Forum"
|
||||
message="Apakah Anda yakin ingin mengubah status forum ini?"
|
||||
onLeftPress={() => {
|
||||
setOpenDrawer(false);
|
||||
setAlertStatus(false);
|
||||
console.log("Batal");
|
||||
}}
|
||||
onRightPress={() => {
|
||||
setOpenDrawer(false);
|
||||
setAlertStatus(false);
|
||||
console.log("Ubah status forum");
|
||||
}}
|
||||
textLeft="Batal"
|
||||
textRight="Ubah"
|
||||
colorRight={MainColor.green}
|
||||
/>
|
||||
|
||||
{/* Alert Delete */}
|
||||
<AlertCustom
|
||||
isVisible={deleteAlert}
|
||||
title="Hapus Forum"
|
||||
message="Apakah Anda yakin ingin menghapus forum ini?"
|
||||
onLeftPress={() => {
|
||||
setOpenDrawer(false);
|
||||
setDeleteAlert(false);
|
||||
console.log("Batal");
|
||||
}}
|
||||
onRightPress={() => {
|
||||
setOpenDrawer(false);
|
||||
setDeleteAlert(false);
|
||||
console.log("Hapus forum");
|
||||
}}
|
||||
textLeft="Batal"
|
||||
textRight="Hapus"
|
||||
colorRight={MainColor.red}
|
||||
/>
|
||||
|
||||
{/* Commentar */}
|
||||
<DrawerCustom
|
||||
height={350}
|
||||
isVisible={openDrawerCommentar}
|
||||
closeDrawer={() => setOpenDrawerCommentar(false)}
|
||||
>
|
||||
<Forum_MenuDrawerCommentar
|
||||
id={id as string}
|
||||
setIsDrawerOpen={() => {
|
||||
setOpenDrawerCommentar(false);
|
||||
}}
|
||||
setShowDeleteAlert={setAlertDeleteCommentar}
|
||||
/>
|
||||
</DrawerCustom>
|
||||
|
||||
{/* Alert Delete Commentar */}
|
||||
<AlertCustom
|
||||
isVisible={alertDeleteCommentar}
|
||||
title="Hapus Komentar"
|
||||
message="Apakah Anda yakin ingin menghapus komentar ini?"
|
||||
onLeftPress={() => {
|
||||
setOpenDrawerCommentar(false);
|
||||
setAlertDeleteCommentar(false);
|
||||
console.log("Batal");
|
||||
}}
|
||||
onRightPress={() => {
|
||||
setOpenDrawerCommentar(false);
|
||||
setAlertDeleteCommentar(false);
|
||||
console.log("Hapus commentar");
|
||||
}}
|
||||
textLeft="Batal"
|
||||
textRight="Hapus"
|
||||
colorRight={MainColor.red}
|
||||
/>
|
||||
{/* <DetailForum />; */}
|
||||
<DetailForum2 />
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,27 +5,69 @@ import {
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { router } from "expo-router";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiForumCreateReportCommentar } from "@/service/api-client/api-forum";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function ForumOtherReportCommentar() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const { user } = useAuth();
|
||||
const [value, setValue] = useState<string>("");
|
||||
|
||||
const handlerSubmitReport = async () => {
|
||||
const newData = {
|
||||
authorId: user?.id,
|
||||
description: value,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await apiForumCreateReportCommentar({
|
||||
id: id as string,
|
||||
data: newData,
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Laporan berhasil dikirim",
|
||||
});
|
||||
router.back();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal",
|
||||
text2: "Laporan gagal dikirim",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = (
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
disabled={!value}
|
||||
backgroundColor={MainColor.red}
|
||||
textColor={MainColor.white}
|
||||
onPress={() => {
|
||||
console.log("Report lainnya");
|
||||
router.back();
|
||||
handlerSubmitReport();
|
||||
}}
|
||||
>
|
||||
Report
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper footerComponent={handleSubmit}>
|
||||
<TextAreaCustom placeholder="Laporkan Komentar" />
|
||||
<TextAreaCustom
|
||||
placeholder="Laporkan Komentar"
|
||||
value={value}
|
||||
onChangeText={setValue}
|
||||
/>
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -5,17 +5,54 @@ import {
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { router } from "expo-router";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiForumCreateReportPosting } from "@/service/api-client/api-forum";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function ForumOtherReportPosting() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const { user } = useAuth();
|
||||
const [value, setValue] = useState<string>("");
|
||||
|
||||
const handlerSubmitReport = async () => {
|
||||
const newData = {
|
||||
authorId: user?.id,
|
||||
description: value,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await apiForumCreateReportPosting({
|
||||
id: id as string,
|
||||
data: newData,
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Laporan berhasil dikirim",
|
||||
});
|
||||
router.back();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal",
|
||||
text2: "Laporan gagal dikirim",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = (
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
disabled={!value}
|
||||
backgroundColor={MainColor.red}
|
||||
textColor={MainColor.white}
|
||||
onPress={() => {
|
||||
console.log("Report lainnya");
|
||||
router.back();
|
||||
handlerSubmitReport();
|
||||
}}
|
||||
>
|
||||
Report
|
||||
@@ -25,7 +62,11 @@ export default function ForumOtherReportPosting() {
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper footerComponent={handleSubmit}>
|
||||
<TextAreaCustom placeholder="Laporkan Diskusi" />
|
||||
<TextAreaCustom
|
||||
placeholder="Laporkan Diskusi"
|
||||
value={value}
|
||||
onChangeText={setValue}
|
||||
/>
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import {
|
||||
BaseBox,
|
||||
NewWrapper,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
|
||||
import NoDataText from "@/components/_ShareComponent/NoDataText";
|
||||
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
|
||||
import { apiForumGetReportComment } from "@/service/api-client/api-forum";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function ForumPreviewReportComment() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [data, setData] = useState<any | null>(null);
|
||||
const [listData, setListData] = useState<any | null>(null);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
// Status
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData(id as string);
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadData = async (id: string) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await apiForumGetReportComment({ id });
|
||||
setData(response.data);
|
||||
setListData(response?.data?.Forum_ReportKomentar);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<NewWrapper>
|
||||
<StackCustom>
|
||||
<TextCustom color="red" bold>
|
||||
Komentar anda telah melanggar aturan forum ! Admin mengambil
|
||||
tindakan untuk menghapus komentar anda!
|
||||
</TextCustom>
|
||||
{loading ? (
|
||||
<CustomSkeleton height={100} />
|
||||
) : (
|
||||
<BaseBox>
|
||||
<TextCustom>"{data?.komentar ? data?.komentar : "-"}"</TextCustom>
|
||||
</BaseBox>
|
||||
)}
|
||||
</StackCustom>
|
||||
|
||||
<Spacing height={10} />
|
||||
<TextCustom bold>Beberapa laporan yang telah diterima</TextCustom>
|
||||
<Spacing height={10} />
|
||||
|
||||
{loading ? (
|
||||
<ListSkeletonComponent />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<NoDataText />
|
||||
) : (
|
||||
listData?.map((e: any, index: number) => (
|
||||
<BaseBox key={index}>
|
||||
{e?.deskripsi ? (
|
||||
<StackCustom gap={"sm"}>
|
||||
<TextCustom bold>Laporan Lainnya</TextCustom>
|
||||
<TextCustom>{e?.deskripsi}</TextCustom>
|
||||
</StackCustom>
|
||||
) : (
|
||||
<StackCustom gap={"sm"}>
|
||||
<TextCustom bold>
|
||||
{e?.ForumMaster_KategoriReport?.title}
|
||||
</TextCustom>
|
||||
<TextCustom>
|
||||
{e?.ForumMaster_KategoriReport?.deskripsi}
|
||||
</TextCustom>
|
||||
</StackCustom>
|
||||
)}
|
||||
</BaseBox>
|
||||
))
|
||||
)}
|
||||
</NewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import {
|
||||
BaseBox,
|
||||
NewWrapper,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
|
||||
import NoDataText from "@/components/_ShareComponent/NoDataText";
|
||||
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
|
||||
import { apiForumGetReportPosting } from "@/service/api-client/api-forum";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function ForumPreviewReportPosting() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [data, setData] = useState<any | null>(null);
|
||||
const [listData, setListData] = useState<any | null>(null);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
// Status
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData(id as string);
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadData = async (id: string) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await apiForumGetReportPosting({ id });
|
||||
setData(response.data);
|
||||
setListData(response?.data?.Forum_ReportPosting);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<NewWrapper>
|
||||
<StackCustom>
|
||||
<TextCustom color="red" bold>
|
||||
Postingan anda telah melanggar aturan forum ! Admin mengambil
|
||||
tindakan untuk menghapus komentar anda!
|
||||
</TextCustom>
|
||||
{loading ? (
|
||||
<CustomSkeleton height={100} />
|
||||
) : (
|
||||
<BaseBox>
|
||||
<TextCustom>"{data?.diskusi ? data?.diskusi : "-"}"</TextCustom>
|
||||
</BaseBox>
|
||||
)}
|
||||
</StackCustom>
|
||||
|
||||
<Spacing height={10} />
|
||||
<TextCustom bold>Beberapa laporan yang telah diterima</TextCustom>
|
||||
<Spacing height={10} />
|
||||
|
||||
{loading ? (
|
||||
<ListSkeletonComponent />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<NoDataText />
|
||||
) : (
|
||||
listData?.map((e: any) => (
|
||||
<BaseBox key={e?.id}>
|
||||
{e?.deskripsi ? (
|
||||
<StackCustom gap={"sm"}>
|
||||
<TextCustom bold>Laporan Lainnya</TextCustom>
|
||||
<TextCustom>{e?.deskripsi}</TextCustom>
|
||||
</StackCustom>
|
||||
) : (
|
||||
<StackCustom gap={"sm"}>
|
||||
<TextCustom bold>
|
||||
{e?.ForumMaster_KategoriReport?.title}
|
||||
</TextCustom>
|
||||
<TextCustom>
|
||||
{e?.ForumMaster_KategoriReport?.deskripsi}
|
||||
</TextCustom>
|
||||
</StackCustom>
|
||||
)}
|
||||
</BaseBox>
|
||||
))
|
||||
)}
|
||||
</NewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,41 +1,106 @@
|
||||
import {
|
||||
ButtonCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
ViewWrapper
|
||||
ButtonCustom,
|
||||
LoaderCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { AccentColor, MainColor } from "@/constants/color-palet";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import Forum_ReportListSection from "@/screens/Forum/ReportListSection";
|
||||
import { router } from "expo-router";
|
||||
import { apiForumCreateReportCommentar } from "@/service/api-client/api-forum";
|
||||
import { apiMasterForumReportList } from "@/service/api-client/api-master";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import { useState, useEffect } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function ForumReportCommentar() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const { user } = useAuth();
|
||||
const [selectReport, setSelectReport] = useState<string>("");
|
||||
const [listMaster, setListMaster] = useState<any[] | null>(null);
|
||||
const [isLoadingList, setIsLoadingList] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
onLoadListMaster();
|
||||
}, []);
|
||||
|
||||
const onLoadListMaster = async () => {
|
||||
try {
|
||||
setIsLoadingList(true);
|
||||
const response = await apiMasterForumReportList();
|
||||
|
||||
setListMaster(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setIsLoadingList(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerReport = async () => {
|
||||
const newData = {
|
||||
authorId: user?.id,
|
||||
categoryId: selectReport,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await apiForumCreateReportCommentar({
|
||||
id: id as string,
|
||||
data: newData,
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Laporan berhasil dikirim",
|
||||
});
|
||||
router.back();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal",
|
||||
text2: "Laporan gagal dikirim",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
<StackCustom>
|
||||
<Forum_ReportListSection />
|
||||
<ButtonCustom
|
||||
backgroundColor={MainColor.red}
|
||||
textColor={MainColor.white}
|
||||
onPress={() => {
|
||||
console.log("Report");
|
||||
router.back();
|
||||
}}
|
||||
>
|
||||
Report
|
||||
</ButtonCustom>
|
||||
<ButtonCustom
|
||||
backgroundColor={AccentColor.blue}
|
||||
textColor={MainColor.white}
|
||||
onPress={() => {
|
||||
console.log("Lainnya");
|
||||
router.replace("/forum/[id]/other-report-commentar");
|
||||
}}
|
||||
>
|
||||
Lainnya
|
||||
</ButtonCustom>
|
||||
<Spacing/>
|
||||
</StackCustom>
|
||||
{isLoadingList ? (
|
||||
<LoaderCustom />
|
||||
) : (
|
||||
<StackCustom>
|
||||
<Forum_ReportListSection
|
||||
listMaster={listMaster}
|
||||
selectReport={selectReport}
|
||||
setSelectReport={setSelectReport}
|
||||
/>
|
||||
<ButtonCustom
|
||||
disabled={!selectReport}
|
||||
backgroundColor={MainColor.red}
|
||||
textColor={MainColor.white}
|
||||
onPress={() => {
|
||||
handlerReport();
|
||||
}}
|
||||
>
|
||||
Report
|
||||
</ButtonCustom>
|
||||
<ButtonCustom
|
||||
backgroundColor={AccentColor.blue}
|
||||
textColor={MainColor.white}
|
||||
onPress={() => {
|
||||
router.replace(`/forum/${id}/other-report-commentar`);
|
||||
}}
|
||||
>
|
||||
Lainnya
|
||||
</ButtonCustom>
|
||||
<Spacing />
|
||||
</StackCustom>
|
||||
)}
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,20 +1,103 @@
|
||||
import { ViewWrapper, StackCustom, ButtonCustom, Spacing } from "@/components";
|
||||
import { MainColor, AccentColor } from "@/constants/color-palet";
|
||||
import {
|
||||
AlertDefaultSystem,
|
||||
ButtonCustom,
|
||||
LoaderCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { AccentColor, MainColor } from "@/constants/color-palet";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import Forum_ReportListSection from "@/screens/Forum/ReportListSection";
|
||||
import { router } from "expo-router";
|
||||
import { apiForumCreateReportPosting } from "@/service/api-client/api-forum";
|
||||
import {
|
||||
apiMasterForumReportList,
|
||||
} from "@/service/api-client/api-master";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import { useEffect, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function ForumReportPosting() {
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
const { id } = useLocalSearchParams();
|
||||
const { user } = useAuth();
|
||||
const [selectReport, setSelectReport] = useState<string>("");
|
||||
const [listMaster, setListMaster] = useState<any[] | null>(null);
|
||||
const [isLoadingList, setIsLoadingList] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
onLoadListMaster();
|
||||
}, []);
|
||||
|
||||
const onLoadListMaster = async () => {
|
||||
try {
|
||||
setIsLoadingList(true);
|
||||
const response = await apiMasterForumReportList();
|
||||
|
||||
setListMaster(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setIsLoadingList(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerReport = async () => {
|
||||
const newData = {
|
||||
authorId: user?.id,
|
||||
categoryId: selectReport,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await apiForumCreateReportPosting({
|
||||
id: id as string,
|
||||
data: newData,
|
||||
});
|
||||
|
||||
|
||||
if (response.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Laporan berhasil dikirim",
|
||||
});
|
||||
router.back();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal",
|
||||
text2: "Laporan gagal dikirim",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
{isLoadingList ? (
|
||||
<LoaderCustom />
|
||||
) : (
|
||||
<StackCustom>
|
||||
<Forum_ReportListSection />
|
||||
<Forum_ReportListSection
|
||||
listMaster={listMaster}
|
||||
selectReport={selectReport}
|
||||
setSelectReport={setSelectReport}
|
||||
/>
|
||||
|
||||
<ButtonCustom
|
||||
disabled={!selectReport}
|
||||
backgroundColor={MainColor.red}
|
||||
textColor={MainColor.white}
|
||||
onPress={() => {
|
||||
console.log("Report");
|
||||
router.back();
|
||||
AlertDefaultSystem({
|
||||
title: "Laporan Posting",
|
||||
message: "Apakah anda yakin ingin melaporkan postingan ini?",
|
||||
textLeft: "Batal",
|
||||
textRight: "Laporkan",
|
||||
onPressRight: () => {
|
||||
handlerReport();
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Report
|
||||
@@ -23,15 +106,15 @@ export default function ForumReportPosting() {
|
||||
backgroundColor={AccentColor.blue}
|
||||
textColor={MainColor.white}
|
||||
onPress={() => {
|
||||
console.log("Lainnya");
|
||||
router.replace("/forum/[id]/other-report-posting");
|
||||
router.replace(`/forum/${id}/other-report-posting`);
|
||||
}}
|
||||
>
|
||||
Lainnya
|
||||
</ButtonCustom>
|
||||
<Spacing />
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ import {
|
||||
TextAreaCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AlertWarning from "@/components/Alert/AlertWarning";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiForumCreate } from "@/service/api-client/api-forum";
|
||||
import { censorText, isBadContent } from "@/utils/badWordsIndonesia";
|
||||
import { router } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
@@ -16,8 +18,19 @@ export default function ForumCreate() {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handlerSubmit = async () => {
|
||||
if (text.trim() === "") {
|
||||
AlertWarning({
|
||||
title: "Lengkapi Data",
|
||||
description: "Postingan tidak boleh kosong",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Bisa di sensor atau return dan tidak bisa di post
|
||||
const cencorContent = censorText(text)
|
||||
|
||||
const newData = {
|
||||
diskusi: text,
|
||||
diskusi: cencorContent,
|
||||
authorId: user?.id,
|
||||
};
|
||||
|
||||
@@ -42,6 +55,7 @@ export default function ForumCreate() {
|
||||
const buttonFooter = (
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
disabled={!text.trim() || isLoading}
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
handlerSubmit();
|
||||
|
||||
@@ -1,165 +1,14 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AvatarComp,
|
||||
BackButton,
|
||||
DrawerCustom,
|
||||
LoaderCustom,
|
||||
SearchInput,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import FloatingButton from "@/components/Button/FloatingButton";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection";
|
||||
import Forum_MenuDrawerBerandaSection from "@/screens/Forum/MenuDrawerSection.tsx/MenuBeranda";
|
||||
import { apiForumGetAll } from "@/service/api-client/api-forum";
|
||||
import { apiUser } from "@/service/api-client/api-user";
|
||||
import { router, Stack, useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import Forum_ViewBeranda from "@/screens/Forum/ViewBeranda";
|
||||
import Forum_ViewBeranda2 from "@/screens/Forum/ViewBeranda2";
|
||||
import Forum_ViewBeranda3 from "@/screens/Forum/ViewBeranda3";
|
||||
|
||||
export default function Forum() {
|
||||
const id = "test-id-forum";
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [status, setStatus] = useState("");
|
||||
const { user } = useAuth();
|
||||
const [dataUser, setDataUser] = useState<any>();
|
||||
const [listData, setListData] = useState<any[]>();
|
||||
const [loadingGetList, setLoadingGetList] = useState(false);
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
onLoadDataProfile(user?.id as string);
|
||||
}, [user?.id, search])
|
||||
);
|
||||
|
||||
const onLoadDataProfile = async (id: string) => {
|
||||
const response = await apiUser(id);
|
||||
setDataUser(response.data);
|
||||
};
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setLoadingGetList(true);
|
||||
const response = await apiForumGetAll({ search: search });
|
||||
console.log("[DATA PROFILE]", JSON.stringify(response.data, null, 2));
|
||||
|
||||
setListData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadingGetList(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Forum",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => (
|
||||
<AvatarComp
|
||||
fileId={dataUser?.Profile?.imageId}
|
||||
size="base"
|
||||
href={`/forum/${user?.id}/forumku`}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
<ViewWrapper
|
||||
headerComponent={
|
||||
<SearchInput
|
||||
placeholder="Cari topik diskusi"
|
||||
onChangeText={(e) => setSearch(e)}
|
||||
/>
|
||||
}
|
||||
floatingButton={
|
||||
<FloatingButton
|
||||
onPress={() =>
|
||||
router.navigate("/(application)/(user)/forum/create")
|
||||
}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{loadingGetList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom align="center" color="gray">
|
||||
Tidak ada diskusi
|
||||
</TextCustom>
|
||||
) : (
|
||||
listData?.map((e: any, i: number) => (
|
||||
<Forum_BoxDetailSection
|
||||
key={i}
|
||||
data={e}
|
||||
setOpenDrawer={setOpenDrawer}
|
||||
setStatus={setStatus}
|
||||
isTruncate={true}
|
||||
href={`/forum/${id}`}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
|
||||
<DrawerCustom
|
||||
height={"auto"}
|
||||
isVisible={openDrawer}
|
||||
closeDrawer={() => setOpenDrawer(false)}
|
||||
>
|
||||
<Forum_MenuDrawerBerandaSection
|
||||
id={id}
|
||||
status={status}
|
||||
setIsDrawerOpen={() => {
|
||||
setOpenDrawer(false);
|
||||
}}
|
||||
setShowDeleteAlert={() => {}}
|
||||
setShowAlertStatus={() => {}}
|
||||
/>
|
||||
</DrawerCustom>
|
||||
|
||||
{/* Alert Status */}
|
||||
{/* <AlertCustom
|
||||
isVisible={alertStatus}
|
||||
title="Ubah Status Forum"
|
||||
message="Apakah Anda yakin ingin mengubah status forum ini?"
|
||||
onLeftPress={() => {
|
||||
setOpenDrawer(false);
|
||||
setAlertStatus(false);
|
||||
console.log("Batal");
|
||||
}}
|
||||
onRightPress={() => {
|
||||
setOpenDrawer(false);
|
||||
setAlertStatus(false);
|
||||
console.log("Ubah status forum");
|
||||
}}
|
||||
textLeft="Batal"
|
||||
textRight="Ubah"
|
||||
colorRight={MainColor.green}
|
||||
/> */}
|
||||
|
||||
{/* Alert Delete */}
|
||||
{/* <AlertCustom
|
||||
isVisible={deleteAlert}
|
||||
title="Hapus Forum"
|
||||
message="Apakah Anda yakin ingin menghapus forum ini?"
|
||||
onLeftPress={() => {
|
||||
setOpenDrawer(false);
|
||||
setDeleteAlert(false);
|
||||
console.log("Batal");
|
||||
}}
|
||||
onRightPress={() => {
|
||||
setOpenDrawer(false);
|
||||
setDeleteAlert(false);
|
||||
console.log("Hapus forum");
|
||||
}}
|
||||
textLeft="Batal"
|
||||
textRight="Hapus"
|
||||
colorRight={MainColor.red}
|
||||
/> */}
|
||||
{/* <Forum_ViewBeranda /> */}
|
||||
{/* <Forum_ViewBeranda2 /> */}
|
||||
<Forum_ViewBeranda3 />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
202
app/(application)/(user)/forum/terms.tsx
Normal file
@@ -0,0 +1,202 @@
|
||||
import {
|
||||
BaseBox,
|
||||
ButtonCustom,
|
||||
CheckboxCustom,
|
||||
NewWrapper,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiAcceptForumTerms } from "@/service/api-client/api-user";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { router } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { Text } from "react-native-paper";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function ForumSplash() {
|
||||
const { user } = useAuth();
|
||||
const [term, setTerm] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const respone = await apiAcceptForumTerms({
|
||||
category: "Forum",
|
||||
userId: user?.id as any,
|
||||
});
|
||||
|
||||
if (respone.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Berhasil",
|
||||
text2: "Terima kasih telah menerima syarat & ketentuan forum ini",
|
||||
});
|
||||
|
||||
router.replace("/(application)/forum");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal",
|
||||
text2: "Terjadi kesalahan, silahkan coba lagi",
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<NewWrapper>
|
||||
{/* <TextCustom bold>HIPMI Badung Connect</TextCustom> . */}
|
||||
|
||||
<BaseBox>
|
||||
<StackCustom>
|
||||
<TextCustom>
|
||||
Dengan mengakses dan menggunakan Forum HIPMI Badung Connect, Anda
|
||||
secara sadar menyetujui ketentuan berikut:
|
||||
</TextCustom>
|
||||
|
||||
<TextCustom bold>
|
||||
1. Dilarang keras memposting konten yang mengandung:
|
||||
</TextCustom>
|
||||
<View style={{ paddingInline: 10 }}>
|
||||
{forumTerms1.map((term, index) => (
|
||||
<View
|
||||
key={index}
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 10,
|
||||
paddingBottom: 10,
|
||||
}}
|
||||
>
|
||||
<Ionicons name="radio-button-on" color={"white"} />
|
||||
<TextCustom>{term.text}</TextCustom>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
<TextCustom bold>
|
||||
2. Setiap pengguna bertanggung jawab penuh atas konten yang
|
||||
diunggah. Konten yang melanggar ketentuan ini dapat dihapus kapan
|
||||
saja tanpa pemberitahuan.
|
||||
</TextCustom>
|
||||
|
||||
<TextCustom bold>
|
||||
3. Jika Anda menemukan konten tidak pantas, segera:
|
||||
</TextCustom>
|
||||
<View style={{ paddingInline: 10 }}>
|
||||
{forumTerms2.map((term, index) => (
|
||||
<View
|
||||
key={index}
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 10,
|
||||
paddingBottom: 10,
|
||||
}}
|
||||
>
|
||||
<Ionicons name="radio-button-on" color={"white"} />
|
||||
<TextCustom>{term.text}</TextCustom>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
<TextCustom bold>
|
||||
4. Gunakan fitur “Blokir Pengguna” di profil pengguna terkait
|
||||
</TextCustom>
|
||||
<View style={{ paddingInline: 10 }}>
|
||||
{forumTerms3.map((term, index) => (
|
||||
<View
|
||||
key={index}
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 10,
|
||||
paddingBottom: 10,
|
||||
}}
|
||||
>
|
||||
<Ionicons name="radio-button-on" color={"white"} />
|
||||
<TextCustom>{term.text}</TextCustom>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
<TextCustom>
|
||||
Pelanggaran terhadap ketentuan ini berakibat{" "}
|
||||
<TextCustom bold>pencabutan akses</TextCustom> ke Forum dan/atau{" "}
|
||||
<TextCustom bold>pemblokiran akun secara permanen</TextCustom> tanpa
|
||||
pemberitahuan lebih lanjut.
|
||||
</TextCustom>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginTop: 16,
|
||||
marginBottom: 16,
|
||||
}}
|
||||
>
|
||||
<CheckboxCustom value={term} onChange={() => setTerm(!term)} />
|
||||
|
||||
<Text style={GStyles.textLabel}>
|
||||
Saya telah membaca dan menyetujui Syarat & Ketentuan Forum ini
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<ButtonCustom
|
||||
disabled={!term || loading}
|
||||
onPress={() => {
|
||||
handleSubmit();
|
||||
}}
|
||||
>
|
||||
Lanjut
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
</NewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
// Data dalam format JSON (bisa juga diimpor dari file terpisah)
|
||||
interface Term {
|
||||
text: string;
|
||||
}
|
||||
|
||||
const forumTerms1: Term[] = [
|
||||
{
|
||||
text: "Ujaran kebencian, diskriminasi, atau konten SARA (Suku, Agama, Ras, Antar-golongan)",
|
||||
},
|
||||
{ text: "Kata kasar, pelecehan, ancaman, atau bullying" },
|
||||
{ text: "Pornografi, hoaks, spam, atau informasi menyesatkan" },
|
||||
{ text: "Promosi aktivitas ilegal seperti perjudian atau narkoba" },
|
||||
];
|
||||
|
||||
const forumTerms2: Term[] = [
|
||||
{
|
||||
text: "Gunakan tombol “Laporkan” di setiap postingan, atau",
|
||||
},
|
||||
{
|
||||
text: "Gunakan fitur “Blokir Pengguna” di profil pengguna terkait.",
|
||||
},
|
||||
];
|
||||
|
||||
const forumTerms3: Term[] = [
|
||||
{
|
||||
text: "Meninjau setiap laporan dalam waktu 24 jam",
|
||||
},
|
||||
{
|
||||
text: "Menghapus konten yang melanggar",
|
||||
},
|
||||
{
|
||||
text: "Memblokir akun pelanggar sesuai tingkat pelanggaran",
|
||||
},
|
||||
];
|
||||
@@ -1,9 +1,11 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { StackCustom, ViewWrapper } from "@/components";
|
||||
import { BasicWrapper, StackCustom, ViewWrapper } from "@/components";
|
||||
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 Home_ImageSection from "@/screens/Home/imageSection";
|
||||
import TabSection from "@/screens/Home/tabSection";
|
||||
import { tabsHome } from "@/screens/Home/tabsList";
|
||||
@@ -11,39 +13,77 @@ import Home_FeatureSection from "@/screens/Home/topFeatureSection";
|
||||
import { apiUser } from "@/service/api-client/api-user";
|
||||
import { apiVersion } from "@/service/api-config";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { Redirect, router, Stack } from "expo-router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Redirect, router, Stack, useFocusEffect } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import { RefreshControl } from "react-native";
|
||||
|
||||
export default function Application() {
|
||||
const { token, user } = useAuth();
|
||||
|
||||
const { token, user, userData } = useAuth();
|
||||
const [data, setData] = useState<any>();
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const { syncUnreadCount } = useNotificationStore();
|
||||
|
||||
useEffect(() => {
|
||||
onLoadData();
|
||||
checkVersion();
|
||||
}, []);
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
checkVersion();
|
||||
userData(token as string);
|
||||
syncUnreadCount();
|
||||
}, [user?.id, token]),
|
||||
);
|
||||
|
||||
async function onLoadData() {
|
||||
const response = await apiUser(user?.id as string);
|
||||
console.log("Response profile >>", JSON.stringify(response?.data?.Profile, null, 2));
|
||||
console.log(
|
||||
"[Profile ID]>>",
|
||||
JSON.stringify(response?.data?.Profile?.id, null, 2),
|
||||
);
|
||||
|
||||
setData(response.data);
|
||||
}
|
||||
|
||||
const checkVersion = async () => {
|
||||
const response = await apiVersion();
|
||||
console.log("Version >>", JSON.stringify(response.data, null, 2));
|
||||
console.log("[Version] >>", JSON.stringify(response.data, null, 2));
|
||||
};
|
||||
|
||||
const onRefresh = useCallback(() => {
|
||||
setRefreshing(true);
|
||||
onLoadData();
|
||||
checkVersion();
|
||||
setRefreshing(false);
|
||||
}, []);
|
||||
|
||||
// if (user && user?.termsOfServiceAccepted === false) {
|
||||
// console.log("User is not accept term service");
|
||||
// return <Redirect href={`/terms-agreement`} />;
|
||||
// }
|
||||
|
||||
if (data && data?.active === false) {
|
||||
console.log("User is not active");
|
||||
return <Redirect href={`/waiting-room`} />;
|
||||
return (
|
||||
<BasicWrapper>
|
||||
<Redirect href={`/waiting-room`} />
|
||||
</BasicWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
if (data && data?.Profile === null) {
|
||||
console.log("Profile is null");
|
||||
return <Redirect href={`/profile/create`} />;
|
||||
return (
|
||||
<BasicWrapper>
|
||||
<Redirect href={`/profile/create`} />
|
||||
</BasicWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
if (data && data?.masterUserRoleId !== "1") {
|
||||
console.log("User is not admin");
|
||||
return (
|
||||
<BasicWrapper>
|
||||
<Redirect href={`/admin/dashboard`} />
|
||||
</BasicWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -61,24 +101,32 @@ export default function Application() {
|
||||
}}
|
||||
/>
|
||||
),
|
||||
headerRight: () => (
|
||||
<Ionicons
|
||||
name="notifications"
|
||||
size={20}
|
||||
color={MainColor.yellow}
|
||||
onPress={() => {
|
||||
router.push("/notifications");
|
||||
}}
|
||||
/>
|
||||
),
|
||||
headerRight: () => <HeaderBell />,
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={onRefresh}
|
||||
tintColor={MainColor.yellow}
|
||||
colors={[MainColor.yellow]}
|
||||
/>
|
||||
}
|
||||
footerComponent={
|
||||
<TabSection tabs={tabsHome(data?.Profile?.id as string)} />
|
||||
<TabSection
|
||||
tabs={tabsHome({
|
||||
acceptedForumTermsAt: data?.acceptedForumTermsAt,
|
||||
profileId: data?.Profile?.id,
|
||||
})}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<StackCustom>
|
||||
{/* <ButtonCustom onPress={() => router.push("./test-notifications")}>
|
||||
Test Notif
|
||||
</ButtonCustom> */}
|
||||
|
||||
<Home_ImageSection />
|
||||
|
||||
<Home_FeatureSection />
|
||||
|
||||