Compare commits
178 Commits
api/10-oct
...
qc-wrapper
| Author | SHA1 | Date | |
|---|---|---|---|
| eb64c30d49 | |||
| 2f87776d8b | |||
| 243313b8eb | |||
| 8e61c980af | |||
| c709dffd20 | |||
| 54537d2449 | |||
| 66792186ca | |||
| c3cf354c28 | |||
| 6ec839fd67 | |||
| b8f8a361d6 | |||
| 1a5ca78041 | |||
| 502cd7bc65 | |||
| 44d9025afe | |||
| b34bc3799e | |||
| 7cb4f30ae9 | |||
| 0f552443c4 | |||
| 90bc8ae343 | |||
| 98f8c7e2bf | |||
| 81bbd8e6b0 | |||
| 57159d2c45 | |||
| 66373fa65b | |||
| 6d545f2af9 | |||
| eeb95336f2 | |||
| 6fb3b229c3 | |||
| 76deec9c53 | |||
| 31948f71db | |||
| 16decd89c8 | |||
| ecbcc12abf | |||
| 0cb734e790 | |||
| 0d2fef1878 | |||
| 2e58f8c7b4 | |||
| b6cd308b0b | |||
| f68deab8c0 | |||
| 37d2fbe48a | |||
| 57c9215771 | |||
| 4efdbd3c7b | |||
| ad32eb6fe6 | |||
| a5026cc285 | |||
| 836ef709d2 | |||
| 3bbee15c3a | |||
| ad7dbaf162 | |||
| 9c94ec0454 | |||
| 4c63485a5b | |||
| f5d09a2906 | |||
| 67070bb2f1 | |||
| fb19ec60b2 | |||
| e8f5c5b174 | |||
| 74a4d88277 | |||
| 2ad93a26a8 | |||
| 768b0caa9e | |||
| 208b0ce813 | |||
| 66e6aebf41 | |||
| 32a42d1b60 | |||
| 107d4312e1 | |||
| 4862975402 | |||
| f284e2ec02 | |||
| 1d61ad51e5 | |||
| 76845b71b4 | |||
| 97e1f50660 | |||
| 1cbe4ab330 | |||
| 42fa80c228 | |||
| 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 |
45
.gitignore
vendored
@@ -40,3 +40,48 @@ 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
|
||||
|
||||
# secrets
|
||||
secrets/
|
||||
|
||||
# @end expo-cli
|
||||
8
.qwen/settings.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(git add *)"
|
||||
]
|
||||
},
|
||||
"$version": 3
|
||||
}
|
||||
7
.qwen/settings.json.orig
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(git add *)"
|
||||
]
|
||||
}
|
||||
}
|
||||
543
QWEN.md
Normal file
@@ -0,0 +1,543 @@
|
||||
# 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.5)
|
||||
- **Language**: TypeScript
|
||||
- **Architecture**: File-based routing with Expo Router
|
||||
- **State Management**: Context API (AuthContext)
|
||||
- **UI Components**: React Native Paper, custom components
|
||||
- **Maps Integration**: Maplibre Maps for React Native (`@maplibre/maplibre-react-native` v10.4.2)
|
||||
- **Push Notifications**: React Native Firebase Messaging
|
||||
- **Build System**: Metro bundler
|
||||
- **Package Manager**: Bun
|
||||
|
||||
### Project Structure
|
||||
```
|
||||
hipmi-mobile/
|
||||
├── app/ # Main application screens and routing (Expo Router)
|
||||
│ ├── _layout.tsx # Root layout component
|
||||
│ ├── index.tsx # Entry point (Login screen)
|
||||
│ └── (application)/ # Main app screens
|
||||
│ ├── admin/ # Admin panel screens
|
||||
│ ├── (user)/ # User screens
|
||||
│ └── ...
|
||||
├── components/ # Reusable UI components
|
||||
│ ├── _ShareComponent/ # Shared components (NewWrapper, Admin components)
|
||||
│ ├── _Icon/ # Icon components
|
||||
│ └── ...
|
||||
├── context/ # State management (AuthContext)
|
||||
├── screens/ # Screen components organized by feature
|
||||
│ ├── Admin/ # Admin panel screens
|
||||
│ │ ├── Donation/ # Donation management screens
|
||||
│ │ ├── Voting/ # Voting management screens
|
||||
│ │ ├── Event/ # Event management screens
|
||||
│ │ └── ...
|
||||
│ ├── Authentication/ # Login, registration flows
|
||||
│ ├── RootLayout/ # Root layout components
|
||||
│ └── ...
|
||||
├── service/ # API services and business logic
|
||||
│ ├── api-admin/ # Admin API endpoints
|
||||
│ ├── api-client/ # Client API endpoints
|
||||
│ └── api-config.ts # Axios configuration
|
||||
├── hooks/ # Custom React hooks
|
||||
│ ├── use-pagination.tsx # Pagination hook
|
||||
│ └── ...
|
||||
├── helpers/ # Helper functions
|
||||
│ ├── paginationHelpers.tsx # Pagination UI helpers
|
||||
│ └── ...
|
||||
├── types/ # TypeScript type definitions
|
||||
├── utils/ # Utility functions
|
||||
├── constants/ # Constants and configuration values
|
||||
├── styles/ # Global styles
|
||||
├── assets/ # Images, icons, and static assets
|
||||
└── docs/ # Documentation files
|
||||
```
|
||||
|
||||
## Building and Running
|
||||
|
||||
### Prerequisites
|
||||
- **Node.js**: v18+ with Bun package manager
|
||||
- **Expo CLI**: Installed globally or via npx
|
||||
- **iOS**: Xcode (macOS only) for iOS simulator/builds
|
||||
- **Android**: Android Studio for Android emulator/builds
|
||||
|
||||
### Setup and Development
|
||||
|
||||
1. **Install Dependencies**
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
|
||||
2. **Run Development Server**
|
||||
```bash
|
||||
bun run start
|
||||
# or
|
||||
bunx expo start
|
||||
```
|
||||
|
||||
3. **Platform-Specific Commands**
|
||||
```bash
|
||||
# iOS Simulator
|
||||
bun run ios
|
||||
# or
|
||||
bunx expo start --ios
|
||||
|
||||
# Android Emulator
|
||||
bun run android
|
||||
# or
|
||||
bunx expo start --android
|
||||
|
||||
# Web Browser
|
||||
bun run web
|
||||
# or
|
||||
bunx expo start --web
|
||||
```
|
||||
|
||||
4. **Linting**
|
||||
```bash
|
||||
bun run lint
|
||||
```
|
||||
|
||||
### Build Commands
|
||||
|
||||
#### EAS Build (Production)
|
||||
```bash
|
||||
# Production build (App Store / Play Store)
|
||||
eas build --profile production
|
||||
|
||||
# Preview build (Internal distribution)
|
||||
eas build --profile preview
|
||||
|
||||
# Development build (Development client)
|
||||
eas build --profile development
|
||||
```
|
||||
|
||||
#### Local Native Builds
|
||||
```bash
|
||||
# Generate native folders (iOS & Android)
|
||||
npx expo prebuild
|
||||
|
||||
# iOS specific
|
||||
bunx expo prebuild --platform ios
|
||||
open ios/HIPMIBadungConnect.xcworkspace
|
||||
|
||||
# Android specific
|
||||
bunx expo prebuild --platform android
|
||||
```
|
||||
|
||||
#### Version Management
|
||||
```bash
|
||||
# Patch version update
|
||||
npm version patch
|
||||
|
||||
# Update iOS build number
|
||||
bunx expo prebuild --platform ios
|
||||
|
||||
# Update Android version code
|
||||
bunx expo prebuild --platform android
|
||||
```
|
||||
|
||||
### Android Debugging
|
||||
```bash
|
||||
# List connected devices
|
||||
adb devices
|
||||
|
||||
# Install APK to device/emulator
|
||||
adb install android/app/build/outputs/apk/debug/app-debug.apk
|
||||
|
||||
# Install to specific device
|
||||
adb -s <device_id> install android/app/build/outputs/apk/debug/app-debug.apk
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Create a `.env` file in the project root with:
|
||||
|
||||
```env
|
||||
API_BASE_URL=https://your-api-base-url.com
|
||||
BASE_URL=https://your-app-url.com
|
||||
DEEP_LINK_URL=hipmimobile://
|
||||
```
|
||||
|
||||
These are loaded in `app.config.js` and accessible via `Constants.expoConfig.extra`.
|
||||
|
||||
## Architecture Patterns
|
||||
|
||||
### 1. Separation of Concerns
|
||||
|
||||
**Route Files** (`app/`) should be minimal (max 5 lines):
|
||||
```typescript
|
||||
import { Admin_ScreenXXX } from "@/screens/Admin/XXX/ScreenXXX";
|
||||
|
||||
export default function AdminXXX() {
|
||||
return <Admin_ScreenXXX />;
|
||||
}
|
||||
```
|
||||
|
||||
**Screen Components** (`screens/`) contain all business logic:
|
||||
```typescript
|
||||
export function Admin_ScreenXXX() {
|
||||
// Logic, hooks, state management
|
||||
return <NewWrapper ... />;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Pagination Pattern
|
||||
|
||||
Using `usePagination` hook with infinite scroll:
|
||||
|
||||
```typescript
|
||||
const pagination = usePagination({
|
||||
fetchFunction: async (page, searchQuery) => {
|
||||
const response = await apiXXX({ page: String(page) });
|
||||
if (response.success) {
|
||||
return { data: response.data };
|
||||
}
|
||||
return { data: [] };
|
||||
},
|
||||
pageSize: PAGINATION_DEFAULT_TAKE, // 10
|
||||
searchQuery: search,
|
||||
dependencies: [dependency],
|
||||
});
|
||||
|
||||
const { ListEmptyComponent, ListFooterComponent } =
|
||||
createPaginationComponents({
|
||||
loading: pagination.loading,
|
||||
refreshing: pagination.refreshing,
|
||||
listData: pagination.listData,
|
||||
emptyMessage: "Belum ada data",
|
||||
skeletonCount: PAGINATION_DEFAULT_TAKE,
|
||||
});
|
||||
```
|
||||
|
||||
### 3. Wrapper Components
|
||||
|
||||
**NewWrapper** (preferred for lists):
|
||||
```typescript
|
||||
<NewWrapper
|
||||
listData={pagination.listData}
|
||||
renderItem={renderItem}
|
||||
headerComponent={headerComponent}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
ListFooterComponent={ListFooterComponent}
|
||||
onEndReached={pagination.loadMore}
|
||||
refreshControl={<RefreshControl ... />}
|
||||
/>
|
||||
```
|
||||
|
||||
**AdminBasicBox** (for card layouts):
|
||||
```typescript
|
||||
<AdminBasicBox
|
||||
onPress={() => router.push(`/path/${item.id}`)}
|
||||
style={{ marginHorizontal: 10, marginVertical: 5 }}
|
||||
>
|
||||
<StackCustom gap={0}>
|
||||
<GridSpan_4_8 label="Label" value={<TextCustom>Value</TextCustom>} />
|
||||
</StackCustom>
|
||||
</AdminBasicBox>
|
||||
```
|
||||
|
||||
### 4. API Service Structure
|
||||
|
||||
```typescript
|
||||
// service/api-admin/api-xxx.ts
|
||||
export async function apiXXX({ page = "1" }: { page?: string }) {
|
||||
try {
|
||||
const response = await apiConfig.get(`/mobile/admin/xxx?page=${page}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Important**: All list APIs should support pagination with `page` parameter (default: "1").
|
||||
|
||||
### 5. Authentication Flow
|
||||
|
||||
Managed by `AuthContext`:
|
||||
- `loginWithNomor()` - Send phone number, receive OTP
|
||||
- `validateOtp()` - Validate OTP, get token
|
||||
- `registerUser()` - Register new user
|
||||
- `logout()` - Clear session and logout
|
||||
- `userData()` - Fetch user data by token
|
||||
|
||||
## Development Conventions
|
||||
|
||||
### Coding Standards
|
||||
- **TypeScript**: Strict mode enabled
|
||||
- **Naming**:
|
||||
- Components: PascalCase (`Admin_ScreenDonationStatus`)
|
||||
- Files: PascalCase for components (`ScreenDonationStatus.tsx`)
|
||||
- Variables: camelCase
|
||||
- Constants: UPPER_SNAKE_CASE
|
||||
- **Path Aliases**: `@/*` maps to project root
|
||||
- **Imports**: Group imports by type (components, hooks, services, etc.)
|
||||
|
||||
### Component Structure
|
||||
```typescript
|
||||
// 1. Imports (grouped)
|
||||
import { ... } from "@/components";
|
||||
import { ... } from "@/hooks";
|
||||
import { ... } from "@/service";
|
||||
|
||||
// 2. Types/Interfaces
|
||||
interface Props { ... }
|
||||
|
||||
// 3. Main Component
|
||||
export function ComponentName() {
|
||||
// State
|
||||
// Hooks
|
||||
// Functions
|
||||
// Render
|
||||
}
|
||||
```
|
||||
|
||||
### Testing
|
||||
- Linting: `bun run lint`
|
||||
- No formal test suite configured yet
|
||||
|
||||
### Git Workflow
|
||||
- Feature branches: `feature/xxx` or `fixed-admin/xxx`
|
||||
- Commit messages: Clear and descriptive
|
||||
- Use CHANGE_LOG.md for tracking changes
|
||||
|
||||
## Key Features
|
||||
|
||||
### Authentication
|
||||
- Phone number login with OTP
|
||||
- User registration
|
||||
- Terms & Conditions acceptance
|
||||
- Session persistence with AsyncStorage
|
||||
|
||||
### Admin Module
|
||||
- **Dashboard**: Overview and statistics
|
||||
- **User Access**: User management
|
||||
- **Event**: Event CRUD with status management
|
||||
- **Voting**: Voting management (publish/review/reject)
|
||||
- **Donation**: Donation management with categories and transaction tracking
|
||||
- **Collaboration**: Collaboration requests
|
||||
- **Investment**: Investment management
|
||||
- **Maps**: Location-based features
|
||||
- **App Information**: Bank and business field management
|
||||
|
||||
### User Module
|
||||
- **Home**: Main dashboard
|
||||
- **Forum**: Discussion forums
|
||||
- **Profile**: User profile management
|
||||
- **Portfolio**: Member portfolio
|
||||
- **Notifications**: Push notifications via Firebase
|
||||
|
||||
## API Configuration
|
||||
|
||||
### Base URLs
|
||||
```typescript
|
||||
// From app.config.js extra
|
||||
API_BASE_URL: process.env.API_BASE_URL
|
||||
BASE_URL: process.env.BASE_URL
|
||||
DEEP_LINK_URL: process.env.DEEP_LINK_URL
|
||||
```
|
||||
|
||||
### Axios Interceptor
|
||||
All API calls use `apiConfig` with automatic token injection:
|
||||
```typescript
|
||||
apiConfig.interceptors.request.use(async (config) => {
|
||||
const token = await AsyncStorage.getItem("authToken");
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
});
|
||||
```
|
||||
|
||||
## Platform Configuration
|
||||
|
||||
### iOS
|
||||
- **Bundle ID**: `com.anonymous.hipmi-mobile`
|
||||
- **Build Number**: 21
|
||||
- **Google Services**: Configured
|
||||
- **Associated Domains**: `applinks:cld-dkr-staging-hipmi.wibudev.com`
|
||||
- **Tablet Support**: Enabled
|
||||
|
||||
### Android
|
||||
- **Package**: `com.bip.hipmimobileapp`
|
||||
- **Version Code**: 4
|
||||
- **Google Services**: Configured (`google-services.json`)
|
||||
- **Deep Links**: HTTPS intent filters configured
|
||||
- **Edge-to-Edge**: Enabled
|
||||
|
||||
### Web
|
||||
- **Output**: Static
|
||||
- **Bundler**: Metro
|
||||
|
||||
## Special Integrations
|
||||
|
||||
### Firebase
|
||||
- Authentication
|
||||
- Push Notifications (FCM)
|
||||
- Configured for both iOS and Android
|
||||
|
||||
### Maplibre
|
||||
- Map integration via `@maplibre/maplibre-react-native`
|
||||
- Location permissions configured
|
||||
|
||||
### Deep Linking
|
||||
- Scheme: `hipmimobile://`
|
||||
- HTTPS: `cld-dkr-hipmi-stg.wibudev.com`
|
||||
- Configured for both platforms
|
||||
|
||||
### Camera
|
||||
- Camera and microphone permissions
|
||||
- QR code generation support
|
||||
|
||||
## Common Development Tasks
|
||||
|
||||
### Adding a New Admin Screen
|
||||
|
||||
1. **Create Screen Component** (`screens/Admin/Feature/ScreenXXX.tsx`):
|
||||
```typescript
|
||||
export function Admin_ScreenXXX() {
|
||||
const pagination = usePagination({...});
|
||||
const renderItem = useCallback(...);
|
||||
const headerComponent = useMemo(...);
|
||||
|
||||
return <NewWrapper ... />;
|
||||
}
|
||||
```
|
||||
|
||||
2. **Create Box Component** (optional, for custom item rendering):
|
||||
```typescript
|
||||
export default function Admin_BoxXXX({ item }: { item: any }) {
|
||||
return (
|
||||
<AdminBasicBox onPress={() => router.push(...)}>
|
||||
...
|
||||
</AdminBasicBox>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
3. **Update API** (add pagination if needed):
|
||||
```typescript
|
||||
export async function apiXXX({ page = "1" }: { page?: string }) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
4. **Create Route File** (`app/(application)/admin/feature/xxx.tsx`):
|
||||
```typescript
|
||||
import { Admin_ScreenXXX } from "@/screens/Admin/Feature/ScreenXXX";
|
||||
|
||||
export default function AdminXXX() {
|
||||
return <Admin_ScreenXXX />;
|
||||
}
|
||||
```
|
||||
|
||||
### Updating API Endpoints
|
||||
|
||||
1. Add function in appropriate service file
|
||||
2. Include `page` parameter for list endpoints
|
||||
3. Use `apiConfig` axios instance
|
||||
4. Handle errors properly
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Build Issues
|
||||
```bash
|
||||
# Clean and rebuild
|
||||
rm -rf node_modules
|
||||
bun install
|
||||
bunx expo prebuild --clean
|
||||
|
||||
# iOS specific
|
||||
cd ios && pod install && cd ..
|
||||
|
||||
# Android specific
|
||||
cd android && ./gradlew clean && cd ..
|
||||
```
|
||||
|
||||
### Cache Issues
|
||||
```bash
|
||||
# Clear Expo cache
|
||||
bunx expo start -c
|
||||
|
||||
# Clear Metro cache
|
||||
bunx expo start --clear
|
||||
```
|
||||
|
||||
### Dependency Issues
|
||||
```bash
|
||||
# Reinstall dependencies
|
||||
rm -rf node_modules bun.lock
|
||||
bun install
|
||||
```
|
||||
|
||||
### iOS Maplibre Crash Fix
|
||||
|
||||
When using Maplibre MapView on iOS, prevent "Attempt to recycle a mounted view" crash:
|
||||
|
||||
1. **Always render PointAnnotation** (not conditional)
|
||||
2. **Use opacity for visibility** instead of conditional rendering
|
||||
3. **Avoid key prop changes** that force remounting
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Stable PointAnnotation
|
||||
<PointAnnotation
|
||||
coordinate={annotationCoordinate} // Always rendered
|
||||
...
|
||||
>
|
||||
<View style={{ opacity: selectedLocation ? 1 : 0 }}>
|
||||
<SelectedLocationMarker />
|
||||
</View>
|
||||
</PointAnnotation>
|
||||
|
||||
// ❌ BAD: Conditional rendering causes crash
|
||||
{selectedLocation && (
|
||||
<PointAnnotation coordinate={selectedLocation} ... />
|
||||
)}
|
||||
```
|
||||
|
||||
## Documentation Files
|
||||
|
||||
- `docs/CHANGE_LOG.md` - Change log for recent updates
|
||||
- `docs/hipmi-note.md` - Build and deployment notes
|
||||
- `docs/prompt-for-qwen-code.md` - Development prompts and patterns
|
||||
|
||||
## Resources
|
||||
|
||||
- [Expo Documentation](https://docs.expo.dev/)
|
||||
- [React Native Documentation](https://reactnative.dev/)
|
||||
- [Expo Router Documentation](https://docs.expo.dev/router/introduction/)
|
||||
- [TypeScript Documentation](https://www.typescriptlang.org/docs/)
|
||||
- [Maplibre React Native](https://github.com/maplibre/maplibre-react-native)
|
||||
|
||||
## Qwen Added Memories
|
||||
- OS_Wrapper contentPaddingBottom pattern:
|
||||
- Default: contentPaddingBottom=100 (untuk list screens)
|
||||
- Forms: contentPaddingBottom=250 (HANYA untuk screens yang punya TextInput/TextArea)
|
||||
- contentPadding=0 (default, per-screen control)
|
||||
- OS_ANDROID_PADDING_TOP=6 (compact tabs)
|
||||
- OS_IOS_PADDING_TOP=12
|
||||
- PADDING_INLINE=16 (constant)
|
||||
|
||||
Contoh:
|
||||
```tsx
|
||||
// List screen (default 100px)
|
||||
<OS_Wrapper listData={data} renderItem={renderItem} />
|
||||
|
||||
// Form screen (explicit 250px)
|
||||
<OS_Wrapper enableKeyboardHandling contentPaddingBottom={250}>
|
||||
<FormWithTextInput />
|
||||
</OS_Wrapper>
|
||||
```
|
||||
- PADDING_INLINE usage pattern - User preference:
|
||||
- PADDING_INLINE (16px) TIDAK selalu diperlukan
|
||||
- User remove PADDING_INLINE dari Profile screens karena mempersempit box tampilan
|
||||
- Decision: Tambahkan PADDING_INLINE HANYA jika diperlukan per-screen, jangan default
|
||||
- User akan review dan tambahkan sendiri jika perlu
|
||||
|
||||
Profile screens: PADDING_INLINE dihapus dari edit.tsx dan create.tsx
|
||||
- User ingin mengecek semua user layout tabs setelah perubahan height layout tabs donation di constants. Pattern yang perlu dicek: semua tabs screens harus pakai contentPadding={PADDING_INLINE} untuk konsistensi layout.
|
||||
@@ -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 5
|
||||
versionName "1.0.2"
|
||||
|
||||
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"
|
||||
}
|
||||
7
android/app/src/debugOptimized/AndroidManifest.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
|
||||
<application android:usesCleartextTraffic="true" tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning" tools:replace="android:usesCleartextTraffic" />
|
||||
</manifest>
|
||||
@@ -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="hipmi.muku.id" 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,26 @@ 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)
|
||||
url 'https://api.mapbox.com/downloads/v2/releases/maven'
|
||||
def token = project.properties['MAPBOX_DOWNLOADS_TOKEN'] ?: System.getenv('RNMAPBOX_MAPS_DOWNLOAD_TOKEN')
|
||||
if (token) {
|
||||
authentication { basic(BasicAuthentication) }
|
||||
credentials {
|
||||
username = 'mapbox'
|
||||
password = token
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
google()
|
||||
mavenCentral()
|
||||
maven { url 'https://www.jitpack.io' }
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
100
app.config.js
@@ -1,61 +1,106 @@
|
||||
// app.config.js
|
||||
require('dotenv').config();
|
||||
require("dotenv").config();
|
||||
|
||||
// const isDev = process.env.NODE_ENV === "development";
|
||||
// const isStaging = process.env.NEXT_PUBLIC_ENV === "staging";
|
||||
// const isProd = process.env.NEXT_PUBLIC_ENV === "production";
|
||||
|
||||
// Domain berdasarkan environment
|
||||
// const domain = isDev
|
||||
// ? "localhost:3000"
|
||||
// : isStaging
|
||||
// ? "cld-dkr-hipmi-stg.wibudev.com"
|
||||
// : "hipmi.muku.id"; // Production domain
|
||||
|
||||
export default {
|
||||
name: 'HIPMI BADUNG',
|
||||
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.2",
|
||||
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: "./secrets/GoogleService-Info.plist",
|
||||
infoPlist: {
|
||||
ITSAppUsesNonExemptEncryption: false,
|
||||
NSLocationWhenInUseUsageDescription:
|
||||
"Aplikasi membutuhkan akses lokasi untuk menampilkan peta.",
|
||||
},
|
||||
associatedDomains: [
|
||||
"applinks:hipmi.muku.id",
|
||||
],
|
||||
buildNumber: "7",
|
||||
},
|
||||
|
||||
android: {
|
||||
googleServicesFile: "./secrets/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: 5,
|
||||
// softwareKeyboardLayoutMode: 'resize', // option: untuk mengatur keyboard pada room chst collaboration
|
||||
intentFilters: [
|
||||
{
|
||||
action: "VIEW",
|
||||
autoVerify: true, // wajib untuk App Links
|
||||
data: [
|
||||
{
|
||||
scheme: "https",
|
||||
host: "hipmi.muku.id",
|
||||
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',
|
||||
"./plugins/withCustomConfig",
|
||||
"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",
|
||||
"@react-native-firebase/app",
|
||||
[
|
||||
"expo-notifications",
|
||||
{
|
||||
icon: "./assets/images/icon.png",
|
||||
color: "#ffffff",
|
||||
iosDisplayInForeground: true,
|
||||
},
|
||||
],
|
||||
"@maplibre/maplibre-react-native",
|
||||
],
|
||||
|
||||
experiments: {
|
||||
@@ -65,10 +110,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,4 +1,5 @@
|
||||
import { BackButton } from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import PdfViewer from "@/components/_ShareComponent/PdfViewer";
|
||||
import API_STRORAGE from "@/constants/base-url-api-strorage";
|
||||
import { Stack, useLocalSearchParams } from "expo-router";
|
||||
@@ -7,13 +8,12 @@ import { SafeAreaView } from "react-native-safe-area-context";
|
||||
export default function FileScreen() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const url = API_STRORAGE.GET({ fileId: id as string });
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "File",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="File" left={<BackButton />} />,
|
||||
}}
|
||||
/>
|
||||
<SafeAreaView style={{ flex: 1 }} edges={["bottom"]}>
|
||||
|
||||
@@ -1,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>
|
||||
|
||||
@@ -105,7 +105,7 @@ export default function TakePicture() {
|
||||
</Pressable>
|
||||
|
||||
<Pressable onPress={pickImage}>
|
||||
<AntDesign name="folderopen" size={32} color="white" />
|
||||
<AntDesign name="folder-open" size={32} color="white" />
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -1,20 +1,27 @@
|
||||
import { BackButton } from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IconPlus } from "@/components/_Icon";
|
||||
import { IconDot } from "@/components/_Icon/IconComponent";
|
||||
import LeftButtonCustom from "@/components/Button/BackButton";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
|
||||
import { HeaderStyles } from "@/styles/header-styles";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { router, Stack } from "expo-router";
|
||||
|
||||
export default function UserLayout() {
|
||||
return (
|
||||
<>
|
||||
<Stack screenOptions={HeaderStyles}>
|
||||
<Stack>
|
||||
<Stack.Screen
|
||||
name="delete-account"
|
||||
options={{
|
||||
header: () => <AppHeader title="Hapus Akun" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="waiting-room"
|
||||
options={{
|
||||
title: "Waiting Room",
|
||||
headerBackVisible: false,
|
||||
header: () => <AppHeader title="Waiting Room" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -38,59 +45,73 @@ export default function UserLayout() {
|
||||
<Stack.Screen
|
||||
name="user-search/index"
|
||||
options={{
|
||||
title: "Pencarian Pengguna",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Pencarian Pengguna" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* ========== 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={{
|
||||
header: () => <AppHeader title="Event" left={<BackButton path="/home" />} />,
|
||||
}}
|
||||
/> */}
|
||||
|
||||
<Stack.Screen
|
||||
name="event/(tabs)"
|
||||
options={{
|
||||
title: "Event",
|
||||
headerLeft: () => (
|
||||
<LeftButtonCustom path="/(application)/(user)/home" />
|
||||
),
|
||||
header: () => <AppHeader title="Event" left={<BackButton path="/home" />} />,
|
||||
// NOTE: DIPINDAH DI FILE /Event/(Tabs)/_layout.tsx
|
||||
// headerLeft: () => (
|
||||
// <LeftButtonCustom path="/(application)/(user)/home" />
|
||||
// ),
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="event/create"
|
||||
options={{
|
||||
title: "Tambah Event",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Tambah Event" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="event/detail/[id]"
|
||||
options={{
|
||||
title: "Event Detail",
|
||||
headerLeft: () => <LeftButtonCustom />,
|
||||
header: () => <AppHeader title="Event Detail" left={<LeftButtonCustom />} />,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="event/[id]/edit"
|
||||
options={{
|
||||
title: "Edit Event",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Edit Event" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="event/[id]/list-of-participants"
|
||||
options={{
|
||||
title: "Daftar peserta",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Daftar peserta" />,
|
||||
}}
|
||||
/>
|
||||
{/* ========== End Event Section ========= */}
|
||||
@@ -99,22 +120,19 @@ export default function UserLayout() {
|
||||
<Stack.Screen
|
||||
name="collaboration/(tabs)"
|
||||
options={{
|
||||
title: "Collaboration",
|
||||
headerLeft: () => <BackButton path="/home" />,
|
||||
header: () => <AppHeader title="Collaboration" left={<BackButton path="/home" />} />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="collaboration/create"
|
||||
options={{
|
||||
title: "Tambah Proyek",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Tambah Proyek" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="collaboration/[id]/list-of-participants"
|
||||
options={{
|
||||
title: "Daftar Partisipan",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Daftar Partisipan" />,
|
||||
}}
|
||||
/>
|
||||
{/* <Stack.Screen
|
||||
@@ -127,22 +145,19 @@ export default function UserLayout() {
|
||||
<Stack.Screen
|
||||
name="collaboration/[id]/edit"
|
||||
options={{
|
||||
title: "Edit Proyek",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Edit Proyek" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="collaboration/[id]/create-pacticipants"
|
||||
options={{
|
||||
title: "Ajukan Partisipasi",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Ajukan Partisipasi" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="collaboration/[id]/select-of-participants"
|
||||
options={{
|
||||
title: "Pilih Partisipan",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Pilih Partisipan" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -152,29 +167,25 @@ export default function UserLayout() {
|
||||
<Stack.Screen
|
||||
name="voting/create"
|
||||
options={{
|
||||
title: "Tambah Voting",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Tambah Voting" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="voting/(tabs)"
|
||||
options={{
|
||||
title: "Voting",
|
||||
headerLeft: () => <BackButton path="/home" />,
|
||||
header: () => <AppHeader title="Voting" left={<BackButton path="/home" />} />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="voting/[id]/edit"
|
||||
options={{
|
||||
title: "Edit Voting",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Edit Voting" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="voting/[id]/list-of-contributor"
|
||||
options={{
|
||||
title: "Daftar Kontributor",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Daftar Kontributor" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -184,8 +195,7 @@ export default function UserLayout() {
|
||||
<Stack.Screen
|
||||
name="crowdfunding/index"
|
||||
options={{
|
||||
title: "Crowdfunding",
|
||||
headerLeft: () => <BackButton path="/home" />,
|
||||
header: () => <AppHeader title="Crowdfunding" left={<BackButton path="/home" />} />,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -195,103 +205,95 @@ export default function UserLayout() {
|
||||
<Stack.Screen
|
||||
name="investment/(tabs)"
|
||||
options={{
|
||||
title: "Investasi",
|
||||
headerLeft: () => <BackButton path="/crowdfunding" />,
|
||||
header: () => <AppHeader title="Investasi" left={<BackButton path="/crowdfunding" />} />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="investment/create"
|
||||
options={{
|
||||
title: "Tambah Investasi",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Tambah Investasi" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="investment/[id]/index"
|
||||
options={{
|
||||
title: "Detail Investasi",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Detail Investasi" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="investment/[id]/edit"
|
||||
options={{
|
||||
title: "Edit Investasi",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Edit Investasi" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="investment/[id]/edit-prospectus"
|
||||
options={{
|
||||
title: "Edit Prospektus",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Edit Prospektus" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="investment/[id]/(document)/list-of-document"
|
||||
options={{
|
||||
title: "Daftar Dokumen",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Daftar Dokumen" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="investment/[id]/(document)/add-document"
|
||||
options={{
|
||||
title: "Tambah Dokumen",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Tambah Dokumen" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="investment/[id]/(document)/edit-document"
|
||||
options={{
|
||||
title: "Edit Dokumen",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Edit Dokumen" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="investment/[id]/(news)/add-news"
|
||||
options={{
|
||||
title: "Tambah Berita",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Tambah Berita" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="investment/[id]/investor"
|
||||
options={{
|
||||
title: "Investor",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Investor" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="investment/[id]/(transaction-flow)/index"
|
||||
options={{
|
||||
title: "Pembelian Saham",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Pembelian Saham" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="investment/[id]/(transaction-flow)/select-bank"
|
||||
options={{
|
||||
title: "Pilih Bank",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Pilih Bank" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="investment/[id]/(transaction-flow)/invoice"
|
||||
options={{
|
||||
title: "Invoice",
|
||||
headerLeft: () => (
|
||||
<Ionicons
|
||||
name="close"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color={MainColor.yellow}
|
||||
onPress={() =>
|
||||
router.navigate(`/investment/(tabs)/transaction`)
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Invoice"
|
||||
left={
|
||||
<Ionicons
|
||||
name="close"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color={MainColor.yellow}
|
||||
onPress={() =>
|
||||
router.navigate(`/investment/(tabs)/transaction`)
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
),
|
||||
@@ -300,14 +302,18 @@ export default function UserLayout() {
|
||||
<Stack.Screen
|
||||
name="investment/[id]/(transaction-flow)/process"
|
||||
options={{
|
||||
title: "Proses",
|
||||
headerLeft: () => (
|
||||
<Ionicons
|
||||
name="close"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color={MainColor.yellow}
|
||||
onPress={() =>
|
||||
router.navigate(`/investment/(tabs)/transaction`)
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Proses"
|
||||
left={
|
||||
<Ionicons
|
||||
name="close"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color={MainColor.yellow}
|
||||
onPress={() =>
|
||||
router.navigate(`/investment/(tabs)/transaction`)
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
),
|
||||
@@ -316,23 +322,20 @@ export default function UserLayout() {
|
||||
<Stack.Screen
|
||||
name="investment/[id]/(transaction-flow)/success"
|
||||
options={{
|
||||
title: "Transaksi Berhasil",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Transaksi Berhasil" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="investment/[id]/(transaction-flow)/failed"
|
||||
options={{
|
||||
title: "Transaksi Gagal",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Transaksi Gagal" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="investment/[id]/(my-holding)/[id]"
|
||||
options={{
|
||||
title: "Detail Saham Saya",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Detail Saham Saya" />,
|
||||
}}
|
||||
/>
|
||||
{/* ========== End Investment Section ========= */}
|
||||
@@ -341,122 +344,111 @@ export default function UserLayout() {
|
||||
<Stack.Screen
|
||||
name="donation/(tabs)"
|
||||
options={{
|
||||
title: "Donasi",
|
||||
headerLeft: () => <BackButton path="/crowdfunding" />,
|
||||
header: () => <AppHeader title="Donasi" left={<BackButton path="/crowdfunding" />} />,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="donation/create"
|
||||
options={{
|
||||
title: "Tambah Donasi",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Tambah Donasi" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="donation/create-story"
|
||||
options={{
|
||||
title: "Tambah Donasi",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Tambah Donasi" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="donation/[id]/edit"
|
||||
options={{
|
||||
title: "Edit Donasi",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Edit Donasi" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="donation/[id]/edit-story"
|
||||
options={{
|
||||
title: "Edit Donasi",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Edit Donasi" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="donation/[id]/edit-rekening"
|
||||
options={{
|
||||
title: "Edit Rekening",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Edit Rekening" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="donation/[id]/detail-story"
|
||||
options={{
|
||||
title: "Cerita Penggalang",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Cerita Penggalang" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="donation/[id]/infromation-fundrising"
|
||||
options={{
|
||||
title: "Informasi Penggalang Dana",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Informasi Penggalang Dana" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="donation/[id]/list-of-donatur"
|
||||
options={{
|
||||
title: "Daftar Donatur",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Daftar Donatur" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="donation/[id]/fund-disbursement"
|
||||
options={{
|
||||
title: "Pencairan Dana",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Pencairan Dana" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="donation/[id]/(news)/recap-of-news"
|
||||
options={{
|
||||
title: "Rekap Kabar",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Rekap Kabar" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="donation/[id]/(news)/add-news"
|
||||
options={{
|
||||
title: "Tambah Berita",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Tambah Berita" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="donation/[id]/(news)/[news]/edit-news"
|
||||
options={{
|
||||
title: "Edit Berita",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Edit Berita" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="donation/[id]/(transaction-flow)/index"
|
||||
options={{
|
||||
title: "Donasi",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Donasi" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="donation/[id]/(transaction-flow)/select-bank"
|
||||
options={{
|
||||
title: "Pilih Bank",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Pilih Bank" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="donation/[id]/(transaction-flow)/[invoiceId]/invoice"
|
||||
options={{
|
||||
title: "Invoice",
|
||||
headerLeft: () => (
|
||||
<Ionicons
|
||||
name="close"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color={MainColor.yellow}
|
||||
onPress={() => router.navigate(`/donation/(tabs)/my-donation`)}
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Invoice"
|
||||
left={
|
||||
<Ionicons
|
||||
name="close"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color={MainColor.yellow}
|
||||
onPress={() => router.navigate(`/donation/(tabs)/my-donation`)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
@@ -464,13 +456,17 @@ export default function UserLayout() {
|
||||
<Stack.Screen
|
||||
name="donation/[id]/(transaction-flow)/[invoiceId]/process"
|
||||
options={{
|
||||
title: "Proses",
|
||||
headerLeft: () => (
|
||||
<Ionicons
|
||||
name="close"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color={MainColor.yellow}
|
||||
onPress={() => router.navigate(`/donation/(tabs)/my-donation`)}
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Proses"
|
||||
left={
|
||||
<Ionicons
|
||||
name="close"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color={MainColor.yellow}
|
||||
onPress={() => router.navigate(`/donation/(tabs)/my-donation`)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
@@ -478,54 +474,51 @@ export default function UserLayout() {
|
||||
<Stack.Screen
|
||||
name="donation/[id]/(transaction-flow)/[invoiceId]/success"
|
||||
options={{
|
||||
title: "Donasi Berhasil",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Donasi Berhasil" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="donation/[id]/(transaction-flow)/[invoiceId]/failed"
|
||||
options={{
|
||||
title: "Donasi Gagal",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Donasi Gagal" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* ========== End Donation Section ========= */}
|
||||
|
||||
{/* ========== Job Section ========= */}
|
||||
|
||||
|
||||
<Stack.Screen
|
||||
name="job/create"
|
||||
options={{
|
||||
title: "Tambah Job",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Tambah Job" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="job/(tabs)"
|
||||
options={{
|
||||
title: "Job Vacancy",
|
||||
headerLeft: () => <BackButton path="/home" />,
|
||||
// NOTE: headerLeft di pindahkan ke Tabs Layout
|
||||
header: () => <AppHeader title="Job Vacancy" left={<BackButton path="/home" />} />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="job/[id]/index"
|
||||
options={{
|
||||
title: "Detail Job",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Detail Job" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="job/[id]/edit"
|
||||
options={{
|
||||
title: "Edit Job",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Edit Job" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="job/[id]/archive"
|
||||
options={{
|
||||
title: "Arsip Job",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Arsip Job" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -535,57 +528,67 @@ export default function UserLayout() {
|
||||
<Stack.Screen
|
||||
name="forum/create"
|
||||
options={{
|
||||
title: "Tambah Diskusi",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Tambah Diskusi" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="forum/[id]/edit"
|
||||
options={{
|
||||
title: "Edit Diskusi",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Edit Diskusi" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="forum/[id]/forumku"
|
||||
options={{
|
||||
title: "Forumku",
|
||||
headerLeft: () => <BackButton icon={"close"} />,
|
||||
header: () => <AppHeader title="Forumku" left={<BackButton icon={"close"} />} />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="forum/[id]/index"
|
||||
options={{
|
||||
title: "Detail",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Detail" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="forum/[id]/report-commentar"
|
||||
options={{
|
||||
title: "Laporkan Komentar",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Laporkan Komentar" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="forum/[id]/other-report-commentar"
|
||||
options={{
|
||||
title: "Laporkan Komentar",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Laporkan Komentar" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="forum/[id]/report-posting"
|
||||
options={{
|
||||
title: "Laporkan Diskusi",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Laporkan Diskusi" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="forum/[id]/other-report-posting"
|
||||
options={{
|
||||
title: "Laporkan Diskusi",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Laporkan Diskusi" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="forum/terms"
|
||||
options={{
|
||||
header: () => <AppHeader title="Syarat & Ketentuan Forum" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="forum/[id]/preview-report-posting"
|
||||
options={{
|
||||
header: () => <AppHeader title="Laporan Postingan" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="forum/[id]/preview-report-comment"
|
||||
options={{
|
||||
header: () => <AppHeader title="Laporan Komentar" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -593,29 +596,25 @@ export default function UserLayout() {
|
||||
<Stack.Screen
|
||||
name="maps/index"
|
||||
options={{
|
||||
title: "Maps",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Maps" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="maps/create"
|
||||
options={{
|
||||
title: "Tambah Maps",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Tambah Maps" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="maps/[id]/edit"
|
||||
options={{
|
||||
title: "Edit Maps",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Edit Maps" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="maps/[id]/custom-pin"
|
||||
options={{
|
||||
title: "Custom Pin Maps",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Custom Pin Maps" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -623,8 +622,7 @@ export default function UserLayout() {
|
||||
<Stack.Screen
|
||||
name="marketplace/index"
|
||||
options={{
|
||||
title: "Market Place",
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Market Place" />,
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
@@ -2,35 +2,64 @@ import { IconHome } from "@/components/_Icon";
|
||||
import { TabsStyles } from "@/styles/tabs-styles";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { Tabs } from "expo-router";
|
||||
import { View } from "react-native";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { Platform } from "react-native";
|
||||
|
||||
function CollaborationTabsWrapper() {
|
||||
const insets = useSafeAreaInsets();
|
||||
const paddingBottom = Platform.OS === "android" ? insets.bottom : 0;
|
||||
|
||||
export default function CollaborationTabsLayout() {
|
||||
return (
|
||||
<Tabs screenOptions={TabsStyles}>
|
||||
<Tabs.Screen
|
||||
name="index"
|
||||
options={{
|
||||
title: "Beranda",
|
||||
tabBarIcon: ({ color }) => <IconHome color={color} />,
|
||||
<View style={{ flex: 1, backgroundColor: MainColor.darkblue }}>
|
||||
<Tabs
|
||||
screenOptions={{
|
||||
...TabsStyles,
|
||||
tabBarStyle: Platform.select({
|
||||
ios: {
|
||||
borderTopWidth: 0,
|
||||
paddingTop: 12,
|
||||
height: 80,
|
||||
},
|
||||
android: {
|
||||
borderTopWidth: 0,
|
||||
paddingTop: 5,
|
||||
height: 70 + paddingBottom,
|
||||
},
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="participant"
|
||||
options={{
|
||||
title: "Partisipan",
|
||||
tabBarIcon: ({ color }) => (
|
||||
<Ionicons size={20} name="people" color={color} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="group"
|
||||
options={{
|
||||
title: "Grup",
|
||||
tabBarIcon: ({ color }) => (
|
||||
<Ionicons size={20} name="chatbox-ellipses" color={color} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
>
|
||||
<Tabs.Screen
|
||||
name="index"
|
||||
options={{
|
||||
title: "Beranda",
|
||||
tabBarIcon: ({ color }) => <IconHome color={color} />,
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="participant"
|
||||
options={{
|
||||
title: "Partisipan",
|
||||
tabBarIcon: ({ color }) => (
|
||||
<Ionicons size={20} name="people" color={color} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="group"
|
||||
options={{
|
||||
title: "Grup",
|
||||
tabBarIcon: ({ color }) => (
|
||||
<Ionicons size={20} name="chatbox-ellipses" color={color} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default function CollaborationTabsLayout() {
|
||||
return <CollaborationTabsWrapper />;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { apiCollaborationGroup } from "@/service/api-client/api-collaboration";
|
||||
import { Stack, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { useState, useCallback } from "react";
|
||||
@@ -40,8 +41,7 @@ export default function CollaborationRoomInfo() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: `Info`,
|
||||
headerLeft: () => <BackButton />,
|
||||
header: () => <AppHeader title="Info" left={<BackButton />} />,
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { BackButton } from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
|
||||
import ChatScreen from "@/screens/Collaboration/GroupChatSection";
|
||||
@@ -12,14 +13,18 @@ export default function CollaborationRoomChat() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: `Proyek ${detail}`,
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => (
|
||||
<Feather
|
||||
name="info"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color={MainColor.yellow}
|
||||
onPress={() => router.push(`/collaboration/${id}/${detail}/info`)}
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title={`Proyek ${detail}`}
|
||||
left={<BackButton />}
|
||||
right={
|
||||
<Feather
|
||||
name="info"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color={MainColor.yellow}
|
||||
onPress={() => router.push(`/collaboration/${id}/${detail}/info`)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
MenuDrawerDynamicGrid,
|
||||
ViewWrapper
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection";
|
||||
import { apiCollaborationGetOne } from "@/service/api-client/api-collaboration";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
@@ -38,10 +39,14 @@ export default function CollaborationDetailParticipant() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Detail Proyek",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => (
|
||||
<DotButton onPress={() => setOpenDrawerParticipant(true)} />
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Detail Proyek"
|
||||
left={<BackButton />}
|
||||
right={
|
||||
<DotButton onPress={() => setOpenDrawerParticipant(true)} />
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
Spacing,
|
||||
ViewWrapper
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IconEdit } from "@/components/_Icon";
|
||||
import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection";
|
||||
import {
|
||||
@@ -66,9 +67,13 @@ export default function CollaborationDetailProjectMain() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Proyek Saya",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Proyek Saya"
|
||||
left={<BackButton />}
|
||||
right={<DotButton onPress={() => setOpenDrawer(true)} />}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
MenuDrawerDynamicGrid,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection";
|
||||
import {
|
||||
@@ -74,10 +75,14 @@ export default function CollaborationDetail() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Detail Proyek",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => (
|
||||
<DotButton onPress={() => setOpenDrawerMenu(true)} />
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Detail Proyek"
|
||||
left={<BackButton />}
|
||||
right={
|
||||
<DotButton onPress={() => setOpenDrawerMenu(true)} />
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {
|
||||
BaseBox,
|
||||
Grid,
|
||||
OS_Wrapper,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
|
||||
@@ -25,7 +25,7 @@ export default function Crowdfunding() {
|
||||
];
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<OS_Wrapper>
|
||||
<StackCustom>
|
||||
<Image
|
||||
source={require("@/assets/images/constants/crowd-hipmi.png")}
|
||||
@@ -63,6 +63,6 @@ export default function Crowdfunding() {
|
||||
</BaseBox>
|
||||
))}
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
</OS_Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
114
app/(application)/(user)/delete-account.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
import {
|
||||
AlertDefaultSystem,
|
||||
BaseBox,
|
||||
ButtonCustom,
|
||||
CenterCustom,
|
||||
OS_Wrapper,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
TextInputCustom,
|
||||
} 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 (
|
||||
<>
|
||||
<OS_Wrapper
|
||||
enableKeyboardHandling
|
||||
contentPaddingBottom={250}
|
||||
>
|
||||
<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>
|
||||
</OS_Wrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,37 +1,66 @@
|
||||
import { IconHome, IconStatus } from "@/components/_Icon";
|
||||
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
|
||||
import { ICON_SIZE_SMALL, OS_ANDROID_HEIGHT, OS_IOS_HEIGHT } from "@/constants/constans-value";
|
||||
import { TabsStyles } from "@/styles/tabs-styles";
|
||||
import {
|
||||
FontAwesome5
|
||||
} from "@expo/vector-icons";
|
||||
import { Tabs } from "expo-router";
|
||||
import { View } from "react-native";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { Platform } from "react-native";
|
||||
|
||||
function DonationTabsWrapper() {
|
||||
const insets = useSafeAreaInsets();
|
||||
const paddingBottom = Platform.OS === "android" ? insets.bottom : 0;
|
||||
|
||||
export default function InvestmentTabsLayout() {
|
||||
return (
|
||||
<Tabs screenOptions={TabsStyles}>
|
||||
<Tabs.Screen
|
||||
name="index"
|
||||
options={{
|
||||
title: "Beranda",
|
||||
tabBarIcon: ({ color }) => <IconHome color={color} />,
|
||||
<View style={{ flex: 1, backgroundColor: MainColor.darkblue }}>
|
||||
<Tabs
|
||||
screenOptions={{
|
||||
...TabsStyles,
|
||||
tabBarStyle: Platform.select({
|
||||
ios: {
|
||||
borderTopWidth: 0,
|
||||
paddingTop: 12,
|
||||
height: OS_IOS_HEIGHT,
|
||||
},
|
||||
android: {
|
||||
borderTopWidth: 0,
|
||||
paddingTop: 5,
|
||||
height: OS_ANDROID_HEIGHT + paddingBottom,
|
||||
},
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="status"
|
||||
options={{
|
||||
title: "Galang Dana",
|
||||
tabBarIcon: ({ color }) => <IconStatus color={color} />,
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="my-donation"
|
||||
options={{
|
||||
title: "Donasi Saya",
|
||||
tabBarIcon: ({ color }) => (
|
||||
<FontAwesome5 name="donate" color={color} size={ICON_SIZE_SMALL} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
>
|
||||
<Tabs.Screen
|
||||
name="index"
|
||||
options={{
|
||||
title: "Beranda",
|
||||
tabBarIcon: ({ color }) => <IconHome color={color} />,
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="status"
|
||||
options={{
|
||||
title: "Galang Dana",
|
||||
tabBarIcon: ({ color }) => <IconStatus color={color} />,
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="my-donation"
|
||||
options={{
|
||||
title: "Donasi Saya",
|
||||
tabBarIcon: ({ color }) => (
|
||||
<FontAwesome5 name="donate" color={color} size={ICON_SIZE_SMALL} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default function DonationTabsLayout() {
|
||||
return <DonationTabsWrapper />;
|
||||
}
|
||||
|
||||
@@ -1,57 +1,9 @@
|
||||
import {
|
||||
FloatingButton,
|
||||
LoaderCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import Donation_BoxPublish from "@/screens/Donation/BoxPublish";
|
||||
import { apiDonationGetAll } from "@/service/api-client/api-donation";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import Donation_ScreenBeranda from "@/screens/Donation/ScreenBeranda";
|
||||
|
||||
export default function DonationBeranda() {
|
||||
const [list, setList] = useState<any[] | null>(null);
|
||||
const [loadList, setLoadList] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setLoadList(true);
|
||||
const response = await apiDonationGetAll({
|
||||
category: "beranda"
|
||||
});
|
||||
console.log("[RES GET ALL]", JSON.stringify(response.data, null, 2));
|
||||
|
||||
setList(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadList(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper
|
||||
hideFooter
|
||||
floatingButton={
|
||||
<FloatingButton onPress={() => router.push("/donation/create")} />
|
||||
}
|
||||
>
|
||||
{loadList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(list) ? (
|
||||
<TextCustom align="center" color="gray">Belum ada donasi</TextCustom>
|
||||
) : (
|
||||
list?.map((item: any, index: number) => (
|
||||
<Donation_BoxPublish data={item} key={index} id={item.id} />
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
<>
|
||||
<Donation_ScreenBeranda />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,142 +1,5 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BadgeCustom,
|
||||
BaseBox,
|
||||
DummyLandscapeImage,
|
||||
Grid,
|
||||
LoaderCustom,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiDonationGetAll } from "@/service/api-client/api-donation";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import { Href, router, useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import Donation_ScreenMyDonation from "@/screens/Donation/ScreenMyDonation";
|
||||
|
||||
export default function DonationMyDonation() {
|
||||
const { user } = useAuth();
|
||||
const [list, setList] = useState<any[] | null>(null);
|
||||
const [loadList, setLoadList] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [user?.id])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setLoadList(true);
|
||||
const response = await apiDonationGetAll({
|
||||
category: "my-donation",
|
||||
authorId: user?.id,
|
||||
});
|
||||
console.log(
|
||||
"[RES GET MY DONATION]",
|
||||
JSON.stringify(response.data, null, 2)
|
||||
);
|
||||
|
||||
setList(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadList(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerColor = (status: string) => {
|
||||
if (status === "menunggu") {
|
||||
return "orange";
|
||||
} else if (status === "proses") {
|
||||
return "white";
|
||||
} else if (status === "berhasil") {
|
||||
return "green";
|
||||
} else if (status === "gagal") {
|
||||
return "red";
|
||||
}
|
||||
};
|
||||
|
||||
const handlePress = ({
|
||||
invoiceId,
|
||||
donationId,
|
||||
status,
|
||||
}: {
|
||||
invoiceId: string;
|
||||
donationId: string;
|
||||
status: string;
|
||||
}) => {
|
||||
const url: Href = `../${donationId}/(transaction-flow)/${invoiceId}`;
|
||||
if (status === "menunggu") {
|
||||
router.push(`${url}/invoice`);
|
||||
} else if (status === "proses") {
|
||||
router.push(`${url}/process`);
|
||||
} else if (status === "berhasil") {
|
||||
router.push(`${url}/success`);
|
||||
} else if (status === "gagal") {
|
||||
router.push(`${url}/failed`);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper hideFooter>
|
||||
{loadList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(list) ? (
|
||||
<TextCustom align="center" color="gray">
|
||||
Belum ada transaksi
|
||||
</TextCustom>
|
||||
) : (
|
||||
list?.map((item, index) => (
|
||||
<BaseBox
|
||||
key={index}
|
||||
paddingTop={7}
|
||||
paddingBottom={7}
|
||||
onPress={() => {
|
||||
handlePress({
|
||||
status: _.lowerCase(item.statusInvoice),
|
||||
invoiceId: item.id,
|
||||
donationId: item.donasiId,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Grid>
|
||||
<Grid.Col span={5}>
|
||||
<DummyLandscapeImage
|
||||
height={100}
|
||||
unClickPath
|
||||
imageId={item.imageId}
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={1}>
|
||||
<View />
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
<StackCustom>
|
||||
<TextCustom truncate={2} bold>
|
||||
{item.title || "-"}
|
||||
</TextCustom>
|
||||
|
||||
<TextCustom bold color="yellow">
|
||||
Rp. {formatCurrencyDisplay(item.nominal)}
|
||||
</TextCustom>
|
||||
|
||||
<BadgeCustom
|
||||
variant="light"
|
||||
color={handlerColor(_.lowerCase(item.statusInvoice))}
|
||||
fullWidth
|
||||
>
|
||||
{item.statusInvoice}
|
||||
</BadgeCustom>
|
||||
</StackCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
);
|
||||
return <Donation_ScreenMyDonation />;
|
||||
}
|
||||
|
||||
@@ -1,80 +1,10 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
LoaderCustom,
|
||||
ScrollableCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
|
||||
import Donasi_BoxStatus from "@/screens/Donation/BoxStatus";
|
||||
import { apiDonationGetByStatus } from "@/service/api-client/api-donation";
|
||||
import { useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import Donation_ScreenStatus from "@/screens/Donation/ScreenStatus";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
|
||||
export default function DonationStatus() {
|
||||
const { user } = useAuth();
|
||||
const [activeCategory, setActiveCategory] = useState<string | null>(
|
||||
"publish"
|
||||
);
|
||||
const [listData, setListData] = useState<any[] | null>(null);
|
||||
const [loadList, setLoadList] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadList();
|
||||
}, [activeCategory])
|
||||
);
|
||||
|
||||
const onLoadList = async () => {
|
||||
try {
|
||||
setLoadList(true);
|
||||
const response = await apiDonationGetByStatus({
|
||||
authorId: user?.id as string,
|
||||
status: activeCategory as string,
|
||||
});
|
||||
|
||||
setListData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
setListData(null);
|
||||
} finally {
|
||||
setLoadList(false);
|
||||
}
|
||||
};
|
||||
|
||||
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}>
|
||||
{loadList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom align="center">Tidak ada data {activeCategory}</TextCustom>
|
||||
) : (
|
||||
listData?.map((item: any, index: number) => (
|
||||
<Donasi_BoxStatus
|
||||
key={index}
|
||||
data={item}
|
||||
status={activeCategory as string}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
<Donation_ScreenStatus initialStatus={status || "publish"} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
InformationBox,
|
||||
LandscapeFrameUploaded,
|
||||
OS_Wrapper,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextAreaCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import API_STRORAGE from "@/constants/base-url-api-strorage";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
@@ -31,7 +32,7 @@ export default function DonationEditNews() {
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [news])
|
||||
}, [news]),
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
@@ -104,7 +105,23 @@ export default function DonationEditNews() {
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<OS_Wrapper
|
||||
enableKeyboardHandling
|
||||
contentPaddingBottom={250}
|
||||
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
|
||||
@@ -148,17 +165,8 @@ export default function DonationEditNews() {
|
||||
/>
|
||||
|
||||
<Spacing />
|
||||
<ButtonCustom
|
||||
disabled={!data?.title || !data?.deskripsi}
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
handlerSubmitUpdate();
|
||||
}}
|
||||
>
|
||||
Update
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
</OS_Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,10 +7,11 @@ import {
|
||||
DrawerCustom,
|
||||
DummyLandscapeImage,
|
||||
MenuDrawerDynamicGrid,
|
||||
OS_Wrapper,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IconEdit } from "@/components/_Icon";
|
||||
import { IconTrash } from "@/components/_Icon/IconTrash";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
@@ -27,6 +28,7 @@ import {
|
||||
} from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
import { dateTimeView } from "@/utils/dateTimeView";
|
||||
|
||||
export default function DonationNews() {
|
||||
const { user } = useAuth();
|
||||
@@ -57,19 +59,24 @@ export default function DonationNews() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Detail Kabar",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () =>
|
||||
user?.id === data?.authorId && (
|
||||
<DotButton onPress={() => setOpenDrawer(true)} />
|
||||
),
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Detail Kabar"
|
||||
left={<BackButton />}
|
||||
right={
|
||||
user?.id === data?.authorId && (
|
||||
<DotButton onPress={() => setOpenDrawer(true)} />
|
||||
)
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
<OS_Wrapper>
|
||||
<BaseBox>
|
||||
<StackCustom>
|
||||
<TextCustom style={{ alignSelf: "flex-end" }}>
|
||||
{formatChatTime(data?.createdAt)}
|
||||
<TextCustom color="gray" size={"small"} style={{ alignSelf: "flex-end" }}>
|
||||
{dateTimeView({date: data?.createdAt})}
|
||||
</TextCustom>
|
||||
|
||||
{data && data.imageId && (
|
||||
@@ -83,7 +90,7 @@ export default function DonationNews() {
|
||||
<TextCustom>{data?.deskripsi || "-"}</TextCustom>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
</ViewWrapper>
|
||||
</OS_Wrapper>
|
||||
|
||||
<DrawerCustom
|
||||
isVisible={openDrawer}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
InformationBox,
|
||||
LandscapeFrameUploaded,
|
||||
OS_Wrapper,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextAreaCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
import { apiDonationCreateNews } from "@/service/api-client/api-donation";
|
||||
@@ -53,7 +54,7 @@ export default function DonationAddNews() {
|
||||
text1: "Gagal menambah berita",
|
||||
});
|
||||
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
@@ -70,7 +71,23 @@ export default function DonationAddNews() {
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<OS_Wrapper
|
||||
enableKeyboardHandling
|
||||
contentPaddingBottom={250}
|
||||
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 image={image?.uri} />
|
||||
@@ -116,17 +133,7 @@ export default function DonationAddNews() {
|
||||
/>
|
||||
|
||||
<Spacing />
|
||||
<ButtonCustom
|
||||
disabled={!data.title || !data.deskripsi}
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
handlerSubmit();
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
</OS_Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,110 +1,8 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BackButton,
|
||||
BaseBox,
|
||||
DrawerCustom,
|
||||
Grid,
|
||||
LoaderCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { IconPlus } from "@/components/_Icon";
|
||||
import { apiDonationGetNewsById } from "@/service/api-client/api-donation";
|
||||
import { formatChatTime } from "@/utils/formatChatTime";
|
||||
import {
|
||||
router,
|
||||
Stack,
|
||||
useFocusEffect,
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, 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);
|
||||
const [list, setList] = useState<any[] | null>(null);
|
||||
const [loadList, setLoadList] = useState<boolean>(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadList();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadList = async () => {
|
||||
try {
|
||||
setLoadList(true);
|
||||
const response = await apiDonationGetNewsById({
|
||||
id: id as string,
|
||||
category: "get-all",
|
||||
});
|
||||
|
||||
setList(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
setList([]);
|
||||
} finally {
|
||||
setLoadList(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Daftar Kabar",
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
{loadList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(list) ? (
|
||||
<TextCustom align="center" color="gray">
|
||||
Tidak ada kabar
|
||||
</TextCustom>
|
||||
) : (
|
||||
list?.map((item: any, index: number) => (
|
||||
<BaseBox key={index} href={`/donation/[id]/(news)/${item.id}`}>
|
||||
<Grid>
|
||||
<Grid.Col span={8}>
|
||||
<TextCustom truncate bold>
|
||||
{item?.title || "-"}
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={4} style={{ alignItems: "flex-end" }}>
|
||||
<TextCustom size="small">
|
||||
{formatChatTime(item?.createdAt)}
|
||||
</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,112 +1,8 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BackButton,
|
||||
BaseBox,
|
||||
DotButton,
|
||||
DrawerCustom,
|
||||
Grid,
|
||||
LoaderCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { IconPlus } from "@/components/_Icon";
|
||||
import { apiDonationGetNewsById } from "@/service/api-client/api-donation";
|
||||
import { formatChatTime } from "@/utils/formatChatTime";
|
||||
import {
|
||||
router,
|
||||
Stack,
|
||||
useFocusEffect,
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, 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);
|
||||
const [list, setList] = useState<any[] | null>(null);
|
||||
const [loadList, setLoadList] = useState<boolean>(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadList();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadList = async () => {
|
||||
try {
|
||||
setLoadList(true);
|
||||
const response = await apiDonationGetNewsById({
|
||||
id: id as string,
|
||||
category: "get-all",
|
||||
});
|
||||
|
||||
setList(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
setList([]);
|
||||
} finally {
|
||||
setLoadList(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Rekap Kabar",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
{loadList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(list) ? (
|
||||
<TextCustom align="center" color="gray">
|
||||
Tidak ada kabar
|
||||
</TextCustom>
|
||||
) : (
|
||||
list?.map((item: any, index: number) => (
|
||||
<BaseBox key={index} href={`/donation/[id]/(news)/${item.id}`}>
|
||||
<Grid>
|
||||
<Grid.Col span={8}>
|
||||
<TextCustom truncate bold>
|
||||
{item?.title || "-"}
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={4} style={{ alignItems: "flex-end" }}>
|
||||
<TextCustom size="small">
|
||||
{formatChatTime(item?.createdAt)}
|
||||
</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,19 +1,69 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
Grid,
|
||||
OS_Wrapper,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { apiDonationGetInvoiceById } from "@/service/api-client/api-donation";
|
||||
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>
|
||||
<OS_Wrapper>
|
||||
<StackCustom>
|
||||
<BaseBox>
|
||||
<StackCustom>
|
||||
@@ -55,29 +105,6 @@ export default function DonasiFailed() {
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
</OS_Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
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,14 +1,15 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
BoxButtonOnFooter,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
Grid,
|
||||
InformationBox,
|
||||
OS_Wrapper,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import CopyButton from "@/components/Button/CoyButton";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
@@ -35,7 +36,7 @@ export default function DonationInvoice() {
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [invoiceId])
|
||||
}, [invoiceId]),
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
@@ -100,7 +101,21 @@ export default function DonationInvoice() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
<OS_Wrapper
|
||||
footerComponent={
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
disabled={!image}
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
handlerUpdateInvoice();
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
}
|
||||
>
|
||||
<StackCustom>
|
||||
<InformationBox
|
||||
text={`Mohon transfer donasi anda ke rekening dibawah`}
|
||||
@@ -122,7 +137,7 @@ export default function DonationInvoice() {
|
||||
}}
|
||||
>
|
||||
<TextCustom size="xlarge" bold color="yellow">
|
||||
{data?.DonasiMaster_Bank?.norek}
|
||||
{data?.MasterBank?.norek}
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col
|
||||
@@ -131,7 +146,7 @@ export default function DonationInvoice() {
|
||||
alignItems: "flex-end",
|
||||
}}
|
||||
>
|
||||
<CopyButton textToCopy={data?.DonasiMaster_Bank?.norek} />
|
||||
<CopyButton textToCopy={data?.MasterBank?.norek} />
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
@@ -204,19 +219,9 @@ export default function DonationInvoice() {
|
||||
</ButtonCenteredOnly>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
|
||||
<ButtonCustom
|
||||
disabled={!image}
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
handlerUpdateInvoice();
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
</OS_Wrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { BaseBox, StackCustom, TextCustom, ViewWrapper } from "@/components";
|
||||
import { BaseBox, OS_Wrapper, StackCustom, TextCustom } from "@/components";
|
||||
import MoneyTransferAnimation from "@/components/_ShareComponent/MoneyTransferAnimation";
|
||||
import { View } from "react-native";
|
||||
|
||||
export default function DonationProcess() {
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
<OS_Wrapper>
|
||||
<BaseBox>
|
||||
<StackCustom>
|
||||
<TextCustom align="center" bold>
|
||||
@@ -35,7 +35,7 @@ export default function DonationProcess() {
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox> */}
|
||||
</ViewWrapper>
|
||||
</OS_Wrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,19 +1,69 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
Grid,
|
||||
OS_Wrapper,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { apiDonationGetInvoiceById } from "@/service/api-client/api-donation";
|
||||
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>
|
||||
<OS_Wrapper>
|
||||
<StackCustom>
|
||||
<BaseBox>
|
||||
<StackCustom>
|
||||
@@ -55,29 +105,8 @@ export default function DonationSuccess() {
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
</OS_Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
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")}`,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -3,28 +3,39 @@ import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCustom,
|
||||
Grid,
|
||||
OS_Wrapper,
|
||||
TextCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
|
||||
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() })
|
||||
JSON.stringify({ nominal: nominal.toString() }),
|
||||
);
|
||||
router.replace(`/donation/${id}/select-bank`);
|
||||
} catch (error) {
|
||||
@@ -53,7 +64,7 @@ export default function InvestmentInputDonation() {
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper footerComponent={bottomComponent}>
|
||||
<OS_Wrapper enableKeyboardHandling contentPaddingBottom={250} footerComponent={bottomComponent}>
|
||||
{listData.map((item, i) => (
|
||||
<BaseBox key={i} onPress={() => setNominal(item.value)}>
|
||||
<Grid>
|
||||
@@ -87,7 +98,7 @@ export default function InvestmentInputDonation() {
|
||||
Minimal donasi Rp. 10.000
|
||||
</TextCustom>
|
||||
</BaseBox>
|
||||
</ViewWrapper>
|
||||
</OS_Wrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
BaseBox,
|
||||
BoxButtonOnFooter,
|
||||
ButtonCustom,
|
||||
ViewWrapper,
|
||||
OS_Wrapper,
|
||||
} from "@/components";
|
||||
import { RadioCustom, RadioGroup } from "@/components/Radio/RadioCustom";
|
||||
import { LOCAL_STORAGE_KEY } from "@/constants/local-storage-key";
|
||||
@@ -91,7 +91,7 @@ export default function DonationSelectBank() {
|
||||
);
|
||||
};
|
||||
return (
|
||||
<ViewWrapper footerComponent={buttonSubmit()}>
|
||||
<OS_Wrapper footerComponent={buttonSubmit()}>
|
||||
<RadioGroup value={select} onChange={setSelect}>
|
||||
{_.isEmpty(listBank)
|
||||
? []
|
||||
@@ -101,6 +101,6 @@ export default function DonationSelectBank() {
|
||||
</BaseBox>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</ViewWrapper>
|
||||
</OS_Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,11 +4,13 @@ import {
|
||||
DotButton,
|
||||
DrawerCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
OS_Wrapper,
|
||||
Spacing,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IconEdit, IconNews } from "@/components/_Icon";
|
||||
import { IMenuDrawerItem } from "@/components/_Interface/types";
|
||||
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
|
||||
import Donation_ButtonStatusSection from "@/screens/Donation/ButtonStatusSection";
|
||||
@@ -16,6 +18,7 @@ import Donation_ComponentBoxDetailData from "@/screens/Donation/ComponentBoxDeta
|
||||
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,
|
||||
@@ -24,19 +27,20 @@ import {
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, 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 [data, setData] = useState<any>();
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [data, setData] = useState<any | null>(null);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id])
|
||||
}, [id]),
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
@@ -58,40 +62,101 @@ 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
|
||||
options={{
|
||||
title: `Detail ${_.startCase(status as string)}`,
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () =>
|
||||
status === "draft" ? (
|
||||
<DotButton onPress={() => setOpenDrawer(true)} />
|
||||
) : status === "publish" ? (
|
||||
<DotButton onPress={() => setOpenDrawerPublish(true)} />
|
||||
) : null,
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title={`Detail ${_.startCase(status as string)}`}
|
||||
left={<BackButton />}
|
||||
right={
|
||||
status === "draft" ? (
|
||||
<DotButton onPress={() => setOpenDrawer(true)} />
|
||||
) : status === "publish" ? (
|
||||
<DotButton onPress={() => setOpenDrawerPublish(true)} />
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
<Donation_ComponentBoxDetailData
|
||||
data={data}
|
||||
bottomSection={
|
||||
status === "publish" && (
|
||||
<Donation_ProgressSection id={id as string} />
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Donation_ComponentStoryFunrising
|
||||
id={id as string}
|
||||
dataStory={data?.CeritaDonasi}
|
||||
/>
|
||||
<Spacing />
|
||||
<Donation_ButtonStatusSection
|
||||
id={id as string}
|
||||
status={status as string}
|
||||
/>
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
<OS_Wrapper
|
||||
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 />
|
||||
</>
|
||||
)}
|
||||
</OS_Wrapper>
|
||||
|
||||
<DrawerCustom
|
||||
isVisible={openDrawer}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
DummyLandscapeImage,
|
||||
OS_Wrapper,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { apiDonationGetOne } from "@/service/api-client/api-donation";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
@@ -32,12 +32,12 @@ export default function DonationDetailStory() {
|
||||
}
|
||||
};
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<OS_Wrapper>
|
||||
<StackCustom>
|
||||
<TextCustom>{data?.pembukaan || "-"}</TextCustom>
|
||||
<DummyLandscapeImage imageId={data?.imageId} />
|
||||
<TextCustom>{data?.cerita || "-"}</TextCustom>
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
</OS_Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
ViewWrapper,
|
||||
OS_Wrapper,
|
||||
StackCustom,
|
||||
InformationBox,
|
||||
TextInputCustom,
|
||||
@@ -76,7 +76,7 @@ export default function DonationEditRekening() {
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<OS_Wrapper enableKeyboardHandling contentPaddingBottom={250}>
|
||||
<StackCustom gap={"xs"}>
|
||||
<InformationBox text="Pastikan Anda mengisi nama bank dan nomor rekening dengan benar. Informasi ini akan membantu admin memverifikasi dan memproses penggalangan dana Anda dengan cepat dan tepat setelah penggalangan dana dipublikasikan." />
|
||||
<TextInputCustom
|
||||
@@ -105,6 +105,6 @@ export default function DonationEditRekening() {
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
</OS_Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ import {
|
||||
ButtonCustom,
|
||||
InformationBox,
|
||||
LandscapeFrameUploaded,
|
||||
OS_Wrapper,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextAreaCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import API_IMAGE from "@/constants/api-storage";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
@@ -93,7 +93,7 @@ export default function DonationEditStory() {
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<OS_Wrapper enableKeyboardHandling contentPaddingBottom={250}>
|
||||
<StackCustom gap={"xs"}>
|
||||
<InformationBox text="Cerita Anda adalah kunci untuk menginspirasi kebaikan. Jelaskan dengan jujur dan jelas tujuan penggalangan dana ini agar calon donatur memahami dampak positif yang dapat mereka wujudkan melalui kontribusi mereka." />
|
||||
<TextAreaCustom
|
||||
@@ -146,6 +146,6 @@ export default function DonationEditStory() {
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
</OS_Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
InformationBox,
|
||||
LandscapeFrameUploaded,
|
||||
LoaderCustom,
|
||||
OS_Wrapper,
|
||||
SelectCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
|
||||
import API_IMAGE from "@/constants/api-storage";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
import {
|
||||
@@ -60,7 +62,7 @@ export default function DonationEdit() {
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
onLoadList();
|
||||
}, [id])
|
||||
}, [id]),
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
@@ -79,7 +81,6 @@ export default function DonationEdit() {
|
||||
imageId: response.data.imageId,
|
||||
});
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
@@ -182,10 +183,25 @@ export default function DonationEdit() {
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<OS_Wrapper
|
||||
enableKeyboardHandling
|
||||
contentPaddingBottom={250}
|
||||
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 ? (
|
||||
<LoaderCustom />
|
||||
<ListSkeletonComponent />
|
||||
) : (
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextInputCustom
|
||||
@@ -260,17 +276,9 @@ export default function DonationEdit() {
|
||||
/>
|
||||
|
||||
<Spacing />
|
||||
<ButtonCustom
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
handlerSubmitUpdate();
|
||||
}}
|
||||
>
|
||||
Update
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
)}
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
</OS_Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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} />;
|
||||
}
|
||||
|
||||
@@ -6,36 +6,37 @@ import {
|
||||
DotButton,
|
||||
DrawerCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
OS_Wrapper,
|
||||
StackCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IconNews } from "@/components/_Icon";
|
||||
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
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 { apiDonationGetOne } from "@/service/api-client/api-donation";
|
||||
import { countDownAndCondition } from "@/utils/countDownAndCondition";
|
||||
import {
|
||||
router,
|
||||
Stack,
|
||||
useFocusEffect,
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
export default function DonasiDetailBeranda() {
|
||||
const { user } = useAuth();
|
||||
const { id } = useLocalSearchParams();
|
||||
console.log("ID ", id);
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
|
||||
const [data, setData] = useState<any>();
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id])
|
||||
}, [id]),
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
@@ -45,21 +46,41 @@ export default function DonasiDetailBeranda() {
|
||||
category: "permanent",
|
||||
});
|
||||
|
||||
console.log("[RES GET ONE]", JSON.stringify(response.data, null, 2));
|
||||
|
||||
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
|
||||
disabled={value?.reminder || !data}
|
||||
onPress={() => router.navigate(`/donation/${id}/(transaction-flow)`)}
|
||||
>
|
||||
Donasi
|
||||
{!data ? "Loading..." : value?.reminder ? "Waktu berakhir" : "Donasi"}
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
</>
|
||||
@@ -69,27 +90,43 @@ export default function DonasiDetailBeranda() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: `Detail Donasi`,
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () =>
|
||||
user?.id === data?.Author?.id ? (
|
||||
<DotButton onPress={() => setOpenDrawer(true)} />
|
||||
) : null,
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Detail Donasi"
|
||||
left={<BackButton />}
|
||||
right={
|
||||
user?.id === data?.Author?.id ? (
|
||||
<DotButton onPress={() => setOpenDrawer(true)} />
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper footerComponent={buttonSection}>
|
||||
<StackCustom>
|
||||
<Donation_ComponentBoxDetailData
|
||||
data={data}
|
||||
bottomSection={<Donation_ProgressSection id={id as string} />}
|
||||
/>
|
||||
<Donation_ComponentInfoFundrising dataAuthor={data?.Author} />
|
||||
<Donation_ComponentStoryFunrising
|
||||
id={id as string}
|
||||
dataStory={data?.CeritaDonasi}
|
||||
/>
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
<OS_Wrapper 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>
|
||||
)}
|
||||
</OS_Wrapper>
|
||||
|
||||
<DrawerCustom
|
||||
isVisible={openDrawer}
|
||||
|
||||
@@ -5,9 +5,9 @@ import {
|
||||
ButtonCustom,
|
||||
Grid,
|
||||
LoaderCustom,
|
||||
OS_Wrapper,
|
||||
Spacing,
|
||||
TextCustom,
|
||||
ViewWrapper
|
||||
} from "@/components";
|
||||
import Donation_BoxPublish from "@/screens/Donation/BoxPublish";
|
||||
import { apiDonationFundrising } from "@/service/api-client/api-donation";
|
||||
@@ -44,7 +44,7 @@ export default function DonationInformationFunrising() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
<OS_Wrapper>
|
||||
<BaseBox>
|
||||
<Grid>
|
||||
<Grid.Col span={6} style={{ justifyContent: "center" }}>
|
||||
@@ -80,7 +80,7 @@ export default function DonationInformationFunrising() {
|
||||
<Donation_BoxPublish key={index} id={item?.id} data={item} />
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
</OS_Wrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,5 +1,6 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
InformationBox,
|
||||
@@ -8,8 +9,8 @@ import {
|
||||
StackCustom,
|
||||
TextAreaCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import OS_Wrapper from "@/components/_ShareComponent/OS_Wrapper";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import {
|
||||
@@ -103,7 +104,7 @@ export default function DonationCreateStory() {
|
||||
type: "success",
|
||||
text1: "Donasi berhasil disimpan",
|
||||
});
|
||||
router.replace("/donation/status");
|
||||
router.replace("/donation/status?status=review");
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
@@ -112,7 +113,24 @@ export default function DonationCreateStory() {
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<OS_Wrapper
|
||||
enableKeyboardHandling
|
||||
contentPaddingBottom={250}
|
||||
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
|
||||
@@ -166,18 +184,8 @@ export default function DonationCreateStory() {
|
||||
value={data.rekening}
|
||||
onChangeText={(value) => setData({ ...data, rekening: value })}
|
||||
/>
|
||||
|
||||
<Spacing />
|
||||
<ButtonCustom
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
handlerSubmit();
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
</OS_Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
InformationBox,
|
||||
LandscapeFrameUploaded,
|
||||
LoaderCustom,
|
||||
OS_Wrapper,
|
||||
SelectCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
import { apiDonationCreate } from "@/service/api-client/api-donation";
|
||||
@@ -43,7 +44,7 @@ export default function DonationCreate() {
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadList();
|
||||
}, [])
|
||||
}, []),
|
||||
);
|
||||
|
||||
const onLoadList = async () => {
|
||||
@@ -125,7 +126,25 @@ export default function DonationCreate() {
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<OS_Wrapper
|
||||
enableKeyboardHandling
|
||||
contentPaddingBottom={250}
|
||||
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." />
|
||||
|
||||
@@ -201,20 +220,8 @@ export default function DonationCreate() {
|
||||
onChange={(value: any) => setData({ ...data, durasiId: value })}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Spacing />
|
||||
<ButtonCustom
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
handlerSubmit();
|
||||
// router.push(`/donation/create-story?id=${"dasdsadsa"}`);
|
||||
}}
|
||||
>
|
||||
Selanjutnya
|
||||
</ButtonCustom>
|
||||
<Spacing />
|
||||
</StackCustom>
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
</OS_Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,40 +4,87 @@ import {
|
||||
IconHome,
|
||||
IconStatus,
|
||||
} from "@/components/_Icon";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import BackButtonFromNotification from "@/components/Button/BackButtonFromNotification";
|
||||
import { TabsStyles } from "@/styles/tabs-styles";
|
||||
import { Tabs } from "expo-router";
|
||||
import { router, Tabs, useLocalSearchParams } from "expo-router";
|
||||
import { View } from "react-native";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { Platform } from "react-native";
|
||||
import { OS_ANDROID_HEIGHT, OS_IOS_HEIGHT } from "@/constants/constans-value";
|
||||
|
||||
function EventTabsWrapper() {
|
||||
const insets = useSafeAreaInsets();
|
||||
const paddingBottom = Platform.OS === "android" ? insets.bottom : 0;
|
||||
const { from, category } = useLocalSearchParams<{
|
||||
from?: string;
|
||||
category?: string;
|
||||
}>();
|
||||
|
||||
export default function EventTabsLayout() {
|
||||
return (
|
||||
<Tabs screenOptions={TabsStyles}>
|
||||
<Tabs.Screen
|
||||
name="index"
|
||||
options={{
|
||||
title: "Beranda",
|
||||
tabBarIcon: ({ color }) => <IconHome color={color} />,
|
||||
<View style={{ flex: 1, backgroundColor: MainColor.darkblue }}>
|
||||
<Tabs
|
||||
screenOptions={{
|
||||
...TabsStyles,
|
||||
tabBarStyle: Platform.select({
|
||||
ios: {
|
||||
borderTopWidth: 0,
|
||||
paddingTop: 12,
|
||||
height: OS_IOS_HEIGHT,
|
||||
},
|
||||
android: {
|
||||
borderTopWidth: 0,
|
||||
paddingTop: 5,
|
||||
height: OS_ANDROID_HEIGHT + paddingBottom,
|
||||
},
|
||||
}),
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Event"
|
||||
left={
|
||||
<BackButtonFromNotification
|
||||
from={from || ""}
|
||||
category={category}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="status"
|
||||
options={{
|
||||
title: "Status",
|
||||
tabBarIcon: ({ color }) => <IconStatus color={color} />,
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="contribution"
|
||||
options={{
|
||||
title: "Kontribusi",
|
||||
tabBarIcon: ({ color }) => <IconContribution color={color} />,
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="history"
|
||||
options={{
|
||||
title: "Riwayat",
|
||||
tabBarIcon: ({ color }) => <IconHistory color={color} />,
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
>
|
||||
<Tabs.Screen
|
||||
name="index"
|
||||
options={{
|
||||
title: "Beranda",
|
||||
tabBarIcon: ({ color }) => <IconHome color={color} />,
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="status"
|
||||
options={{
|
||||
title: "Status",
|
||||
tabBarIcon: ({ color }) => <IconStatus color={color} />,
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="contribution"
|
||||
options={{
|
||||
title: "Kontribusi",
|
||||
tabBarIcon: ({ color }) => <IconContribution color={color} />,
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="history"
|
||||
options={{
|
||||
title: "Riwayat",
|
||||
tabBarIcon: ({ color }) => <IconHistory color={color} />,
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default function EventTabsLayout() {
|
||||
return <EventTabsWrapper />;
|
||||
}
|
||||
|
||||
@@ -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 />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,11 +5,12 @@ import {
|
||||
DrawerCustom,
|
||||
Grid,
|
||||
MenuDrawerDynamicGrid,
|
||||
OS_Wrapper,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IMenuDrawerItem } from "@/components/_Interface/types";
|
||||
import LeftButtonCustom from "@/components/Button/BackButton";
|
||||
import Event_ButtonStatusSection from "@/screens/Event/ButtonStatusSection";
|
||||
@@ -81,15 +82,20 @@ export default function EventDetailStatus() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: `Detail ${status === "publish" ? "" : status}`,
|
||||
headerLeft: () => <LeftButtonCustom />,
|
||||
headerRight: () =>
|
||||
status === "draft" ? (
|
||||
<DotButton onPress={() => setOpenDrawer(true)} />
|
||||
) : null,
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title={`Detail ${status === "publish" ? "" : status}`}
|
||||
left={<LeftButtonCustom />}
|
||||
right={
|
||||
status === "draft" ? (
|
||||
<DotButton onPress={() => setOpenDrawer(true)} />
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
<OS_Wrapper>
|
||||
<BaseBox>
|
||||
<StackCustom>
|
||||
<TextCustom bold align="center" size="xlarge">
|
||||
@@ -112,7 +118,7 @@ export default function EventDetailStatus() {
|
||||
status={status as string}
|
||||
/>
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
</OS_Wrapper>
|
||||
|
||||
<DrawerCustom
|
||||
isVisible={openDrawer}
|
||||
|
||||
561
app/(application)/(user)/event/[id]/confirmation.tsx
Normal file
@@ -0,0 +1,561 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
ButtonCustom,
|
||||
CenterCustom,
|
||||
LoaderCustom,
|
||||
OS_Wrapper,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { 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>
|
||||
<UserParticipan_And_DuringEvent
|
||||
id={data.id}
|
||||
userId={user?.id as string}
|
||||
data={data}
|
||||
/>
|
||||
);
|
||||
}
|
||||
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={{
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Konfirmasi Event"
|
||||
left={
|
||||
<Ionicons
|
||||
name="arrow-back"
|
||||
size={20}
|
||||
color={MainColor.yellow}
|
||||
onPress={() => router.navigate("/")}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<OS_Wrapper>{handlerReturn()}</OS_Wrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -4,9 +4,10 @@ import {
|
||||
DrawerCustom,
|
||||
LoaderCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
OS_Wrapper,
|
||||
Spacing,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IMenuDrawerItem } from "@/components/_Interface/types";
|
||||
import LeftButtonCustom from "@/components/Button/BackButton";
|
||||
import Event_BoxDetailPublishSection from "@/screens/Event/BoxDetailPublishSection";
|
||||
@@ -49,19 +50,23 @@ export default function EventDetailContribution() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: `Detail kontribusi`,
|
||||
headerLeft: () => <LeftButtonCustom />,
|
||||
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Detail kontribusi"
|
||||
left={<LeftButtonCustom />}
|
||||
right={<DotButton onPress={() => setOpenDrawer(true)} />}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
<OS_Wrapper>
|
||||
{isLoadData ? (
|
||||
<LoaderCustom />
|
||||
) : (
|
||||
<Event_BoxDetailPublishSection data={data} />
|
||||
)}
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
</OS_Wrapper>
|
||||
<DrawerCustom
|
||||
isVisible={openDrawer}
|
||||
closeDrawer={() => setOpenDrawer(false)}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCustom,
|
||||
LoaderCustom,
|
||||
OS_Wrapper,
|
||||
SelectCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextAreaCustom,
|
||||
TextCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
|
||||
import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom";
|
||||
import {
|
||||
apiEventGetOne,
|
||||
@@ -18,7 +20,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 +50,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 +102,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 +157,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 +185,21 @@ export default function EventEdit() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
<OS_Wrapper
|
||||
enableKeyboardHandling
|
||||
contentPaddingBottom={250}
|
||||
footerComponent={
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
isLoading={isLoading}
|
||||
title="Update"
|
||||
onPress={handlerSubmit}
|
||||
/>
|
||||
</BoxButtonOnFooter>
|
||||
}
|
||||
>
|
||||
{isLoadData ? (
|
||||
<LoaderCustom />
|
||||
<ListSkeletonComponent />
|
||||
) : (
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextInputCustom
|
||||
@@ -185,31 +209,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 +245,7 @@ export default function EventEdit() {
|
||||
{
|
||||
validateDateRange(
|
||||
selectedDate as any,
|
||||
selectedEndDate as any
|
||||
selectedEndDate as any,
|
||||
).error
|
||||
}
|
||||
</TextCustom>
|
||||
@@ -241,32 +254,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>
|
||||
</OS_Wrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,9 +3,10 @@ import {
|
||||
DotButton,
|
||||
DrawerCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
ViewWrapper,
|
||||
OS_Wrapper,
|
||||
Spacing,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IMenuDrawerItem } from "@/components/_Interface/types";
|
||||
import LeftButtonCustom from "@/components/Button/BackButton";
|
||||
import Event_BoxDetailPublishSection from "@/screens/Event/BoxDetailPublishSection";
|
||||
@@ -44,19 +45,23 @@ export default function EventDetailHistory() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: `Detail riwayat`,
|
||||
headerLeft: () => <LeftButtonCustom />,
|
||||
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Detail riwayat"
|
||||
left={<LeftButtonCustom />}
|
||||
right={<DotButton onPress={() => setOpenDrawer(true)} />}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
<OS_Wrapper>
|
||||
<Event_BoxDetailPublishSection data={data} />
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
</OS_Wrapper>
|
||||
<DrawerCustom
|
||||
isVisible={openDrawer}
|
||||
closeDrawer={() => setOpenDrawer(false)}
|
||||
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,16 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AlertDefaultSystem,
|
||||
BackButton,
|
||||
ButtonCustom,
|
||||
DotButton,
|
||||
DrawerCustom,
|
||||
LoaderCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
ViewWrapper,
|
||||
OS_Wrapper,
|
||||
} from "@/components";
|
||||
import AppHeader from "@/components/_ShareComponent/AppHeader";
|
||||
import { IMenuDrawerItem } from "@/components/_Interface/types";
|
||||
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
|
||||
import LeftButtonCustom from "@/components/Button/BackButton";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import Event_BoxDetailPublishSection from "@/screens/Event/BoxDetailPublishSection";
|
||||
@@ -18,23 +20,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 +111,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 (
|
||||
<OS_Wrapper>
|
||||
<CustomSkeleton />
|
||||
</OS_Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
const FooterButton = () => {
|
||||
return (
|
||||
<>
|
||||
<ButtonCustom
|
||||
@@ -135,21 +157,25 @@ export default function EventDetailPublish() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: `Event publish`,
|
||||
headerLeft: () => <LeftButtonCustom />,
|
||||
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
|
||||
header: () => (
|
||||
<AppHeader
|
||||
title="Event Publish"
|
||||
left={<BackButton onPress={() => router.back()} />}
|
||||
right={<DotButton onPress={() => setOpenDrawer(true)} />}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
<OS_Wrapper>
|
||||
{isLoadingData ? (
|
||||
<LoaderCustom />
|
||||
<CustomSkeleton height={400} />
|
||||
) : (
|
||||
<Event_BoxDetailPublishSection
|
||||
data={data}
|
||||
footerButton={footerButton()}
|
||||
footerButton={FooterButton()}
|
||||
/>
|
||||
)}
|
||||
</ViewWrapper>
|
||||
</OS_Wrapper>
|
||||
|
||||
<DrawerCustom
|
||||
isVisible={openDrawer}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCustom,
|
||||
OS_Wrapper,
|
||||
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,11 @@ export default function EventCreate() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
<OS_Wrapper
|
||||
enableKeyboardHandling
|
||||
contentPaddingBottom={250}
|
||||
footerComponent={<BoxButtonOnFooter>{buttonSubmit}</BoxButtonOnFooter>}
|
||||
>
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextInputCustom
|
||||
placeholder="Masukkan nama event"
|
||||
@@ -137,24 +125,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 +163,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>
|
||||
</OS_Wrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||