- Kelompokkan docs/ ke subfolder: architecture/, testing/, notes/, ai/, tasks/ - Pindahkan tasks/ (root) ke docs/tasks/ - Tambah docs/README.md sebagai index navigasi - Tambah CLAUDE.md (project instructions) - Hapus .qwen/settings.json Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7.1 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project
HIPMI Badung Connect — Cross-platform mobile app (iOS, Android, Web) for HIPMI (Himpunan Pengusaha Muda Indonesia) chapter Badung members.
- Framework: Expo v54 + React Native v0.81.5, TypeScript strict
- Routing: Expo Router v6 (file-based)
- Package manager: Bun
Commands
bun install # install dependencies
bunx expo start # dev server
bunx expo start --ios # iOS simulator
bunx expo start --android # Android emulator
bunx expo start -c # clear cache
bun run lint # expo lint
# Build (EAS)
eas build --profile production
eas build --profile preview
eas build --profile development
# Troubleshooting
rm -rf node_modules && bun install
bunx expo start --clear
Architecture
Route → Screen separation (strict)
app/ contains route files only (max 5 lines). All business logic lives in screens/.
// app/(application)/admin/feature/screen-name.tsx
import { Admin_ScreenXXX } from "@/screens/Admin/Feature/ScreenXXX";
export default function AdminScreenXXX() { return <Admin_ScreenXXX />; }
// screens/Admin/Feature/ScreenXXX.tsx — ALL logic here
export function Admin_ScreenXXX() {
// hooks, state, handlers, render
return <OS_Wrapper ... />;
}
Naming conventions
| Item | Convention | Example |
|---|---|---|
| Admin screen | Admin_ScreenXXX |
Admin_ScreenDonationList |
| User screen | PascalCase | ScreenDonationDetail |
| Admin card | Admin_BoxXXX |
Admin_BoxDonation |
| Path alias | @/* |
@/components/... |
Key Components
OS_Wrapper — always use as root wrapper
Automatically selects iOS/Android layout. Two modes:
// List screen
<OS_Wrapper
listData={pagination.listData}
renderItem={renderItem}
headerComponent={headerComponent}
ListEmptyComponent={ListEmptyComponent}
ListFooterComponent={ListFooterComponent}
onEndReached={pagination.loadMore}
refreshControl={<RefreshControl refreshing={pagination.refreshing} onRefresh={pagination.onRefresh} />}
/>
// Form screen (with TextInput / TextArea)
<OS_Wrapper enableKeyboardHandling contentPaddingBottom={250}>
<FormContent />
</OS_Wrapper>
contentPaddingBottom={100}— default for list screenscontentPaddingBottom={250}— only for screens with TextInput/TextAreaenableKeyboardHandling— Android keyboard auto-scroll (ignored on iOS)
AdminBasicBox + GridSpan_4_8
<AdminBasicBox onPress={() => router.push(`/path/${item.id}`)} style={{ marginHorizontal: 10, marginVertical: 5 }}>
<StackCustom gap={0}>
<GridSpan_4_8 label="Nama" value={<TextCustom>{item.name}</TextCustom>} />
</StackCustom>
</AdminBasicBox>
Pagination Pattern
import { usePagination } from "@/hooks/use-pagination";
import { createPaginationComponents } from "@/helpers/paginationHelpers";
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
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,
});
usePagination returns: listData, loading, refreshing, hasMore, page, onRefresh, loadMore, reset, setListData, isInitialLoad
API Service Pattern
Always use apiConfig (not axios directly) — auth token injected automatically via request interceptor from AsyncStorage.getItem("authToken").
// 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;
}
}
- All list endpoints require
pageparam (default"1"as string) - Response shape:
{ success: boolean, data: T[], message?: string } - Admin APIs:
service/api-admin/ - Client APIs:
service/api-client/
Authentication
// context/AuthContext.tsx — access via useAuth()
const { user, token, isAdmin, isUserActive, loginWithNomor, validateOtp, logout } = useAuth();
// Role check
const isAdmin = user?.masterUserRoleId !== "1"; // "1" = regular user
// Post-login routing
// active user → /(application)/(user)/home
// inactive user → /(application)/(user)/waiting-room
AsyncStorage keys: "authToken", "userData"
File Upload
import { uploadFileService, deleteFileService } from "@/service/upload-service";
import { API_IMAGE } from "@/constants/api-storage";
const result = await uploadFileService({ dirId: "folder-name", imageUri: localUri });
// result.data.id → save this ID to backend
await deleteFileService({ id: fileId });
const imageUrl = API_IMAGE.GET({ fileId: "xxx", size: 200 }); // wibu-storage CDN
Constants (constants/constans-value.ts)
PADDING_INLINE = 16 // use selectively, not on every screen by default
PADDING_EXTRA_SMALL = 10
PADDING_SMALL = 12
PADDING_MEDIUM = 16
PADDING_LARGE = 20
OS_ANDROID_HEIGHT = 60 / OS_IOS_HEIGHT = 80
OS_ANDROID_PADDING_TOP = 6 / OS_IOS_PADDING_TOP = 12
TEXT_SIZE_SMALL = 12 / MEDIUM = 14 / LARGE = 16 / XLARGE = 18
PAGINATION_DEFAULT_TAKE = 10
DRAWER_HEIGHT = 500
RADIUS_BUTTON = 50
// constants/color-palet.ts
MainColor.darkblue = "#001D3D" // user app primary
AdminColor.bgAdmin = "#182c47" // admin panel background
Known Issues
iOS Maplibre crash — "Attempt to recycle a mounted view"
Never conditionally render PointAnnotation. Control visibility via opacity instead:
// ✅ correct
<PointAnnotation coordinate={coordinate}>
<View style={{ opacity: selectedLocation ? 1 : 0 }}>
<SelectedLocationMarker />
</View>
</PointAnnotation>
// ❌ crashes iOS
{selectedLocation && <PointAnnotation ... />}
Known Typos — Do NOT fix unless asked
| Path | Typo |
|---|---|
constants/constans-value.ts |
constans |
constants/base-url-api-strorage.ts |
strorage |
screens/Invesment/ |
Invesment |
screens/Portofolio/ |
Portofolio |
screens/UserSeach/ |
UserSeach |
Environment Variables (.env)
API_BASE_URL=https://your-api-url.com
BASE_URL=https://your-app-url.com
DEEP_LINK_URL=hipmimobile://
Accessed via: Constants.expoConfig?.extra?.API_BASE_URL
New Screen Checklist
service/api-admin/api-xxx.ts— API function withpageparamscreens/Admin/Feature/ScreenXXX.tsx— screen withusePagination+OS_Wrapperscreens/Admin/Feature/BoxXXX.tsx— card component (optional)app/(application)/admin/feature/index.tsx— thin route file