Compare commits

..

54 Commits

Author SHA1 Message Date
b8b1efc71e Deskripsi:
- New component: Datetime custom, datetime android, datetime ios
- Fix: event create input datetime

# No Issue
2025-07-18 11:48:24 +08:00
eaf0ebfb0a feature
Deskripsi:
try component: Date input
2025-07-17 17:21:31 +08:00
e68d366d49 Deskripsi:
Fix: ViewWrapper tinggi footer
Style: Constan value untuk tinggi footer per OS

# No Issue
2025-07-15 15:30:11 +08:00
9999f78ed4 deskripsi:
Feat: event create
Fix: tabs event

# No Issue
2025-07-15 15:21:41 +08:00
2be5afe5ca Fix: register text input 2025-07-15 14:15:33 +08:00
24913a9f97 deskripsi:
Fix: portofolio: alert hapus porto
Fix: profile create hapus text input alamat & edit ganti select jenis kelamin
Fix: menu forum
Fix:  button porto

# No Issue
2025-07-15 14:13:08 +08:00
3376336c55 Deskripsi:
- new comp : Radio
- fix comp : Text area > placeholder diatas
- fix page : report forum sudah pakai radio

# No Issue
2025-07-15 12:01:28 +08:00
a0dad5618a feature
Desskripsi:
- new page: other-report-commentar, other-report-posting, report-commentar, report-posting

# No Issue
2025-07-15 11:24:57 +08:00
cce27c26f6 feature & fix
deskripsi:
feature :
- Forum : beranda, edit, detail, create
# No Issue
2025-07-14 17:37:43 +08:00
3211404397 fix
deskripsi:
- forum & forumku
2025-07-14 16:30:32 +08:00
fbde2fd031 feature & fix
deskripsi:
feature :
- forumku

fix :
- forum index
- Back button : bisa custom icon
- Button custom : di tambah href
# No Issue
2025-07-14 14:12:01 +08:00
ac9dae7c5b feature & fix
deskripsi:
feature:
- floating button
- Forum create

fix:
- Base box
- Avatar : penambahan onPres & href
- Text custom : penambahan warna props green
- Text Area : penambhaan hight
- ViewWrapper : penambahan props floating
# No Issue "
2025-07-14 11:56:44 +08:00
5183769a7c feature & fix
deskripsi:
- new component : Scroll
- fix : notifikasi, use search, portofolio item drawer
- new constats : padding value
- fix component :
Text custom : tambah warna gray di props
Text Input: tambah props container Style

# No Issue
2025-07-11 17:34:04 +08:00
3301cf3d7a fix
deskripsi:
- fix page: user search
- fix global-style
- fix component Text Input dan ViewWrapper ( sekarang bisa menambahkan header )
# No Issue
2025-07-10 17:37:54 +08:00
6913e9e4b5 fix
deskripsi:
- fix page maps
- fix ViewWrapper : props styles di tambah
# No Issue
2025-07-10 16:57:49 +08:00
c5798b3127 feature & fix
deskripsi:
- new component Clickable
- new folder (image) untuk take picture dan imaga preview
- fix klik gambar
# No Issue
2025-07-10 16:47:02 +08:00
2c94638e27 fix
deskripsi:
- fix component : Menu drawer
- fix feature : list menui drawer pada portofolio & profile
# No Issue
2025-07-10 15:21:40 +08:00
b8ed577fea feature & fix
deskripsi:
- new component : Center
- fix component : Button > disable props
- feture : fix portofolio > edit, edit logo, edit sosmed
- fix feature : maps > edit map, custom-pin
- fix featue : profile > update photo
# No Issue
2025-07-10 15:03:52 +08:00
e68a18bb89 feature
deskripsi:
- komponen Map
# No Issue
2025-07-10 14:03:01 +08:00
862af44c03 feature
deskripsi:
- portofolio: detail bisnis, maps, media social
- new component divide
# No Issue
2025-07-10 11:58:23 +08:00
71ea0ca5f2 feature & fix
deskripsi:
- perubahan komponen drawer
- penambahan list page drawer portofolio
- new file portofolio: edit, edit logo, edit sosmed
- new file maps: edit, custom-pin
#No Issue
2025-07-10 10:27:30 +08:00
ee7efaef6a fix & featur
deskripsi:
- fix component stack dan drawer
- new page list path profile
2025-07-09 17:22:43 +08:00
bfb029058c feature
deskripsi:
- new page daftar portofolio
2025-07-09 15:29:04 +08:00
b7e774a556 fix
deskripsi:
- perubahan pada component: ButtonCustom, TextArea, TextInput
- fix style global
- tambhan color pada palet
2025-07-09 15:03:41 +08:00
2901d19db0 fix
deksripsi:
- fix Text input exported
2025-07-09 11:52:38 +08:00
5c4dadbe7c fix
dekripsi:
- fix styles Select
2025-07-09 11:40:22 +08:00
6ac122c631 styles
deskripsi:
- fix styles Text input & Text area
2025-07-09 10:58:47 +08:00
16559b94fe feature & fix
deskripsi:
- fix Text input
- feature Box footer & button center
2025-07-09 10:19:02 +08:00
0698e14d36 fix route
deskripsi:
- perbaiki route tujuan pada create profile
2025-07-08 14:18:50 +08:00
3d9672154c fix folder
deskripsi:
- fix folder profile ke (user)
2025-07-08 12:22:15 +08:00
55b4b1fa8d fix folder
deskripsi:
- pindah folder portofolio ke (user)
2025-07-08 12:11:35 +08:00
b80968999e fix
-deskripsi:
- fix folder: map, marketplace, forum ke (user)
2025-07-08 12:04:51 +08:00
6bac89c536 fix folder
deskripsi:
- pindah folder event ke (user)
2025-07-08 11:58:32 +08:00
b9af7e0ca7 fix folder
deksripsi:
- pindah folder user search & notifikasi ke (user)
2025-07-08 11:47:32 +08:00
8abf23fd13 fix
deskripsi:
- fix ViewWrapper : flexibel terhadap keypad
- saat keypad keluar tidak ada lagi space di atas navighation bar

No Issue
2025-07-08 10:46:45 +08:00
1a16b16f47 feature & fix
deksripsi:
feature:
- Information Box
- Create profile
fix:
component: Alet, Avatar, Select
# No Issue
2025-07-04 17:42:22 +08:00
0b1fd05eec feature
deskripsi:
- folder take-picture
2025-07-04 16:32:44 +08:00
b54693caa7 feature
deskripsi:
- new component camera
2025-07-04 15:36:09 +08:00
17e6208aae feature
deskripsi:
- new component select
- fix styles login dan verifikasi
2025-07-03 17:43:52 +08:00
f5cf9e1549 fix component
- buttom radius valuenya constans
- perbaikan bottom pada auth file
2025-07-03 17:05:53 +08:00
7b58e3315f feature
deskripsi:
- tampilan edit profile
- resourcing stack
- fix text input & buttom
2025-07-03 16:35:39 +08:00
101c9053d8 feature
deskripsi:
- new component stack
2025-07-03 14:54:05 +08:00
4a92385d6d fix nama komponen profile 2025-07-03 14:24:10 +08:00
7e39133c2f feature components
deskripsi:
- BaseBox, TextCustom
- Perbaikan home & profile
2025-07-03 11:29:51 +08:00
e2744f0344 fix styles dan feature component text 2025-07-02 14:55:12 +08:00
9667065bb3 fix folder component 2025-07-02 12:20:42 +08:00
23ae416f42 fix component drawer 2025-07-02 11:56:04 +08:00
8fb37db0db feature
deksripsi:
- merapikan folder profile
- Issu: Drawer
2025-07-02 11:43:22 +08:00
258e20751e feature profile
deskripsi:
- drawer & alert
- screen baru: edit profile, update photo, update background, create portofolio
2025-07-01 17:47:51 +08:00
564ea68d29 feature
deksripsi:
- resourcing drawer dan alert
2025-07-01 11:58:54 +08:00
4701fce07c feature profile
deskripsi
- resaourcing drawer
2025-06-30 17:44:00 +08:00
d58304a146 feature event
deskripsi:
- resourcing component event
2025-06-30 16:41:17 +08:00
5577ef5d1e fix custom tabs
deskripsi:
- penambahan style untuk tabs
2025-06-30 12:09:52 +08:00
c1bee77629 new featur
deskripsi:
- search user
- notifications
2025-06-30 10:39:04 +08:00
134 changed files with 9096 additions and 1289 deletions

View File

@@ -1,6 +1,6 @@
{
"expo": {
"name": "hipmi-mobile",
"name": "HIPMI BADUNG",
"slug": "hipmi-mobile",
"version": "1.0.0",
"orientation": "portrait",
@@ -38,7 +38,16 @@
"resizeMode": "contain",
"backgroundColor": "#ffffff"
}
]
],
[
"expo-camera",
{
"cameraPermission": "Allow $(PRODUCT_NAME) to access your camera",
"microphonePermission": "Allow $(PRODUCT_NAME) to access your microphone",
"recordAudioAndroid": true
}
],
"expo-font"
],
"experiments": {
"typedRoutes": true

View File

@@ -0,0 +1,9 @@
import { LandscapeFrameUploaded, ViewWrapper } from "@/components";
export default function PreviewImage() {
return (
<ViewWrapper>
<LandscapeFrameUploaded />
</ViewWrapper>
);
}

View File

@@ -0,0 +1,171 @@
import {
ButtonCustom,
Spacing,
StackCustom,
ViewWrapper
} from "@/components";
import AntDesign from "@expo/vector-icons/AntDesign";
import FontAwesome6 from "@expo/vector-icons/FontAwesome6";
import { CameraType, CameraView, useCameraPermissions } from "expo-camera";
import { Image } from "expo-image";
import * as ImagePicker from "expo-image-picker";
import { router, useLocalSearchParams } from "expo-router";
import { useRef, useState } from "react";
import { Pressable, StyleSheet, Text, View } from "react-native";
export default function TakePicture() {
const { id } = useLocalSearchParams();
// console.log("Take Picture ID >>", id);
const [permission, requestPermission] = useCameraPermissions();
const ref = useRef<CameraView>(null);
const [uri, setUri] = useState<string | null>(null);
const [facing, setFacing] = useState<CameraType>("back");
if (!permission?.granted) {
return (
<View style={styles.container}>
<Text style={{ textAlign: "center" }}>
We need your permission to use the camera
</Text>
<Pressable onPress={requestPermission}>
<Text style={{ color: "blue", marginTop: 10 }}>Grant permission</Text>
</Pressable>
</View>
);
}
const takePicture = async () => {
const photo = await ref.current?.takePictureAsync();
setUri(photo?.uri || null);
};
const pickImage = async () => {
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [1, 1],
quality: 1,
});
if (!result.canceled) {
setUri(result.assets[0].uri);
}
};
const toggleFacing = () => {
setFacing((prev) => (prev === "back" ? "front" : "back"));
};
const renderPicture = () => {
return (
<View>
<Image
source={uri ? uri : null}
contentFit="contain"
style={{ width: 340, aspectRatio: 1 }}
/>
<Spacing />
<StackCustom >
<ButtonCustom onPress={() => setUri(null)} title="Foto ulang" />
<ButtonCustom
onPress={() => {
console.log("Upload picture >>", id);
router.back();
}}
title="Upload Foto"
/>
</StackCustom>
</View>
);
};
const renderCameraUI = () => {
return (
<View style={styles.cameraOverlay}>
<View style={styles.shutterContainer}>
<Pressable onPress={toggleFacing}>
<FontAwesome6 name="rotate-left" size={32} color="white" />
</Pressable>
<Pressable onPress={takePicture}>
{({ pressed }) => (
<View
style={[
styles.shutterBtn,
{
opacity: pressed ? 0.5 : 1,
},
]}
>
<View style={styles.shutterBtnInner} />
</View>
)}
</Pressable>
<Pressable onPress={pickImage}>
<AntDesign name="folderopen" size={32} color="white" />
</Pressable>
</View>
</View>
);
};
return (
<>
{uri ? (
<ViewWrapper>
<View style={styles.container}>{renderPicture()}</View>
</ViewWrapper>
) : (
<>
<CameraView
style={styles.camera}
ref={ref}
facing={facing}
responsiveOrientationWhenOrientationLocked
/>
{renderCameraUI()}
</>
)}
</>
);
}
const styles = StyleSheet.create({
container: {
justifyContent: "center",
alignItems: "center",
},
camera: {
flex: 1,
width: "100%",
},
cameraOverlay: {
...StyleSheet.absoluteFillObject,
justifyContent: "flex-end",
padding: 44,
},
shutterContainer: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
},
shutterBtn: {
backgroundColor: "transparent",
borderWidth: 5,
borderColor: "white",
width: 85,
height: 85,
borderRadius: 45,
alignItems: "center",
justifyContent: "center",
},
shutterBtnInner: {
width: 70,
height: 70,
borderRadius: 50,
backgroundColor: "white",
},
});

View File

@@ -0,0 +1,194 @@
import { BackButton } from "@/components";
import LeftButtonCustom from "@/components/Button/BackButton";
import { MainColor } from "@/constants/color-palet";
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.Screen
name="home"
options={{
title: "HIPMI",
headerLeft: () => (
<Ionicons
name="search"
size={20}
color={MainColor.yellow}
onPress={() => router.push("/user-search")}
/>
),
headerRight: () => (
<Ionicons
name="notifications"
size={20}
color={MainColor.yellow}
onPress={() => router.push("/notifications")}
/>
),
}}
/>
{/* ========== Profile Section ========= */}
<Stack.Screen
name="profile"
options={{
headerShown: false,
}}
/>
{/* ========== Portofolio Section ========= */}
<Stack.Screen
name="portofolio"
options={{
headerShown: false,
}}
/>
{/* ========== User Search Section ========= */}
<Stack.Screen
name="user-search/index"
options={{
title: "Pencarian Pengguna",
headerLeft: () => <BackButton />,
}}
/>
{/* ========== Notification Section ========= */}
<Stack.Screen
name="notifications/index"
options={{
title: "Notifikasi",
headerLeft: () => <BackButton />,
}}
/>
{/* ========== Event Section ========= */}
<Stack.Screen
name="event/(tabs)"
options={{
title: "Event",
headerLeft: () => (
<LeftButtonCustom path="/(application)/(user)/home" />
),
}}
/>
<Stack.Screen
name="event/create"
options={{
title: "Tambah Event",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="event/detail/[id]"
options={{
title: "Event Detail",
headerLeft: () => <LeftButtonCustom />,
}}
/>
{/* ========== Forum Section ========= */}
<Stack.Screen
name="forum/create"
options={{
title: "Tambah Diskusi",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="forum/[id]/edit"
options={{
title: "Edit Diskusi",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="forum/[id]/forumku"
options={{
title: "Forumku",
headerLeft: () => <BackButton icon={'close'} />,
}}
/>
<Stack.Screen
name="forum/[id]/index"
options={{
title: "Detail",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="forum/[id]/report-commentar"
options={{
title: "Laporkan Komentar",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="forum/[id]/other-report-commentar"
options={{
title: "Laporkan Komentar",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="forum/[id]/report-posting"
options={{
title: "Laporkan Diskusi",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="forum/[id]/other-report-posting"
options={{
title: "Laporkan Diskusi",
headerLeft: () => <BackButton />,
}}
/>
{/* ========== Maps Section ========= */}
<Stack.Screen
name="maps/index"
options={{
title: "Maps",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="maps/create"
options={{
title: "Tambah Maps",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="maps/[id]/edit"
options={{
title: "Edit Maps",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="maps/[id]/custom-pin"
options={{
title: "Custom Pin Maps",
headerLeft: () => <BackButton />,
}}
/>
{/* ========== Marketplace Section ========= */}
<Stack.Screen
name="marketplace/index"
options={{
title: "Market Place",
headerLeft: () => <BackButton />,
}}
/>
</Stack>
</>
);
}

View File

@@ -0,0 +1,81 @@
import { AccentColor, MainColor } from "@/constants/color-palet";
import { OS_IOS_HEIGHT, OS_ANDROID_HEIGHT } from "@/constants/constans-value";
import { FontAwesome5, Ionicons } from "@expo/vector-icons";
import { Tabs } from "expo-router";
import { Platform, View } from "react-native";
export default function EventLayout() {
return (
<Tabs
screenOptions={{
headerShown: false,
tabBarActiveTintColor: MainColor.yellow,
tabBarInactiveTintColor: MainColor.white_gray,
tabBarBackground: CustomTabBarBackground,
tabBarStyle: Platform.select({
ios: {
borderTopWidth: 0,
paddingTop: 5,
height: OS_IOS_HEIGHT,
},
android: {
borderTopWidth: 0,
paddingTop: 5,
height: OS_ANDROID_HEIGHT,
},
default: {},
}),
}}
>
<Tabs.Screen
name="index"
options={{
title: "Home",
tabBarIcon: ({ color }) => (
<Ionicons size={20} name="home" color={color} />
),
}}
/>
<Tabs.Screen
name="status"
options={{
title: "Status",
tabBarIcon: ({ color }) => (
<Ionicons size={20} name="list" color={color} />
),
}}
/>
<Tabs.Screen
name="kontribusi"
options={{
title: "Kontribusi",
tabBarIcon: ({ color }) => (
<Ionicons size={20} name="extension-puzzle" color={color} />
),
}}
/>
<Tabs.Screen
name="riwayat"
options={{
title: "Riwayat",
tabBarIcon: ({ color }) => (
<FontAwesome5 size={20} name="history" color={color} />
),
}}
/>
</Tabs>
);
}
function CustomTabBarBackground() {
return (
<View
style={{
flex: 1,
backgroundColor: MainColor.darkblue,
borderTopWidth: 1,
borderTopColor: AccentColor.blue,
}}
/>
);
}

View File

@@ -0,0 +1,30 @@
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import FloatingButton from "@/components/Button/FloatingButton";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { GStyles } from "@/styles/global-styles";
import { router } from "expo-router";
import { Text, TouchableHighlight, View } from "react-native";
export default function Event() {
return (
<ViewWrapper
floatingButton={
<FloatingButton onPress={() => router.push("/event/create")} />
}
>
<TouchableHighlight onPress={() => router.push("/event/detail/1")}>
<View
style={{
padding: 20,
backgroundColor: MainColor.darkblue,
borderRadius: 10,
borderColor: AccentColor.blue,
borderWidth: 1,
}}
>
<Text style={GStyles.textLabel}>Event</Text>
</View>
</TouchableHighlight>
</ViewWrapper>
);
}

View File

@@ -0,0 +1,11 @@
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { GStyles } from "@/styles/global-styles";
import { Text } from "react-native";
export default function Kontribusi() {
return (
<ViewWrapper>
<Text style={GStyles.textLabel}>Kontribusi</Text>
</ViewWrapper>
);
}

View File

@@ -0,0 +1,11 @@
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { GStyles } from "@/styles/global-styles";
import { Text } from "react-native";
export default function Riwayat() {
return (
<ViewWrapper>
<Text style={GStyles.textLabel}>Riwayat</Text>
</ViewWrapper>
);
}

View File

@@ -0,0 +1,11 @@
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { GStyles } from "@/styles/global-styles";
import { Text } from "react-native";
export default function Status() {
return (
<ViewWrapper>
<Text style={GStyles.textLabel}>Status</Text>
</ViewWrapper>
);
}

View File

@@ -0,0 +1,100 @@
import {
BoxButtonOnFooter,
ButtonCustom,
SelectCustom,
StackCustom,
TextAreaCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom";
import { masterTypeEvent } from "@/lib/dummy-data/event/master-type-event";
import { DateTimePickerEvent } from "@react-native-community/datetimepicker";
import React, { useState } from "react";
import { Platform } from "react-native";
export default function EventCreate() {
const [selectedDate, setSelectedDate] = useState<
Date | DateTimePickerEvent | null
>(null);
const [selectedEndDate, setSelectedEndDate] = useState<
Date | DateTimePickerEvent | null
>(null);
const handlerSubmit = () => {
if (selectedDate) {
console.log("Tanggal yang dipilih:", selectedDate);
console.log(`ISO Format ${Platform.OS}:`, selectedDate.toString());
// Kirim ke API atau proses lanjutan
} else {
console.warn("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.warn("Tanggal belum dipilih");
}
};
const buttonSubmit = (
<BoxButtonOnFooter>
<ButtonCustom title="Simpan" onPress={handlerSubmit} />
</BoxButtonOnFooter>
);
return (
<>
<ViewWrapper footerComponent={buttonSubmit}>
<StackCustom gap={"xs"}>
<TextInputCustom
placeholder="Masukkan nama event"
label="Nama Event"
required
/>
<SelectCustom
label="Tipe Event"
placeholder="Pilih tipe event"
data={masterTypeEvent}
onChange={(value) => console.log(value)}
/>
<TextInputCustom
label="Lokasi"
placeholder="Masukkan lokasi event"
required
/>
<DateTimePickerCustom
label="Tanggal & Waktu Mulai"
required
onChange={(date: Date) => {
setSelectedDate(date as any);
}}
value={selectedDate as any}
minimumDate={new Date(Date.now())}
/>
<DateTimePickerCustom
label="Tanggal & Waktu Berakhir"
required
onChange={(date: Date) => {
setSelectedEndDate(date as any);
}}
value={selectedEndDate as any}
/>
<TextAreaCustom
label="Deskripsi"
placeholder="Masukkan deskripsi event"
required
showCount
maxLength={100}
/>
</StackCustom>
</ViewWrapper>
</>
);
}

View File

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

View File

@@ -0,0 +1,37 @@
import {
BoxButtonOnFooter,
ButtonCustom,
TextAreaCustom,
ViewWrapper,
} from "@/components";
import { router } from "expo-router";
import { useState } from "react";
export default function ForumEdit() {
const [text, setText] = useState("");
const buttonFooter = (
<BoxButtonOnFooter>
<ButtonCustom
onPress={() => {
console.log("Posting", text);
router.back();
}}
>
Update
</ButtonCustom>
</BoxButtonOnFooter>
);
return (
<ViewWrapper footerComponent={buttonFooter}>
<TextAreaCustom
placeholder="Ketik diskusi anda..."
maxLength={1000}
showCount
value={text}
onChangeText={setText}
/>
</ViewWrapper>
);
}

View File

@@ -0,0 +1,113 @@
import {
AlertCustom,
AvatarCustom,
ButtonCustom,
CenterCustom,
DrawerCustom,
Grid,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection";
import { listDummyDiscussionForum } from "@/screens/Forum/list-data-dummy";
import Forum_MenuDrawerBerandaSection from "@/screens/Forum/MenuDrawerSection.tsx/MenuBeranda";
import { useLocalSearchParams } from "expo-router";
import { useState } from "react";
export default function Forumku() {
const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false);
const [status, setStatus] = useState("");
const [alertStatus, setAlertStatus] = useState(false);
const [deleteAlert, setDeleteAlert] = useState(false);
return (
<>
<ViewWrapper>
<StackCustom>
<CenterCustom>
<AvatarCustom
href={`/(application)/(image)/preview-image/${id}`}
size="xl"
/>
</CenterCustom>
<Grid>
<Grid.Col span={6}>
<TextCustom bold truncate>
@bagas_banuna
</TextCustom>
<TextCustom>1 postingan</TextCustom>
</Grid.Col>
<Grid.Col span={6} style={{ alignItems: "flex-end" }}>
<ButtonCustom href={`/profile/${id}`}>
Kunjungi Profile
</ButtonCustom>
</Grid.Col>
</Grid>
{listDummyDiscussionForum.map((e, i) => (
<Forum_BoxDetailSection
key={i}
data={e}
setOpenDrawer={setOpenDrawer}
setStatus={setStatus}
isTruncate={true}
href={`/forum/${id}`}
/>
))}
</StackCustom>
</ViewWrapper>
{/* Drawer Komponen Eksternal */}
<DrawerCustom
height={350}
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
>
<Forum_MenuDrawerBerandaSection
id={id as string}
status={status}
setIsDrawerOpen={() => {
setOpenDrawer(false);
}}
setShowDeleteAlert={setDeleteAlert}
setShowAlertStatus={setAlertStatus}
/>
</DrawerCustom>
{/* Alert Komponen Eksternal */}
<AlertCustom
isVisible={alertStatus}
onLeftPress={() => setAlertStatus(false)}
onRightPress={() => {
setOpenDrawer(false);
setAlertStatus(false);
console.log("Ubah status forum");
}}
title="Ubah Status Forum"
message="Apakah Anda yakin ingin mengubah status forum ini?"
textLeft="Batal"
textRight="Ubah"
colorRight={MainColor.green}
/>
{/* Alert Delete */}
<AlertCustom
isVisible={deleteAlert}
onLeftPress={() => setDeleteAlert(false)}
onRightPress={() => {
setOpenDrawer(false);
setDeleteAlert(false);
console.log("Hapus forum");
}}
title="Hapus Forum"
message="Apakah Anda yakin ingin menghapus forum ini?"
textLeft="Batal"
textRight="Hapus"
colorRight={MainColor.red}
/>
</>
);
}

View File

@@ -0,0 +1,176 @@
import {
AlertCustom,
ButtonCustom,
DrawerCustom,
Spacing,
TextAreaCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import Forum_CommentarBoxSection from "@/screens/Forum/CommentarBoxSection";
import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection";
import { listDummyCommentarForum } from "@/screens/Forum/list-data-dummy";
import Forum_MenuDrawerBerandaSection from "@/screens/Forum/MenuDrawerSection.tsx/MenuBeranda";
import Forum_MenuDrawerCommentar from "@/screens/Forum/MenuDrawerSection.tsx/MenuCommentar";
import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
export default function ForumDetail() {
const { id } = useLocalSearchParams();
console.log(id);
const [openDrawer, setOpenDrawer] = useState(false);
const [status, setStatus] = useState("");
const [alertStatus, setAlertStatus] = useState(false);
const [deleteAlert, setDeleteAlert] = useState(false);
const [text, setText] = useState("");
// Comentar
const [openDrawerCommentar, setOpenDrawerCommentar] = useState(false);
const [alertDeleteCommentar, setAlertDeleteCommentar] = useState(false);
const dataDummy = {
name: "Bagas",
status: "Open",
date: "14/07/2025",
deskripsi:
"Lorem ipsum dolor sit amet consectetur adipisicing elit. Vitae inventore iure pariatur, libero omnis excepturi. Ullam ad officiis deleniti quos esse odit nesciunt, ipsam adipisci cumque aliquam corporis culpa fugit?",
jumlahBalas: 2,
};
return (
<>
<ViewWrapper>
{/* <StackCustom>
</StackCustom> */}
<Forum_BoxDetailSection
data={dataDummy}
setOpenDrawer={setOpenDrawer}
setStatus={setStatus}
/>
<TextAreaCustom
placeholder="Ketik diskusi anda..."
maxLength={1000}
showCount
value={text}
onChangeText={setText}
style={{
marginBottom: 0,
}}
/>
<ButtonCustom
style={{
alignSelf: "flex-end",
}}
onPress={() => {
console.log("Posting", text);
router.back();
}}
>
Balas
</ButtonCustom>
<Spacing height={40} />
{listDummyCommentarForum.map((e, i) => (
<Forum_CommentarBoxSection
key={i}
data={e}
setOpenDrawer={setOpenDrawerCommentar}
/>
))}
</ViewWrapper>
<DrawerCustom
height={350}
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
>
<Forum_MenuDrawerBerandaSection
id={id as string}
status={status}
setIsDrawerOpen={() => {
setOpenDrawer(false);
}}
setShowDeleteAlert={setDeleteAlert}
setShowAlertStatus={setAlertStatus}
/>
</DrawerCustom>
{/* Alert Status */}
<AlertCustom
isVisible={alertStatus}
title="Ubah Status Forum"
message="Apakah Anda yakin ingin mengubah status forum ini?"
onLeftPress={() => {
setOpenDrawer(false);
setAlertStatus(false);
console.log("Batal");
}}
onRightPress={() => {
setOpenDrawer(false);
setAlertStatus(false);
console.log("Ubah status forum");
}}
textLeft="Batal"
textRight="Ubah"
colorRight={MainColor.green}
/>
{/* Alert Delete */}
<AlertCustom
isVisible={deleteAlert}
title="Hapus Forum"
message="Apakah Anda yakin ingin menghapus forum ini?"
onLeftPress={() => {
setOpenDrawer(false);
setDeleteAlert(false);
console.log("Batal");
}}
onRightPress={() => {
setOpenDrawer(false);
setDeleteAlert(false);
console.log("Hapus forum");
}}
textLeft="Batal"
textRight="Hapus"
colorRight={MainColor.red}
/>
{/* Commentar */}
<DrawerCustom
height={350}
isVisible={openDrawerCommentar}
closeDrawer={() => setOpenDrawerCommentar(false)}
>
<Forum_MenuDrawerCommentar
id={id as string}
setIsDrawerOpen={() => {
setOpenDrawerCommentar(false);
}}
setShowDeleteAlert={setAlertDeleteCommentar}
/>
</DrawerCustom>
{/* Alert Delete Commentar */}
<AlertCustom
isVisible={alertDeleteCommentar}
title="Hapus Komentar"
message="Apakah Anda yakin ingin menghapus komentar ini?"
onLeftPress={() => {
setOpenDrawerCommentar(false);
setAlertDeleteCommentar(false);
console.log("Batal");
}}
onRightPress={() => {
setOpenDrawerCommentar(false);
setAlertDeleteCommentar(false);
console.log("Hapus commentar");
}}
textLeft="Batal"
textRight="Hapus"
colorRight={MainColor.red}
/>
</>
);
}

View File

@@ -0,0 +1,32 @@
import {
BoxButtonOnFooter,
ButtonCustom,
TextAreaCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { router } from "expo-router";
export default function ForumOtherReportCommentar() {
const handleSubmit = (
<BoxButtonOnFooter>
<ButtonCustom
backgroundColor={MainColor.red}
textColor={MainColor.white}
onPress={() => {
console.log("Report lainnya");
router.back();
}}
>
Report
</ButtonCustom>
</BoxButtonOnFooter>
);
return (
<>
<ViewWrapper footerComponent={handleSubmit}>
<TextAreaCustom placeholder="Laporkan Komentar" />
</ViewWrapper>
</>
);
}

View File

@@ -0,0 +1,32 @@
import {
BoxButtonOnFooter,
ButtonCustom,
TextAreaCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { router } from "expo-router";
export default function ForumOtherReportPosting() {
const handleSubmit = (
<BoxButtonOnFooter>
<ButtonCustom
backgroundColor={MainColor.red}
textColor={MainColor.white}
onPress={() => {
console.log("Report lainnya");
router.back();
}}
>
Report
</ButtonCustom>
</BoxButtonOnFooter>
);
return (
<>
<ViewWrapper footerComponent={handleSubmit}>
<TextAreaCustom placeholder="Laporkan Diskusi" />
</ViewWrapper>
</>
);
}

View File

@@ -0,0 +1,42 @@
import {
ButtonCustom,
Spacing,
StackCustom,
ViewWrapper
} from "@/components";
import { AccentColor, MainColor } from "@/constants/color-palet";
import Forum_ReportListSection from "@/screens/Forum/ReportListSection";
import { router } from "expo-router";
export default function ForumReportCommentar() {
return (
<>
<ViewWrapper>
<StackCustom>
<Forum_ReportListSection />
<ButtonCustom
backgroundColor={MainColor.red}
textColor={MainColor.white}
onPress={() => {
console.log("Report");
router.back();
}}
>
Report
</ButtonCustom>
<ButtonCustom
backgroundColor={AccentColor.blue}
textColor={MainColor.white}
onPress={() => {
console.log("Lainnya");
router.replace("/forum/[id]/other-report-commentar");
}}
>
Lainnya
</ButtonCustom>
<Spacing/>
</StackCustom>
</ViewWrapper>
</>
);
}

View File

@@ -0,0 +1,37 @@
import { ViewWrapper, StackCustom, ButtonCustom, Spacing } from "@/components";
import { MainColor, AccentColor } from "@/constants/color-palet";
import Forum_ReportListSection from "@/screens/Forum/ReportListSection";
import { router } from "expo-router";
export default function ForumReportPosting() {
return (
<>
<ViewWrapper>
<StackCustom>
<Forum_ReportListSection />
<ButtonCustom
backgroundColor={MainColor.red}
textColor={MainColor.white}
onPress={() => {
console.log("Report");
router.back();
}}
>
Report
</ButtonCustom>
<ButtonCustom
backgroundColor={AccentColor.blue}
textColor={MainColor.white}
onPress={() => {
console.log("Lainnya");
router.replace("/forum/[id]/other-report-posting");
}}
>
Lainnya
</ButtonCustom>
<Spacing />
</StackCustom>
</ViewWrapper>
</>
);
}

View File

@@ -0,0 +1,37 @@
import {
BoxButtonOnFooter,
ButtonCustom,
TextAreaCustom,
ViewWrapper,
} from "@/components";
import { router } from "expo-router";
import { useState } from "react";
export default function ForumCreate() {
const [text, setText] = useState("");
const buttonFooter = (
<BoxButtonOnFooter>
<ButtonCustom
onPress={() => {
console.log("Posting", text);
router.back();
}}
>
Posting
</ButtonCustom>
</BoxButtonOnFooter>
);
return (
<ViewWrapper footerComponent={buttonFooter}>
<TextAreaCustom
placeholder="Ketik diskusi anda..."
maxLength={1000}
showCount
value={text}
onChangeText={setText}
/>
</ViewWrapper>
);
}

View File

@@ -0,0 +1,128 @@
import {
AlertCustom,
AvatarCustom,
BackButton,
DrawerCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import FloatingButton from "@/components/Button/FloatingButton";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection";
import { listDummyDiscussionForum } from "@/screens/Forum/list-data-dummy";
import Forum_MenuDrawerBerandaSection from "@/screens/Forum/MenuDrawerSection.tsx/MenuBeranda";
import { Ionicons } from "@expo/vector-icons";
import { router, Stack } from "expo-router";
import { useState } from "react";
export default function Forum() {
const id = "test-id-forum";
const [openDrawer, setOpenDrawer] = useState(false);
const [status, setStatus] = useState("");
const [alertStatus, setAlertStatus] = useState(false);
const [deleteAlert, setDeleteAlert] = useState(false);
return (
<>
<Stack.Screen
options={{
title: "Forum",
headerLeft: () => <BackButton />,
headerRight: () => <AvatarCustom href={`/forum/${id}/forumku`} />,
}}
/>
<ViewWrapper
headerComponent={
<TextInputCustom
iconLeft={
<Ionicons
name="search-outline"
size={ICON_SIZE_SMALL}
color={MainColor.placeholder}
/>
}
placeholder="Cari topik forum..."
borderRadius={50}
containerStyle={{ marginBottom: 0 }}
/>
}
floatingButton={
<FloatingButton
onPress={() =>
router.navigate("/(application)/(user)/forum/create")
}
/>
}
>
{listDummyDiscussionForum.map((e, i) => (
<Forum_BoxDetailSection
key={i}
data={e}
setOpenDrawer={setOpenDrawer}
setStatus={setStatus}
isTruncate={true}
href={`/forum/${id}`}
/>
))}
</ViewWrapper>
<DrawerCustom
height={350}
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
>
<Forum_MenuDrawerBerandaSection
id={id}
status={status}
setIsDrawerOpen={() => {
setOpenDrawer(false);
}}
setShowDeleteAlert={setDeleteAlert}
setShowAlertStatus={setAlertStatus}
/>
</DrawerCustom>
{/* Alert Status */}
<AlertCustom
isVisible={alertStatus}
title="Ubah Status Forum"
message="Apakah Anda yakin ingin mengubah status forum ini?"
onLeftPress={() => {
setOpenDrawer(false);
setAlertStatus(false);
console.log("Batal");
}}
onRightPress={() => {
setOpenDrawer(false);
setAlertStatus(false);
console.log("Ubah status forum");
}}
textLeft="Batal"
textRight="Ubah"
colorRight={MainColor.green}
/>
{/* Alert Delete */}
<AlertCustom
isVisible={deleteAlert}
title="Hapus Forum"
message="Apakah Anda yakin ingin menghapus forum ini?"
onLeftPress={() => {
setOpenDrawer(false);
setDeleteAlert(false);
console.log("Batal");
}}
onRightPress={() => {
setOpenDrawer(false);
setDeleteAlert(false);
console.log("Hapus forum");
}}
textLeft="Batal"
textRight="Hapus"
colorRight={MainColor.red}
/>
</>
);
}

View File

@@ -1,9 +1,9 @@
import NewHomeView from "@/screens/Home/UiHome";
import UiHome from "@/screens/Home/UiHome";
export default function Application() {
return (
<>
<NewHomeView />
<UiHome />
</>
);
}

View File

@@ -0,0 +1,55 @@
import {
AvatarCustom,
BaseBox,
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
InformationBox,
MapCustom,
Spacing,
StackCustom,
ViewWrapper,
} from "@/components";
import CenterCustom from "@/components/Center/CenterCustom";
import { router, useLocalSearchParams } from "expo-router";
export default function MapsCustomPin() {
const { id } = useLocalSearchParams();
const buttonFooter = (
<BoxButtonOnFooter>
<ButtonCustom
onPress={() => {
console.log(`Simpan maps ${id}`);
router.back();
}}
>
Simpan
</ButtonCustom>
</BoxButtonOnFooter>
);
return (
<>
<ViewWrapper footerComponent={buttonFooter}>
<StackCustom>
<InformationBox text="Pin map akan secara otomatis menampilkan logo pada porotofolio ini, jika anda ingin melakukan custom silahkan upload logo pin baru anda." />
<CenterCustom>
<AvatarCustom size="xl" />
</CenterCustom>
<ButtonCenteredOnly
onPress={() => console.log("Upload")}
icon="upload"
>
Upload
</ButtonCenteredOnly>
<Spacing />
<BaseBox>
<MapCustom />
</BaseBox>
<Spacing />
</StackCustom>
</ViewWrapper>
</>
);
}

View File

@@ -0,0 +1,59 @@
import {
BaseBox,
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
InformationBox,
LandscapeFrameUploaded,
MapCustom,
Spacing,
TextInputCustom,
ViewWrapper
} from "@/components";
import { router, useLocalSearchParams } from "expo-router";
export default function MapsEdit() {
const { id } = useLocalSearchParams();
const buttonFooter = (
<BoxButtonOnFooter>
<ButtonCustom
onPress={() => {
console.log(`Simpan maps ${id}`);
router.back()
}}
>
Simpan
</ButtonCustom>
</BoxButtonOnFooter>
);
return (
<ViewWrapper footerComponent={buttonFooter}>
<InformationBox text="Tentukan lokasi pin map dengan menekan pada map." />
<BaseBox>
<MapCustom />
</BaseBox>
<TextInputCustom
required
label="Nama Pin"
placeholder="Masukkan nama pin maps"
/>
<Spacing />
<InformationBox text="Upload foto lokasi bisnis anda untuk ditampilkan dalam detail maps." />
<LandscapeFrameUploaded />
<ButtonCenteredOnly
icon="upload"
onPress={() => {
console.log("Upload foto ");
router.navigate(`/take-picture/${id}`);
}}
>
Upload
</ButtonCenteredOnly>
<Spacing height={50} />
</ViewWrapper>
);
}

View File

@@ -0,0 +1,59 @@
import {
BaseBox,
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
InformationBox,
LandscapeFrameUploaded,
Spacing,
TextCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import { router, useLocalSearchParams } from "expo-router";
export default function MapsCreate() {
const { id } = useLocalSearchParams();
const buttonFooter = (
<BoxButtonOnFooter>
<ButtonCustom
onPress={() => {
console.log(`Simpan maps ${id}`);
router.replace(`/portofolio/${id}`);
}}
>
Simpan
</ButtonCustom>
</BoxButtonOnFooter>
);
return (
<ViewWrapper footerComponent={buttonFooter}>
<InformationBox text="Tentukan lokasi pin map dengan menekan pada map." />
<BaseBox style={{ height: 400 }}>
<TextCustom>Maps Her</TextCustom>
</BaseBox>
<TextInputCustom
required
label="Nama Pin"
placeholder="Masukkan nama pin maps"
/>
<Spacing height={50} />
<InformationBox text="Upload foto lokasi bisnis anda untuk ditampilkan dalam detail maps." />
<LandscapeFrameUploaded />
<ButtonCenteredOnly
icon="upload"
onPress={() => {
console.log("Upload foto ");
router.navigate(`/take-picture/${id}`);
}}
>
Upload
</ButtonCenteredOnly>
<Spacing height={50} />
</ViewWrapper>
);
}

View File

@@ -0,0 +1,9 @@
import { MapCustom, ViewWrapper } from "@/components";
export default function Maps() {
return (
<ViewWrapper style={{ paddingInline: 0, paddingBlock: 0 }}>
<MapCustom height={"100%"} />
</ViewWrapper>
);
}

View File

@@ -0,0 +1,9 @@
import { TextCustom, ViewWrapper } from "@/components";
export default function Marketplace() {
return (
<ViewWrapper>
<TextCustom>Marketplace</TextCustom>
</ViewWrapper>
);
}

View File

@@ -0,0 +1,118 @@
import {
BaseBox,
Grid,
ScrollableCustom,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { useState } from "react";
import { View } from "react-native";
const categories = [
{ id: 1, label: "Semua" },
{ id: 2, label: "Event" },
{ id: 3, label: "Job" },
{ id: 4, label: "Voting" },
{ id: 5, label: "Donasi" },
{ id: 6, label: "Investasi" },
{ id: 7, label: "Forum" },
{ id: 8, label: "Collaboration" },
];
const selectedCategory = (id: number) => {
const category = categories.find((c) => c.id === id);
return category?.label;
};
const BoxNotification = ({
index,
activeCategory,
}: {
index: number;
activeCategory: number | null;
}) => {
return (
<>
<BaseBox
onPress={() =>
console.log(
"Notification >",
selectedCategory(activeCategory as number)
)
}
>
<StackCustom>
<TextCustom bold>
# {selectedCategory(activeCategory as number)}
</TextCustom>
<View
style={{
borderBottomColor: MainColor.white_gray,
borderBottomWidth: 1,
}}
/>
<TextCustom truncate={2}>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Sint odio
unde quidem voluptate quam culpa sequi molestias ipsa corrupti id,
soluta, nostrum adipisci similique, et illo asperiores deleniti eum
labore.
</TextCustom>
<Grid>
<Grid.Col span={6}>
<TextCustom size="small" color="gray">
{index + 1} Agustus 2025
</TextCustom>
</Grid.Col>
<Grid.Col span={6} style={{ alignItems: "flex-end" }}>
<TextCustom size="small" color="gray">
Belum lihat
</TextCustom>
</Grid.Col>
</Grid>
</StackCustom>
</BaseBox>
</>
);
};
export default function Notifications() {
const [activeCategory, setActiveCategory] = useState<number | null>(1);
const handlePress = (item: any) => {
setActiveCategory(item.id);
// tambahkan logika lain seperti filter dsb.
};
return (
<ViewWrapper
headerComponent={
<ScrollableCustom
data={categories}
onButtonPress={handlePress}
activeId={activeCategory as any}
/>
}
>
{Array.from({ length: 20 }).map((e, i) => (
<View key={i}>
<BoxNotification index={i} activeCategory={activeCategory} />
</View>
))}
{/* Konten utama di sini */}
{/* <View style={{ flex: 1 }}>
<Text style={{ color: "white" }}>
{activeCategory
? `Kategori Aktif: ${
categories.find((c) => c.id === activeCategory)?.label
}`
: "Pilih kategori"}
</Text>
</View> */}
</ViewWrapper>
);
}

View File

@@ -0,0 +1,191 @@
import {
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
Grid,
InformationBox,
LandscapeFrameUploaded,
SelectCustom,
Spacing,
StackCustom,
TextAreaCustom,
TextCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import dummyMasterBidangBisnis from "@/lib/dummy-data/master-bidang-bisnis";
import dummyMasterSubBidangBisnis from "@/lib/dummy-data/master-sub-bidang-bisnis";
import { Ionicons } from "@expo/vector-icons";
import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import { Text, TouchableOpacity, View } from "react-native";
import PhoneInput, { ICountry } from "react-native-international-phone-number";
export default function PortofolioCreate() {
const { id } = useLocalSearchParams();
const [selectedCountry, setSelectedCountry] = useState<null | ICountry>(null);
const [inputValue, setInputValue] = useState<string>("");
const [data, setData] = useState({
name: "",
bidang_usaha: "",
sub_bidang_usaha: "",
alamat: "",
nomor_telepon: "",
deskripsi: "",
});
function handleInputValue(phoneNumber: string) {
setInputValue(phoneNumber);
}
function handleSelectedCountry(country: ICountry) {
setSelectedCountry(country);
}
function handleSave() {
console.log("Selanjutnya");
router.replace(`/maps/create`);
}
const buttonSave = (
<BoxButtonOnFooter>
<ButtonCustom onPress={handleSave}>Selanjutnya</ButtonCustom>
</BoxButtonOnFooter>
);
return (
<ViewWrapper footerComponent={buttonSave}>
{/* <TextCustom>Portofolio Create {id}</TextCustom> */}
<StackCustom gap={"xs"}>
<InformationBox text="Lengkapi data bisnis anda." />
<TextInputCustom
required
label="Nama Bisnis"
placeholder="Masukkan nama bisnis"
/>
<SelectCustom
label="Bidang Usaha"
required
data={dummyMasterBidangBisnis.map((item) => ({
label: item.name,
value: item.id,
}))}
value={data.bidang_usaha}
onChange={(value) => {
setData({ ...(data as any), bidang_usaha: value });
}}
/>
<Grid>
<Grid.Col span={10}>
<SelectCustom
// disabled
label="Sub Bidang Usaha"
required
data={dummyMasterSubBidangBisnis.map((item) => ({
label: item.name,
value: item.id,
}))}
value={data.sub_bidang_usaha}
onChange={(value) => {
setData({ ...(data as any), sub_bidang_usaha: value });
}}
/>
</Grid.Col>
<Grid.Col
span={2}
style={{ alignItems: "center", justifyContent: "center" }}
>
<TouchableOpacity onPress={() => console.log("delete")}>
<Ionicons name="trash" size={24} color={MainColor.red} />
</TouchableOpacity>
</Grid.Col>
</Grid>
<ButtonCenteredOnly onPress={() => console.log("add")}>
Tambah Pilihan
</ButtonCenteredOnly>
<Spacing />
<View>
<View style={{ flexDirection: "row", alignItems: "center" }}>
<TextCustom semiBold style={{ color: MainColor.white_gray }}>
Nomor Telepon
</TextCustom>
<Text style={{ color: "red" }}> *</Text>
</View>
<Spacing height={5} />
<PhoneInput
value={inputValue}
onChangePhoneNumber={handleInputValue}
selectedCountry={selectedCountry}
onChangeSelectedCountry={handleSelectedCountry}
defaultCountry="ID"
placeholder="xxx-xxx-xxx"
/>
</View>
<Spacing />
<TextInputCustom
required
label="Alamat Bisnis"
placeholder="Masukkan alamat bisnis"
/>
<TextAreaCustom
label="Deskripsi Bisnis"
placeholder="Masukkan deskripsi bisnis"
value={data.deskripsi}
onChangeText={(value: any) => setData({ ...data, deskripsi: value })}
autosize
minRows={2}
maxRows={5}
required
showCount
maxLength={100}
/>
<Spacing />
{/* Logo */}
<InformationBox text="Upload logo bisnis anda untuk di tampilaka pada portofolio." />
<LandscapeFrameUploaded />
<ButtonCenteredOnly
icon="upload"
onPress={() => {
console.log("Upload logo >>", id);
router.navigate(`/(application)/(image)/take-picture/${id}`);
}}
>
Upload
</ButtonCenteredOnly>
<Spacing height={40} />
{/* Social Media */}
<InformationBox text="Isi hanya pada sosial media yang anda miliki." />
<TextInputCustom
label="Tiktok"
placeholder="Masukkan username tiktok"
/>
<TextInputCustom
label="Facebook"
placeholder="Masukkan username facebook"
/>
<TextInputCustom
label="Instagram"
placeholder="Masukkan username instagram"
/>
<TextInputCustom
label="Twitter"
placeholder="Masukkan username twitter"
/>
<TextInputCustom
label="Youtube"
placeholder="Masukkan username youtube"
/>
<Spacing />
</StackCustom>
</ViewWrapper>
);
}

View File

@@ -0,0 +1,48 @@
import {
AvatarCustom,
BaseBox,
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
ViewWrapper,
} from "@/components";
import { router, useLocalSearchParams } from "expo-router";
export default function PortofolioEditLogo() {
const { id } = useLocalSearchParams();
const buttonFooter = (
<BoxButtonOnFooter>
<ButtonCustom
onPress={() => {
console.log("Simpan logo ");
router.back();
}}
>
Simpan
</ButtonCustom>
</BoxButtonOnFooter>
);
return (
<>
<ViewWrapper footerComponent={buttonFooter}>
<BaseBox
style={{
alignItems: "center",
justifyContent: "center",
height: 250,
}}
>
<AvatarCustom size="xl" />
</BaseBox>
<ButtonCenteredOnly
icon="upload"
onPress={() => router.navigate(`/take-picture/${id}`)}
>
Update
</ButtonCenteredOnly>
</ViewWrapper>
</>
);
}

View File

@@ -0,0 +1,36 @@
import {
BoxButtonOnFooter,
ButtonCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import { useLocalSearchParams, router } from "expo-router";
export default function PortofolioEditSocialMedia() {
const { id } = useLocalSearchParams();
const buttonFooter = (
<BoxButtonOnFooter>
<ButtonCustom
onPress={() => {
console.log(`Simpan sosmed ${id}`);
router.back();
}}
>
Simpan
</ButtonCustom>
</BoxButtonOnFooter>
);
return (
<>
<ViewWrapper footerComponent={buttonFooter}>
<TextInputCustom label="Tiktok" placeholder="Masukkan tiktok" />
<TextInputCustom label="Instagram" placeholder="Masukkan instagram" />
<TextInputCustom label="Facebook" placeholder="Masukkan facebook" />
<TextInputCustom label="Twitter" placeholder="Masukkan twitter" />
<TextInputCustom label="Youtube" placeholder="Masukkan youtube" />
</ViewWrapper>
</>
);
}

View File

@@ -0,0 +1,151 @@
import {
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
Grid,
SelectCustom,
Spacing,
StackCustom,
TextAreaCustom,
TextCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import dummyMasterBidangBisnis from "@/lib/dummy-data/master-bidang-bisnis";
import dummyMasterSubBidangBisnis from "@/lib/dummy-data/master-sub-bidang-bisnis";
import { Ionicons } from "@expo/vector-icons";
import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import { Text, TouchableOpacity, View } from "react-native";
import PhoneInput, { ICountry } from "react-native-international-phone-number";
export default function PortofolioEdit() {
const { id } = useLocalSearchParams();
const [selectedCountry, setSelectedCountry] = useState<null | ICountry>(null);
const [inputValue, setInputValue] = useState<string>("");
const [data, setData] = useState({
name: "",
bidang_usaha: "",
sub_bidang_usaha: "",
alamat: "",
nomor_telepon: "",
deskripsi: "",
});
function handleInputValue(phoneNumber: string) {
setInputValue(phoneNumber);
}
function handleSelectedCountry(country: ICountry) {
setSelectedCountry(country);
}
function handleSave() {
console.log(`Update portofolio berhasil ${id}`);
router.back();
}
const buttonUpdate = (
<BoxButtonOnFooter>
<ButtonCustom onPress={handleSave}>Simpan</ButtonCustom>
</BoxButtonOnFooter>
);
return (
<>
<ViewWrapper footerComponent={buttonUpdate}>
<StackCustom gap={"xs"}>
<TextInputCustom
required
label="Nama Bisnis"
placeholder="Masukkan nama bisnis"
/>
<SelectCustom
label="Bidang Usaha"
required
data={dummyMasterBidangBisnis.map((item) => ({
label: item.name,
value: item.id,
}))}
value={data.bidang_usaha}
onChange={(value) => {
setData({ ...(data as any), bidang_usaha: value });
}}
/>
<Grid>
<Grid.Col span={10}>
<SelectCustom
// disabled
label="Sub Bidang Usaha"
required
data={dummyMasterSubBidangBisnis.map((item) => ({
label: item.name,
value: item.id,
}))}
value={data.sub_bidang_usaha}
onChange={(value) => {
setData({ ...(data as any), sub_bidang_usaha: value });
}}
/>
</Grid.Col>
<Grid.Col
span={2}
style={{ alignItems: "center", justifyContent: "center" }}
>
<TouchableOpacity onPress={() => console.log("delete")}>
<Ionicons name="trash" size={24} color={MainColor.red} />
</TouchableOpacity>
</Grid.Col>
</Grid>
<ButtonCenteredOnly onPress={() => console.log("add")}>
Tambah Pilihan
</ButtonCenteredOnly>
<Spacing />
<View>
<View style={{ flexDirection: "row", alignItems: "center" }}>
<TextCustom semiBold style={{ color: MainColor.white_gray }}>
Nomor Telepon
</TextCustom>
<Text style={{ color: "red" }}> *</Text>
</View>
<Spacing height={5} />
<PhoneInput
value={inputValue}
onChangePhoneNumber={handleInputValue}
selectedCountry={selectedCountry}
onChangeSelectedCountry={handleSelectedCountry}
defaultCountry="ID"
placeholder="xxx-xxx-xxx"
/>
</View>
<Spacing />
<TextInputCustom
required
label="Alamat Bisnis"
placeholder="Masukkan alamat bisnis"
/>
<TextAreaCustom
label="Deskripsi Bisnis"
placeholder="Masukkan deskripsi bisnis"
value={data.deskripsi}
onChangeText={(value: any) =>
setData({ ...data, deskripsi: value })
}
autosize
minRows={2}
maxRows={5}
required
showCount
maxLength={100}
/>
<Spacing />
</StackCustom>
</ViewWrapper>
</>
);
}

View File

@@ -0,0 +1,78 @@
import { AlertCustom, DrawerCustom } from "@/components";
import LeftButtonCustom from "@/components/Button/BackButton";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { MainColor } from "@/constants/color-palet";
import { drawerItemsPortofolio } from "@/screens/Portofolio/ListPage";
import Portofolio_MenuDrawerSection from "@/screens/Portofolio/MenuDrawer";
import PorfofolioSection from "@/screens/Portofolio/PorfofolioSection";
import { GStyles } from "@/styles/global-styles";
import { Ionicons } from "@expo/vector-icons";
import { Stack, useLocalSearchParams, router } from "expo-router";
import { useState } from "react";
import { TouchableOpacity } from "react-native";
export default function Portofolio() {
const { id } = useLocalSearchParams();
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
const [deleteAlert, setDeleteAlert] = useState(false);
const openDrawer = () => {
setIsDrawerOpen(true);
};
const closeDrawer = () => {
setIsDrawerOpen(false);
};
return (
<>
<ViewWrapper>
{/* Header */}
<Stack.Screen
options={{
title: "Portofolio",
headerLeft: () => <LeftButtonCustom />,
headerRight: () => (
<TouchableOpacity onPress={openDrawer}>
<Ionicons
name="ellipsis-vertical"
size={20}
color={MainColor.yellow}
/>
</TouchableOpacity>
),
headerStyle: GStyles.headerStyle,
headerTitleStyle: GStyles.headerTitleStyle,
}}
/>
<PorfofolioSection setShowDeleteAlert={setDeleteAlert} />
</ViewWrapper>
{/* Drawer Komponen Eksternal */}
<DrawerCustom
isVisible={isDrawerOpen}
closeDrawer={closeDrawer}
height={350}
>
<Portofolio_MenuDrawerSection
drawerItems={drawerItemsPortofolio({ id: id as string })}
setIsDrawerOpen={setIsDrawerOpen}
/>
</DrawerCustom>
{/* Alert Delete */}
<AlertCustom
isVisible={deleteAlert}
onLeftPress={() => setDeleteAlert(false)}
onRightPress={() => {
setDeleteAlert(false);
console.log("Hapus portofolio");
router.back();
}}
title="Hapus Portofolio"
message="Apakah Anda yakin ingin menghapus portofolio ini?"
textLeft="Batal"
textRight="Hapus"
colorRight={MainColor.red}
/>
</>
);
}

View File

@@ -0,0 +1,47 @@
import { BaseBox, Grid, TextCustom, ViewWrapper } from "@/components";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { Ionicons } from "@expo/vector-icons";
import { router, useLocalSearchParams } from "expo-router";
export default function ListPortofolio() {
const { id } = useLocalSearchParams();
return (
<ViewWrapper>
{Array.from({ length: 10 }).map((_, index) => (
<BaseBox
key={index}
style={{ backgroundColor: MainColor.darkblue }}
onPress={() => {
console.log("press to Portofolio");
router.push(`/portofolio/${id}`);
}}
>
<Grid>
<Grid.Col
span={10}
style={{ justifyContent: "center", backgroundColor: "" }}
>
<TextCustom bold size="large" truncate={1}>
Nama usaha portofolio
</TextCustom>
<TextCustom size="small" color="yellow">
#id-porofolio12345
</TextCustom>
</Grid.Col>
<Grid.Col
span={2}
style={{ alignItems: "flex-end", justifyContent: "center" }}
>
<Ionicons
name="caret-forward"
size={ICON_SIZE_SMALL}
color="white"
/>
</Grid.Col>
</Grid>
</BaseBox>
))}
</ViewWrapper>
);
}

View File

@@ -0,0 +1,32 @@
import LeftButtonCustom from "@/components/Button/BackButton";
import { HeaderStyles } from "@/styles/header-styles";
import { Stack } from "expo-router";
export default function PortofolioLayout() {
return (
<>
<Stack
screenOptions={{
...HeaderStyles,
headerLeft: () => <LeftButtonCustom />,
}}
>
{/* <Stack.Screen name="[id]/index" options={{ title: "Portofolio" }} /> */}
<Stack.Screen
name="[id]/create"
options={{ title: "Tambah Portofolio" }}
/>
<Stack.Screen
name="[id]/list"
options={{ title: "Daftar Portofolio" }}
/>
<Stack.Screen name="[id]/edit" options={{ title: "Edit Portofolio" }} />
<Stack.Screen name="[id]/edit-logo" options={{ title: "Edit Logo " }} />
<Stack.Screen
name="[id]/edit-social-media"
options={{ title: "Edit Social Media" }}
/>
</Stack>
</>
);
}

View File

@@ -0,0 +1,110 @@
import {
AvatarCustom,
ButtonCenteredOnly,
ButtonCustom,
SelectCustom,
Spacing,
StackCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import BoxButtonOnFooter from "@/components/Box/BoxButtonOnFooter";
import InformationBox from "@/components/Box/InformationBox";
import LandscapeFrameUploaded from "@/components/Image/LandscapeFrameUploaded";
import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import { View } from "react-native";
export default function CreateProfile() {
const { id } = useLocalSearchParams();
const [data, setData] = useState({
name: "",
email: "",
address: "",
gender: "",
});
const handlerSave = () => {
console.log("data create profile >>", data);
router.back();
};
const footerComponent = (
<BoxButtonOnFooter>
<ButtonCustom
onPress={handlerSave}
// disabled={!data.name || !data.email || !data.address || !data.gender}
>
Simpan
</ButtonCustom>
</BoxButtonOnFooter>
);
return (
<ViewWrapper footerComponent={footerComponent}>
<StackCustom>
<InformationBox text="Upload foto profile anda." />
<View style={{ alignItems: "center" }}>
<AvatarCustom size="xl" />
<Spacing />
<ButtonCenteredOnly
icon="upload"
onPress={() => router.navigate(`/take-picture/${id}`)}
>
Upload
</ButtonCenteredOnly>
</View>
<Spacing />
<View>
<InformationBox text="Upload foto latar belakang anda." />
<LandscapeFrameUploaded />
<Spacing />
<ButtonCenteredOnly
icon="upload"
onPress={() => router.navigate(`/take-picture/${id}`)}
>
Upload
</ButtonCenteredOnly>
</View>
<Spacing />
<TextInputCustom
required
label="Nama"
placeholder="Masukkan nama"
value={data.name}
onChangeText={(text) => setData({ ...data, name: text })}
/>
<TextInputCustom
keyboardType="email-address"
required
label="Email"
placeholder="Masukkan email"
value={data.email}
onChangeText={(text) => setData({ ...data, email: text })}
/>
<TextInputCustom
required
label="Alamat"
placeholder="Masukkan alamat"
value={data.address}
onChangeText={(text) => setData({ ...data, address: text })}
/>
<SelectCustom
label="Jenis Kelamin"
placeholder="Pilih jenis kelamin"
data={[
{ label: "Laki-laki", value: "laki-laki" },
{ label: "Perempuan", value: "perempuan" },
]}
value={data.gender}
required
onChange={(value) => setData({ ...(data as any), gender: value })}
/>
<Spacing />
</StackCustom>
</ViewWrapper>
);
}

View File

@@ -0,0 +1,108 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import {
ButtonCustom,
SelectCustom,
StackCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import BoxButtonOnFooter from "@/components/Box/BoxButtonOnFooter";
import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import { StyleSheet } from "react-native";
export default function ProfileEdit() {
const { id } = useLocalSearchParams();
const [data, setData] = useState({
nama: "Bagas Banuna",
email: "bagasbanuna@gmail.com",
alamat: "Jember",
selectedValue: "",
});
const options = [
{ label: "Laki-laki", value: "laki-laki" },
{ label: "Perempuan", value: "perempuan" },
];
const handleSave = () => {
console.log({
nama: data.nama,
email: data.email,
alamat: data.alamat,
selectedValue: data.selectedValue,
});
router.back();
};
return (
<ViewWrapper
footerComponent={
<BoxButtonOnFooter>
<ButtonCustom
// disabled={
// !data.nama || !data.email || !data.alamat || !data.selectedValue
// }
onPress={handleSave}
>
Simpan
</ButtonCustom>
</BoxButtonOnFooter>
}
>
<StackCustom gap={"xs"}>
<TextInputCustom
label="Nama"
placeholder="Nama"
value={data.nama}
onChangeText={(text) => {
setData({ ...data, nama: text });
}}
required
/>
<TextInputCustom
label="Email"
placeholder="Email"
value={data.email}
onChangeText={(text) => {
setData({ ...data, email: text });
}}
required
/>
<TextInputCustom
label="Alamat"
placeholder="Alamat"
value={data.alamat}
onChangeText={(text) => {
setData({ ...data, alamat: text });
}}
required
/>
<SelectCustom
required
label="Jenis Kelamin"
placeholder="Pilih jenis kelamin"
data={options}
value={data.selectedValue}
onChange={(value) => {
setData({ ...(data as any), selectedValue: value });
}}
/>
</StackCustom>
</ViewWrapper>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
padding: 20,
},
result: {
marginTop: 20,
fontSize: 16,
fontWeight: "bold",
},
});

View File

@@ -0,0 +1,83 @@
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import AlertCustom from "@/components/Alert/AlertCustom";
import LeftButtonCustom from "@/components/Button/BackButton";
import DrawerCustom from "@/components/Drawer/DrawerCustom";
import { MainColor } from "@/constants/color-palet";
import { drawerItemsProfile } from "@/screens/Profile/ListPage";
import Profile_MenuDrawerSection from "@/screens/Profile/MenuDrawerSection";
import ProfilSection from "@/screens/Profile/ProfilSection";
import { GStyles } from "@/styles/global-styles";
import { Ionicons } from "@expo/vector-icons";
import { router, Stack, useLocalSearchParams } from "expo-router";
import React, { useState } from "react";
import { TouchableOpacity } from "react-native";
export default function Profile() {
const { id } = useLocalSearchParams();
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
const [showLogoutAlert, setShowLogoutAlert] = useState(false);
const openDrawer = () => {
setIsDrawerOpen(true);
};
const closeDrawer = () => {
setIsDrawerOpen(false);
};
const handleLogout = () => {
console.log("User logout");
router.replace("/");
setShowLogoutAlert(false);
};
return (
<>
<ViewWrapper>
{/* Header */}
<Stack.Screen
options={{
title: "Profile",
headerLeft: () => <LeftButtonCustom />,
headerRight: () => (
<TouchableOpacity onPress={openDrawer}>
<Ionicons
name="ellipsis-vertical"
size={20}
color={MainColor.yellow}
/>
</TouchableOpacity>
),
headerStyle: GStyles.headerStyle,
headerTitleStyle: GStyles.headerTitleStyle,
}}
/>
<ProfilSection />
</ViewWrapper>
{/* Drawer Komponen Eksternal */}
<DrawerCustom
height={350}
isVisible={isDrawerOpen}
closeDrawer={closeDrawer}
>
<Profile_MenuDrawerSection
drawerItems={drawerItemsProfile({ id: id as string })}
setShowLogoutAlert={setShowLogoutAlert}
setIsDrawerOpen={setIsDrawerOpen}
/>
</DrawerCustom>
{/* Alert Komponen Eksternal */}
<AlertCustom
isVisible={showLogoutAlert}
onLeftPress={() => setShowLogoutAlert(false)}
onRightPress={handleLogout}
title="Apakah anda yakin ingin keluar?"
textLeft="Batal"
textRight="Keluar"
colorRight={MainColor.red}
/>
</>
);
}

View File

@@ -0,0 +1,190 @@
// COMPONENT : Jika ingin uoload gambar dan video gunakan component ini
import {
ButtonCustom,
Spacing,
StackCustom,
ViewWrapper
} from "@/components";
import AntDesign from "@expo/vector-icons/AntDesign";
import Feather from "@expo/vector-icons/Feather";
import FontAwesome6 from "@expo/vector-icons/FontAwesome6";
import {
CameraMode,
CameraType,
CameraView,
useCameraPermissions,
} from "expo-camera";
import { Image } from "expo-image";
import { router } from "expo-router";
import { useRef, useState } from "react";
import { Button, Pressable, StyleSheet, Text, View } from "react-native";
export default function TakePictureProfile2() {
const [permission, requestPermission] = useCameraPermissions();
const ref = useRef<CameraView>(null);
const [uri, setUri] = useState<string | null>(null);
const [mode, setMode] = useState<CameraMode>("picture");
const [facing, setFacing] = useState<CameraType>("back");
const [recording, setRecording] = useState(false);
if (!permission?.granted) {
return (
<View style={styles.container}>
<Text style={{ textAlign: "center" }}>
We need your permission to use the camera
</Text>
<Button onPress={requestPermission} title="Grant permission" />
</View>
);
}
const takePicture = async () => {
const photo = await ref.current?.takePictureAsync();
setUri(photo?.uri || null);
};
const recordVideo = async () => {
if (recording) {
setRecording(false);
ref.current?.stopRecording();
return;
}
setRecording(true);
const video = await ref.current?.recordAsync();
console.log({ video });
};
const toggleMode = () => {
setMode((prev) => (prev === "picture" ? "video" : "picture"));
};
const toggleFacing = () => {
setFacing((prev) => (prev === "back" ? "front" : "back"));
};
const renderPicture = () => {
console.log("renderPicture", uri);
return (
<View>
<Image
source={uri ? uri : null}
contentFit="contain"
style={{ width: 340, aspectRatio: 1 }}
/>
<Spacing />
<StackCustom>
<ButtonCustom onPress={() => setUri(null)} title="Foto ulang" />
<ButtonCustom
onPress={() => {
console.log("Update foto");
router.back();
}}
title="Update Foto"
/>
</StackCustom>
</View>
);
};
const renderCameraUI = () => {
return (
<View style={styles.cameraOverlay}>
<View style={styles.shutterContainer}>
<Pressable onPress={toggleMode}>
{mode === "picture" ? (
<AntDesign name="picture" size={32} color="white" />
) : (
<Feather name="video" size={32} color="white" />
)}
</Pressable>
<Pressable onPress={mode === "picture" ? takePicture : recordVideo}>
{({ pressed }) => (
<View
style={[
styles.shutterBtn,
{
opacity: pressed ? 0.5 : 1,
},
]}
>
<View
style={[
styles.shutterBtnInner,
{
backgroundColor: mode === "picture" ? "white" : "red",
},
]}
/>
</View>
)}
</Pressable>
<Pressable onPress={toggleFacing}>
<FontAwesome6 name="rotate-left" size={32} color="white" />
</Pressable>
</View>
</View>
);
};
return (
<>
{uri ? (
<ViewWrapper>
<View style={styles.container}>{renderPicture()}</View>
</ViewWrapper>
) : (
<>
<CameraView
style={styles.camera}
ref={ref}
mode={mode}
facing={facing}
mute={false}
responsiveOrientationWhenOrientationLocked
/>
{renderCameraUI()}
</>
)}
</>
);
}
const styles = StyleSheet.create({
container: {
justifyContent: "center",
alignItems: "center",
},
camera: {
flex: 1,
width: "100%",
},
cameraOverlay: {
...StyleSheet.absoluteFillObject,
justifyContent: "flex-end",
padding: 44,
},
shutterContainer: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
},
shutterBtn: {
backgroundColor: "transparent",
borderWidth: 5,
borderColor: "white",
width: 85,
height: 85,
borderRadius: 45,
alignItems: "center",
justifyContent: "center",
},
shutterBtnInner: {
width: 70,
height: 70,
borderRadius: 50,
},
});

View File

@@ -0,0 +1,47 @@
import {
BaseBox,
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
} from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import DUMMY_IMAGE from "@/constants/dummy-image-value";
import { router, useLocalSearchParams } from "expo-router";
import { Image } from "react-native";
export default function UpdateBackgroundProfile() {
const { id } = useLocalSearchParams();
const buttonFooter = (
<BoxButtonOnFooter>
<ButtonCustom
onPress={() => {
console.log("Simpan foto background >>", id);
router.back();
}}
>
Simpan
</ButtonCustom>
</BoxButtonOnFooter>
);
return (
<ViewWrapper footerComponent={buttonFooter}>
<BaseBox
style={{ alignItems: "center", justifyContent: "center", height: 250 }}
>
<Image
source={DUMMY_IMAGE.background}
resizeMode="cover"
style={{ width: "100%", height: "100%", borderRadius: 10 }}
/>
</BaseBox>
<ButtonCenteredOnly
icon="upload"
onPress={() => router.navigate(`/(application)/take-picture/${id}`)}
>
Update
</ButtonCenteredOnly>
</ViewWrapper>
);
}

View File

@@ -0,0 +1,47 @@
import {
AvatarCustom,
BaseBox,
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
} from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { router, useLocalSearchParams } from "expo-router";
export default function UpdatePhotoProfile() {
const { id } = useLocalSearchParams();
const buttonFooter = (
<BoxButtonOnFooter>
<ButtonCustom
onPress={() => {
console.log("Simpan foto profile >>", id);
router.back();
}}
>
Simpan
</ButtonCustom>
</BoxButtonOnFooter>
);
return (
<ViewWrapper footerComponent={buttonFooter}>
<BaseBox
style={{ alignItems: "center", justifyContent: "center", height: 250 }}
>
<AvatarCustom size="xl" />
</BaseBox>
<ButtonCenteredOnly
icon="upload"
onPress={() => {
console.log("Update photo >>", id);
router.navigate(`/(application)/take-picture/${id}`);
}}
>
Update
</ButtonCenteredOnly>
{/* <Spacing />
<ButtonCustom>Test</ButtonCustom> */}
</ViewWrapper>
);
}

View File

@@ -0,0 +1,34 @@
import { BackButton } from "@/components";
import { GStyles } from "@/styles/global-styles";
import { Stack } from "expo-router";
export default function ProfileLayout() {
return (
<>
<Stack
screenOptions={{
headerStyle: GStyles.headerStyle,
headerTitleStyle: GStyles.headerTitleStyle,
headerTitleAlign: "center",
headerBackButtonDisplayMode: "minimal",
headerLeft: () => <BackButton />,
}}
>
{/* <Stack.Screen name="[id]/index" options={{ headerShown: false }} /> */}
<Stack.Screen name="[id]/edit" options={{ title: "Edit Profile" }} />
<Stack.Screen
name="[id]/update-photo"
options={{ title: "Update Foto" }}
/>
<Stack.Screen
name="[id]/update-background"
options={{ title: "Update Latar Belakang" }}
/>
<Stack.Screen
name="[id]/create"
options={{ title: "Buat Profile" }}
/>
</Stack>
</>
);
}

View File

@@ -0,0 +1,100 @@
import {
AvatarCustom,
ClickableCustom,
Grid,
Spacing,
StackCustom,
TextCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { Ionicons } from "@expo/vector-icons";
import { router } from "expo-router";
export default function UserSearch() {
function generateRandomPhoneNumber(index: number) {
let prefix;
// Menentukan prefix berdasarkan index genap atau ganjil
if (index % 2 === 0) {
const evenPrefixes = ["6288", "6289", "6281"];
prefix = evenPrefixes[Math.floor(Math.random() * evenPrefixes.length)];
} else {
const oddPrefixes = ["6285", "6283"];
prefix = oddPrefixes[Math.floor(Math.random() * oddPrefixes.length)];
}
// Menghitung panjang sisa nomor acak (antara 10 - 12 digit)
const remainingLength = Math.floor(Math.random() * 3) + 10; // 10, 11, atau 12
// Membuat sisa nomor acak
let randomNumber = "";
for (let i = 0; i < remainingLength; i++) {
randomNumber += Math.floor(Math.random() * 10); // Digit acak antara 0-9
}
// Menggabungkan prefix dan sisa nomor
return prefix + randomNumber;
}
return (
<>
<ViewWrapper
headerComponent={
<TextInputCustom
iconLeft={
<Ionicons
name="search"
size={ICON_SIZE_SMALL}
color={MainColor.placeholder}
/>
}
placeholder="Cari Pengguna"
borderRadius={50}
containerStyle={{ marginBottom: 0 }}
/>
}
>
<StackCustom>
{Array.from({ length: 20 }).map((e, index) => {
return (
<Grid key={index}>
<Grid.Col span={2}>
<AvatarCustom href={`/profile/${index}`}/>
</Grid.Col>
<Grid.Col span={9}>
<TextCustom size="large">Nama user {index}</TextCustom>
<TextCustom size="small">
+{generateRandomPhoneNumber(index)}
</TextCustom>
</Grid.Col>
<Grid.Col
span={1}
style={{
justifyContent: "center",
alignItems: "flex-end",
}}
>
<ClickableCustom
onPress={() => {
console.log("Ke Profile");
router.push(`/profile/${index}`);
}}
>
<Ionicons
name="chevron-forward"
size={ICON_SIZE_SMALL}
color={MainColor.white}
/>
</ClickableCustom>
</Grid.Col>
</Grid>
);
})}
</StackCustom>
<Spacing height={50} />
</ViewWrapper>
</>
);
}

View File

@@ -1,104 +1,28 @@
import { AccentColor, MainColor } from "@/constants/color-palet";
import { Ionicons } from "@expo/vector-icons";
import { router, Stack } from "expo-router";
import { BackButton } from "@/components";
import { HeaderStyles } from "@/styles/header-styles";
import { Stack } from "expo-router";
export default function ApplicationLayout() {
return (
<>
<Stack
screenOptions={{
headerStyle: { backgroundColor: MainColor.darkblue },
headerTitleStyle: { color: MainColor.yellow, fontWeight: "bold" },
headerTitleAlign: "center",
contentStyle: {
borderBottomColor: AccentColor.blue,
borderBottomWidth: 2,
},
headerLargeStyle: {
backgroundColor: MainColor.darkblue,
},
}}
>
<Stack screenOptions={HeaderStyles}>
<Stack.Screen name="(user)" options={{ headerShown: false }} />
{/* Take Picture */}
<Stack.Screen
name="home"
name="(image)/take-picture/[id]/index"
options={{
title: "HIPMI",
headerLeft: () => (
<Ionicons
name="search"
size={20}
color={MainColor.yellow}
onPress={() => router.back()}
/>
),
headerRight: () => (
<Ionicons
name="notifications"
size={20}
color={MainColor.yellow}
onPress={() => router.back()}
/>
),
title: "Ambil Gambar",
headerLeft: () => <BackButton />,
}}
/>
{/* Preview Image */}
<Stack.Screen
name="forum/index"
name="(image)/preview-image/[id]/index"
options={{
title: "Forum",
headerLeft: () => (
<Ionicons
name="arrow-back"
size={20}
color={MainColor.yellow}
onPress={() => router.back()}
/>
),
}}
/>
<Stack.Screen
name="maps/index"
options={{
title: "Maps",
headerLeft: () => (
<Ionicons
name="arrow-back"
size={20}
color={MainColor.yellow}
onPress={() => router.back()}
/>
),
}}
/>
<Stack.Screen
name="market-place/index"
options={{
title: "Market Place",
headerLeft: () => (
<Ionicons
name="arrow-back"
size={20}
color={MainColor.yellow}
onPress={() => router.back()}
/>
),
}}
/>
<Stack.Screen
name="profile/index"
options={{
title: "Profile",
headerLeft: () => (
<Ionicons
name="arrow-back"
size={20}
color={MainColor.yellow}
onPress={() => router.back()}
/>
),
title: "Preview Gambar",
headerLeft: () => <BackButton />,
}}
/>
</Stack>

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import React from "react";
import {
View,
@@ -86,7 +85,7 @@ const CustomTabNavigator = () => {
activeIcon: "notifications",
label: "Forum",
component: NotificationScreen,
path: "/(application)/(home-tabs)/forum",
path: "/forum",
},
{
id: "profile",
@@ -94,7 +93,7 @@ const CustomTabNavigator = () => {
activeIcon: "person",
label: "Katalog",
component: ProfileScreen,
path: "/(application)/(home-tabs)/katalog",
path: "/profile",
},
];
@@ -102,7 +101,7 @@ const CustomTabNavigator = () => {
// Function untuk handle tab press
const handleTabPress = (tabId: string) => {
setActiveTab(tabId);
// setShowHome(false); // Hide home when any tab is pressed
setShowHome(false); // Hide home when any tab is pressed
};
// Determine which component to show
@@ -110,8 +109,9 @@ const CustomTabNavigator = () => {
if (showHome || activeTab === "home") {
return HomeScreen;
}
const selectedTab = tabs.find((tab) => tab.id === activeTab);
return selectedTab ? selectedTab.component : HomeScreen;
// const selectedTab = tabs.find((tab) => tab.id === activeTab);
// return selectedTab ? selectedTab.component : HomeScreen;
return HomeScreen
};
const ActiveComponent = getActiveComponent();
@@ -135,7 +135,7 @@ const CustomTabNavigator = () => {
label={e.label}
isActive={activeTab === e.id && !showHome}
onPress={() => {
// handleTabPress(e.id);
handleTabPress(e.id);
router.push(e.path as any);
}}
/>

View File

@@ -1,9 +0,0 @@
import { Text, View } from "react-native";
export default function Event() {
return (
<View>
<Text>Event</Text>
</View>
)
}

View File

@@ -1,11 +0,0 @@
import { Text, View } from "react-native";
export default function Forum() {
return (
<>
<View>
<Text>Forum</Text>
</View>
</>
);
}

View File

@@ -1,9 +0,0 @@
import { Text, View } from "react-native";
export default function Maps() {
return (
<View>
<Text>Maps</Text>
</View>
)
}

View File

@@ -1,9 +0,0 @@
import { Text, View } from "react-native";
export default function MarketPlace() {
return (
<View>
<Text>Market Place</Text>
</View>
);
}

View File

@@ -1,9 +0,0 @@
import { Text, View } from "react-native";
export default function Profile() {
return (
<View>
<Text>Profile</Text>
</View>
);
}

View File

@@ -1,9 +1,16 @@
import { Text, View } from "react-native";
import { StackCustom, TextCustom, ViewWrapper } from "@/components";
export default function NotFoundScreen() {
return (
<View>
<Text>Not Found</Text>
</View>
)
<ViewWrapper>
<StackCustom align="center" gap={0} style={{justifyContent: "center", alignItems: "center", flex: 1}}>
<TextCustom size="large" bold style={{fontSize: 100}}>
404
</TextCustom>
<TextCustom size="large" bold>
Sorry, File Not Found
</TextCustom>
</StackCustom>
</ViewWrapper>
);
}

View File

@@ -1,34 +1,32 @@
import { MainColor } from "@/constants/color-palet";
import { Stack } from "expo-router";
import "react-native-gesture-handler";
import { SafeAreaProvider } from "react-native-safe-area-context";
export default function RootLayout() {
return (
<SafeAreaProvider
style={{
backgroundColor: MainColor.darkblue,
}}
>
<Stack
screenOptions={{
headerStyle: { backgroundColor: MainColor.darkblue },
headerTitleStyle: { color: MainColor.yellow, fontWeight: "bold" },
headerTitleAlign: "center",
// contentStyle: {
// borderBottomColor: AccentColor.blue,
// borderBottomWidth: 2,
// },
// headerLargeStyle: {
// backgroundColor: MainColor.darkblue,
// },
// headerShadowVisible: false,
}}
>
<Stack.Screen name="index" options={{ headerShown: false }} />
<Stack.Screen name="verification" options={{ headerShown: false }} />
<Stack.Screen name="register" options={{ headerShown: false }} />
<Stack.Screen name="(application)" options={{ headerShown: false }} />
</Stack>
</SafeAreaProvider>
<>
<SafeAreaProvider>
<Stack
screenOptions={{
headerStyle: { backgroundColor: MainColor.darkblue },
headerTitleStyle: { color: MainColor.yellow, fontWeight: "bold" },
headerTitleAlign: "center",
}}
>
<Stack.Screen name="index" options={{ title: "" }} />
<Stack.Screen name="+not-found" options={{ title: "" }} />
<Stack.Screen
name="verification"
options={{ title: "", headerBackVisible: false }}
/>
<Stack.Screen
name="register"
options={{ title: "", headerBackVisible: false }}
/>
<Stack.Screen name="(application)" options={{ headerShown: false }} />
</Stack>
</SafeAreaProvider>
</>
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

143
bun.lock
View File

@@ -5,30 +5,37 @@
"name": "hipmi-mobile",
"dependencies": {
"@expo/vector-icons": "^14.1.0",
"@react-native-community/datetimepicker": "8.4.1",
"@react-navigation/bottom-tabs": "^7.4.2",
"@react-navigation/drawer": "^7.5.2",
"@react-navigation/elements": "^2.3.8",
"@react-navigation/native": "^7.1.6",
"@react-navigation/native-stack": "^7.3.21",
"@types/react-native-vector-icons": "^6.4.18",
"expo": "~53.0.12",
"dayjs": "^1.11.13",
"expo": "53.0.17",
"expo-blur": "~14.1.5",
"expo-constants": "~17.1.6",
"expo-font": "~13.3.1",
"expo-camera": "~16.1.10",
"expo-constants": "~17.1.7",
"expo-font": "~13.3.2",
"expo-haptics": "~14.1.4",
"expo-image": "~2.3.0",
"expo-linking": "~7.1.5",
"expo-router": "~5.1.0",
"expo-splash-screen": "~0.30.9",
"expo-image": "~2.3.2",
"expo-image-picker": "~16.1.4",
"expo-linking": "~7.1.7",
"expo-router": "~5.1.3",
"expo-splash-screen": "~0.30.10",
"expo-status-bar": "~2.2.3",
"expo-symbols": "~0.4.5",
"expo-system-ui": "~5.0.9",
"expo-system-ui": "~5.0.10",
"expo-web-browser": "~14.2.0",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-native": "0.79.4",
"react-native": "0.79.5",
"react-native-gesture-handler": "~2.24.0",
"react-native-international-phone-number": "^0.9.3",
"react-native-maps": "1.20.1",
"react-native-otp-entry": "^1.8.5",
"react-native-paper": "^5.14.5",
"react-native-reanimated": "~3.17.4",
"react-native-safe-area-context": "5.4.0",
"react-native-screens": "~4.11.1",
@@ -234,6 +241,8 @@
"@babel/types": ["@babel/types@7.27.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q=="],
"@callstack/react-theme-provider": ["@callstack/react-theme-provider@3.0.9", "", { "dependencies": { "deepmerge": "^3.2.0", "hoist-non-react-statics": "^3.3.0" }, "peerDependencies": { "react": ">=16.3.0" } }, "sha512-tTQ0uDSCL0ypeMa8T/E9wAZRGKWj8kXP7+6RYgPTfOPs9N07C9xM8P02GJ3feETap4Ux5S69D9nteq9mEj86NA=="],
"@egjs/hammerjs": ["@egjs/hammerjs@2.0.17", "", { "dependencies": { "@types/hammerjs": "^2.0.36" } }, "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A=="],
"@emnapi/core": ["@emnapi/core@1.4.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" } }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="],
@@ -260,37 +269,37 @@
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.3.2", "", { "dependencies": { "@eslint/core": "^0.15.0", "levn": "^0.4.1" } }, "sha512-4SaFZCNfJqvk/kenHpI8xvN42DMaoycy4PzKc5otHxRswww1kAt82OlBuwRVLofCACCTZEcla2Ydxv8scMXaTg=="],
"@expo/cli": ["@expo/cli@0.24.15", "", { "dependencies": { "@0no-co/graphql.web": "^1.0.8", "@babel/runtime": "^7.20.0", "@expo/code-signing-certificates": "^0.0.5", "@expo/config": "~11.0.10", "@expo/config-plugins": "~10.0.3", "@expo/devcert": "^1.1.2", "@expo/env": "~1.0.5", "@expo/image-utils": "^0.7.4", "@expo/json-file": "^9.1.4", "@expo/metro-config": "~0.20.15", "@expo/osascript": "^2.2.4", "@expo/package-manager": "^1.8.4", "@expo/plist": "^0.3.4", "@expo/prebuild-config": "^9.0.7", "@expo/spawn-async": "^1.7.2", "@expo/ws-tunnel": "^1.0.1", "@expo/xcpretty": "^4.3.0", "@react-native/dev-middleware": "0.79.4", "@urql/core": "^5.0.6", "@urql/exchange-retry": "^1.3.0", "accepts": "^1.3.8", "arg": "^5.0.2", "better-opn": "~3.0.2", "bplist-creator": "0.1.0", "bplist-parser": "^0.3.1", "chalk": "^4.0.0", "ci-info": "^3.3.0", "compression": "^1.7.4", "connect": "^3.7.0", "debug": "^4.3.4", "env-editor": "^0.4.1", "freeport-async": "^2.0.0", "getenv": "^2.0.0", "glob": "^10.4.2", "lan-network": "^0.1.6", "minimatch": "^9.0.0", "node-forge": "^1.3.1", "npm-package-arg": "^11.0.0", "ora": "^3.4.0", "picomatch": "^3.0.1", "pretty-bytes": "^5.6.0", "pretty-format": "^29.7.0", "progress": "^2.0.3", "prompts": "^2.3.2", "qrcode-terminal": "0.11.0", "require-from-string": "^2.0.2", "requireg": "^0.2.2", "resolve": "^1.22.2", "resolve-from": "^5.0.0", "resolve.exports": "^2.0.3", "semver": "^7.6.0", "send": "^0.19.0", "slugify": "^1.3.4", "source-map-support": "~0.5.21", "stacktrace-parser": "^0.1.10", "structured-headers": "^0.4.1", "tar": "^7.4.3", "terminal-link": "^2.1.1", "undici": "^6.18.2", "wrap-ansi": "^7.0.0", "ws": "^8.12.1" }, "bin": { "expo-internal": "build/bin/cli" } }, "sha512-RDZS30OSnbXkSPnBXdyPL29KbltjOmegE23bZZDiGV23WOReWcPgRc5U7Fd8eLPhtRjHBKlBpNJMTed5Ntr/uw=="],
"@expo/cli": ["@expo/cli@0.24.18", "", { "dependencies": { "@0no-co/graphql.web": "^1.0.8", "@babel/runtime": "^7.20.0", "@expo/code-signing-certificates": "^0.0.5", "@expo/config": "~11.0.12", "@expo/config-plugins": "~10.1.1", "@expo/devcert": "^1.1.2", "@expo/env": "~1.0.7", "@expo/image-utils": "^0.7.6", "@expo/json-file": "^9.1.5", "@expo/metro-config": "~0.20.17", "@expo/osascript": "^2.2.5", "@expo/package-manager": "^1.8.6", "@expo/plist": "^0.3.5", "@expo/prebuild-config": "^9.0.10", "@expo/spawn-async": "^1.7.2", "@expo/ws-tunnel": "^1.0.1", "@expo/xcpretty": "^4.3.0", "@react-native/dev-middleware": "0.79.5", "@urql/core": "^5.0.6", "@urql/exchange-retry": "^1.3.0", "accepts": "^1.3.8", "arg": "^5.0.2", "better-opn": "~3.0.2", "bplist-creator": "0.1.0", "bplist-parser": "^0.3.1", "chalk": "^4.0.0", "ci-info": "^3.3.0", "compression": "^1.7.4", "connect": "^3.7.0", "debug": "^4.3.4", "env-editor": "^0.4.1", "freeport-async": "^2.0.0", "getenv": "^2.0.0", "glob": "^10.4.2", "lan-network": "^0.1.6", "minimatch": "^9.0.0", "node-forge": "^1.3.1", "npm-package-arg": "^11.0.0", "ora": "^3.4.0", "picomatch": "^3.0.1", "pretty-bytes": "^5.6.0", "pretty-format": "^29.7.0", "progress": "^2.0.3", "prompts": "^2.3.2", "qrcode-terminal": "0.11.0", "require-from-string": "^2.0.2", "requireg": "^0.2.2", "resolve": "^1.22.2", "resolve-from": "^5.0.0", "resolve.exports": "^2.0.3", "semver": "^7.6.0", "send": "^0.19.0", "slugify": "^1.3.4", "source-map-support": "~0.5.21", "stacktrace-parser": "^0.1.10", "structured-headers": "^0.4.1", "tar": "^7.4.3", "terminal-link": "^2.1.1", "undici": "^6.18.2", "wrap-ansi": "^7.0.0", "ws": "^8.12.1" }, "bin": { "expo-internal": "build/bin/cli" } }, "sha512-4cUVWa7bHS0oLn9hBCH5QKI47o+qm0JTUl8xINC0In0JIG9mlLugDSNY+t81powr28Htq9n5gOGkKPTGc1vKiw=="],
"@expo/code-signing-certificates": ["@expo/code-signing-certificates@0.0.5", "", { "dependencies": { "node-forge": "^1.2.1", "nullthrows": "^1.1.1" } }, "sha512-BNhXkY1bblxKZpltzAx98G2Egj9g1Q+JRcvR7E99DOj862FTCX+ZPsAUtPTr7aHxwtrL7+fL3r0JSmM9kBm+Bw=="],
"@expo/config": ["@expo/config@11.0.10", "", { "dependencies": { "@babel/code-frame": "~7.10.4", "@expo/config-plugins": "~10.0.2", "@expo/config-types": "^53.0.4", "@expo/json-file": "^9.1.4", "deepmerge": "^4.3.1", "getenv": "^1.0.0", "glob": "^10.4.2", "require-from-string": "^2.0.2", "resolve-from": "^5.0.0", "resolve-workspace-root": "^2.0.0", "semver": "^7.6.0", "slugify": "^1.3.4", "sucrase": "3.35.0" } }, "sha512-8S8Krr/c5lnl0eF03tA2UGY9rGBhZcbWKz2UWw5dpL/+zstwUmog8oyuuC8aRcn7GiTQLlbBkxcMeT8sOGlhbA=="],
"@expo/config": ["@expo/config@11.0.12", "", { "dependencies": { "@babel/code-frame": "~7.10.4", "@expo/config-plugins": "~10.1.1", "@expo/config-types": "^53.0.5", "@expo/json-file": "^9.1.5", "deepmerge": "^4.3.1", "getenv": "^2.0.0", "glob": "^10.4.2", "require-from-string": "^2.0.2", "resolve-from": "^5.0.0", "resolve-workspace-root": "^2.0.0", "semver": "^7.6.0", "slugify": "^1.3.4", "sucrase": "3.35.0" } }, "sha512-XEh7g5F8OziJ6eZzBi93qt2YwmPceD3yAEd4Qv/ODK3MrgFCmB5IAJJ2ZFepdeoQFpcxS26Nl4aUuIJYEhJiUw=="],
"@expo/config-plugins": ["@expo/config-plugins@10.0.3", "", { "dependencies": { "@expo/config-types": "^53.0.4", "@expo/json-file": "~9.1.4", "@expo/plist": "^0.3.4", "@expo/sdk-runtime-versions": "^1.0.0", "chalk": "^4.1.2", "debug": "^4.3.5", "getenv": "^2.0.0", "glob": "^10.4.2", "resolve-from": "^5.0.0", "semver": "^7.5.4", "slash": "^3.0.0", "slugify": "^1.6.6", "xcode": "^3.0.1", "xml2js": "0.6.0" } }, "sha512-fjCckkde67pSDf48x7wRuPsgQVIqlDwN7NlOk9/DFgQ1hCH0L5pGqoSmikA1vtAyiA83MOTpkGl3F3wyATyUog=="],
"@expo/config-plugins": ["@expo/config-plugins@10.1.1", "", { "dependencies": { "@expo/config-types": "^53.0.5", "@expo/json-file": "~9.1.5", "@expo/plist": "^0.3.5", "@expo/sdk-runtime-versions": "^1.0.0", "chalk": "^4.1.2", "debug": "^4.3.5", "getenv": "^2.0.0", "glob": "^10.4.2", "resolve-from": "^5.0.0", "semver": "^7.5.4", "slash": "^3.0.0", "slugify": "^1.6.6", "xcode": "^3.0.1", "xml2js": "0.6.0" } }, "sha512-2L/ryY/R/AzwHfLpzBBsj0qdwN+E2RkF24uwo33L7M5DOswbLVaA007IdLlun+G6ctZYnwgm3TDLxjbvqZ9Avw=="],
"@expo/config-types": ["@expo/config-types@53.0.4", "", {}, "sha512-0s+9vFx83WIToEr0Iwy4CcmiUXa5BgwBmEjylBB2eojX5XAMm9mJvw9KpjAb8m7zq2G0Q6bRbeufkzgbipuNQg=="],
"@expo/config-types": ["@expo/config-types@53.0.5", "", {}, "sha512-kqZ0w44E+HEGBjy+Lpyn0BVL5UANg/tmNixxaRMLS6nf37YsDrLk2VMAmeKMMk5CKG0NmOdVv3ngeUjRQMsy9g=="],
"@expo/devcert": ["@expo/devcert@1.2.0", "", { "dependencies": { "@expo/sudo-prompt": "^9.3.1", "debug": "^3.1.0", "glob": "^10.4.2" } }, "sha512-Uilcv3xGELD5t/b0eM4cxBFEKQRIivB3v7i+VhWLV/gL98aw810unLKKJbGAxAIhY6Ipyz8ChWibFsKFXYwstA=="],
"@expo/env": ["@expo/env@1.0.5", "", { "dependencies": { "chalk": "^4.0.0", "debug": "^4.3.4", "dotenv": "~16.4.5", "dotenv-expand": "~11.0.6", "getenv": "^1.0.0" } }, "sha512-dtEZ4CAMaVrFu2+tezhU3FoGWtbzQl50xV+rNJE5lYVRjUflWiZkVHlHkWUlPAwDPifLy4TuissVfScGGPWR5g=="],
"@expo/env": ["@expo/env@1.0.7", "", { "dependencies": { "chalk": "^4.0.0", "debug": "^4.3.4", "dotenv": "~16.4.5", "dotenv-expand": "~11.0.6", "getenv": "^2.0.0" } }, "sha512-qSTEnwvuYJ3umapO9XJtrb1fAqiPlmUUg78N0IZXXGwQRt+bkp0OBls+Y5Mxw/Owj8waAM0Z3huKKskRADR5ow=="],
"@expo/fingerprint": ["@expo/fingerprint@0.13.1", "", { "dependencies": { "@expo/spawn-async": "^1.7.2", "arg": "^5.0.2", "chalk": "^4.1.2", "debug": "^4.3.4", "find-up": "^5.0.0", "getenv": "^2.0.0", "glob": "^10.4.2", "ignore": "^5.3.1", "minimatch": "^9.0.0", "p-limit": "^3.1.0", "resolve-from": "^5.0.0", "semver": "^7.6.0" }, "bin": { "fingerprint": "bin/cli.js" } }, "sha512-MgZ5uIvvwAnjWeQoj4D3RnBXjD1GNOpCvhp2jtZWdQ8yEokhDEJGoHjsMT8/NCB5m2fqP5sv2V5nPzC7CN1YjQ=="],
"@expo/fingerprint": ["@expo/fingerprint@0.13.4", "", { "dependencies": { "@expo/spawn-async": "^1.7.2", "arg": "^5.0.2", "chalk": "^4.1.2", "debug": "^4.3.4", "find-up": "^5.0.0", "getenv": "^2.0.0", "glob": "^10.4.2", "ignore": "^5.3.1", "minimatch": "^9.0.0", "p-limit": "^3.1.0", "resolve-from": "^5.0.0", "semver": "^7.6.0" }, "bin": { "fingerprint": "bin/cli.js" } }, "sha512-MYfPYBTMfrrNr07DALuLhG6EaLVNVrY/PXjEzsjWdWE4ZFn0yqI0IdHNkJG7t1gePT8iztHc7qnsx+oo/rDo6w=="],
"@expo/image-utils": ["@expo/image-utils@0.7.4", "", { "dependencies": { "@expo/spawn-async": "^1.7.2", "chalk": "^4.0.0", "getenv": "^1.0.0", "jimp-compact": "0.16.1", "parse-png": "^2.1.0", "resolve-from": "^5.0.0", "semver": "^7.6.0", "temp-dir": "~2.0.0", "unique-string": "~2.0.0" } }, "sha512-LcZ82EJy/t/a1avwIboeZbO6hlw8CvsIRh2k6SWPcAOvW0RqynyKFzUJsvnjWlhUzfBEn4oI7y/Pu5Xkw3KkkA=="],
"@expo/image-utils": ["@expo/image-utils@0.7.6", "", { "dependencies": { "@expo/spawn-async": "^1.7.2", "chalk": "^4.0.0", "getenv": "^2.0.0", "jimp-compact": "0.16.1", "parse-png": "^2.1.0", "resolve-from": "^5.0.0", "semver": "^7.6.0", "temp-dir": "~2.0.0", "unique-string": "~2.0.0" } }, "sha512-GKnMqC79+mo/1AFrmAcUcGfbsXXTRqOMNS1umebuevl3aaw+ztsYEFEiuNhHZW7PQ3Xs3URNT513ZxKhznDscw=="],
"@expo/json-file": ["@expo/json-file@9.1.4", "", { "dependencies": { "@babel/code-frame": "~7.10.4", "json5": "^2.2.3" } }, "sha512-7Bv86X27fPERGhw8aJEZvRcH9sk+9BenDnEmrI3ZpywKodYSBgc8lX9Y32faNVQ/p0YbDK9zdJ0BfAKNAOyi0A=="],
"@expo/json-file": ["@expo/json-file@9.1.5", "", { "dependencies": { "@babel/code-frame": "~7.10.4", "json5": "^2.2.3" } }, "sha512-prWBhLUlmcQtvN6Y7BpW2k9zXGd3ySa3R6rAguMJkp1z22nunLN64KYTUWfijFlprFoxm9r2VNnGkcbndAlgKA=="],
"@expo/metro-config": ["@expo/metro-config@0.20.15", "", { "dependencies": { "@babel/core": "^7.20.0", "@babel/generator": "^7.20.5", "@babel/parser": "^7.20.0", "@babel/types": "^7.20.0", "@expo/config": "~11.0.10", "@expo/env": "~1.0.5", "@expo/json-file": "~9.1.4", "@expo/spawn-async": "^1.7.2", "chalk": "^4.1.0", "debug": "^4.3.2", "dotenv": "~16.4.5", "dotenv-expand": "~11.0.6", "getenv": "^2.0.0", "glob": "^10.4.2", "jsc-safe-url": "^0.2.4", "lightningcss": "~1.27.0", "minimatch": "^9.0.0", "postcss": "~8.4.32", "resolve-from": "^5.0.0" } }, "sha512-m8i58IQ7I8iOdVRfOhFmhPMHuhgeTVfQp1+mxW7URqPZaeVbuDVktPqOiNoHraKBoGPLKMUSsD+qdUuJVL3wMg=="],
"@expo/metro-config": ["@expo/metro-config@0.20.17", "", { "dependencies": { "@babel/core": "^7.20.0", "@babel/generator": "^7.20.5", "@babel/parser": "^7.20.0", "@babel/types": "^7.20.0", "@expo/config": "~11.0.12", "@expo/env": "~1.0.7", "@expo/json-file": "~9.1.5", "@expo/spawn-async": "^1.7.2", "chalk": "^4.1.0", "debug": "^4.3.2", "dotenv": "~16.4.5", "dotenv-expand": "~11.0.6", "getenv": "^2.0.0", "glob": "^10.4.2", "jsc-safe-url": "^0.2.4", "lightningcss": "~1.27.0", "minimatch": "^9.0.0", "postcss": "~8.4.32", "resolve-from": "^5.0.0" } }, "sha512-lpntF2UZn5bTwrPK6guUv00Xv3X9mkN3YYla+IhEHiYXWyG7WKOtDU0U4KR8h3ubkZ6SPH3snDyRyAzMsWtZFA=="],
"@expo/metro-runtime": ["@expo/metro-runtime@5.0.4", "", { "peerDependencies": { "react-native": "*" } }, "sha512-r694MeO+7Vi8IwOsDIDzH/Q5RPMt1kUDYbiTJwnO15nIqiDwlE8HU55UlRhffKZy6s5FmxQsZ8HA+T8DqUW8cQ=="],
"@expo/osascript": ["@expo/osascript@2.2.4", "", { "dependencies": { "@expo/spawn-async": "^1.7.2", "exec-async": "^2.2.0" } }, "sha512-Q+Oyj+1pdRiHHpev9YjqfMZzByFH8UhKvSszxa0acTveijjDhQgWrq4e9T/cchBHi0GWZpGczWyiyJkk1wM1dg=="],
"@expo/osascript": ["@expo/osascript@2.2.5", "", { "dependencies": { "@expo/spawn-async": "^1.7.2", "exec-async": "^2.2.0" } }, "sha512-Bpp/n5rZ0UmpBOnl7Li3LtM7la0AR3H9NNesqL+ytW5UiqV/TbonYW3rDZY38u4u/lG7TnYflVIVQPD+iqZJ5w=="],
"@expo/package-manager": ["@expo/package-manager@1.8.4", "", { "dependencies": { "@expo/json-file": "^9.1.4", "@expo/spawn-async": "^1.7.2", "chalk": "^4.0.0", "npm-package-arg": "^11.0.0", "ora": "^3.4.0", "resolve-workspace-root": "^2.0.0" } }, "sha512-8H8tLga/NS3iS7QaX/NneRPqbObnHvVCfMCo0ShudreOFmvmgqhYjRlkZTRstSyFqefai8ONaT4VmnLHneRYYg=="],
"@expo/package-manager": ["@expo/package-manager@1.8.6", "", { "dependencies": { "@expo/json-file": "^9.1.5", "@expo/spawn-async": "^1.7.2", "chalk": "^4.0.0", "npm-package-arg": "^11.0.0", "ora": "^3.4.0", "resolve-workspace-root": "^2.0.0" } }, "sha512-gcdICLuL+nHKZagPIDC5tX8UoDDB8vNA5/+SaQEqz8D+T2C4KrEJc2Vi1gPAlDnKif834QS6YluHWyxjk0yZlQ=="],
"@expo/plist": ["@expo/plist@0.3.4", "", { "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.2.3", "xmlbuilder": "^15.1.1" } }, "sha512-MhBLaUJNe9FQDDU2xhSNS4SAolr6K2wuyi4+A79vYuXLkAoICsbTwcGEQJN5jPY6D9izO/jsXh5k0h+mIWQMdw=="],
"@expo/plist": ["@expo/plist@0.3.5", "", { "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.2.3", "xmlbuilder": "^15.1.1" } }, "sha512-9RYVU1iGyCJ7vWfg3e7c/NVyMFs8wbl+dMWZphtFtsqyN9zppGREU3ctlD3i8KUE0sCUTVnLjCWr+VeUIDep2g=="],
"@expo/prebuild-config": ["@expo/prebuild-config@9.0.7", "", { "dependencies": { "@expo/config": "~11.0.10", "@expo/config-plugins": "~10.0.3", "@expo/config-types": "^53.0.4", "@expo/image-utils": "^0.7.4", "@expo/json-file": "^9.1.4", "@react-native/normalize-colors": "0.79.4", "debug": "^4.3.1", "resolve-from": "^5.0.0", "semver": "^7.6.0", "xml2js": "0.6.0" } }, "sha512-1w5MBp6NdF51gPGp0HsCZt0QC82hZWo37wI9HfxhdQF/sN/92Mh4t30vaY7gjHe71T5QNyab00oxZH/wP0MDgQ=="],
"@expo/prebuild-config": ["@expo/prebuild-config@9.0.10", "", { "dependencies": { "@expo/config": "~11.0.12", "@expo/config-plugins": "~10.1.1", "@expo/config-types": "^53.0.5", "@expo/image-utils": "^0.7.6", "@expo/json-file": "^9.1.5", "@react-native/normalize-colors": "0.79.5", "debug": "^4.3.1", "resolve-from": "^5.0.0", "semver": "^7.6.0", "xml2js": "0.6.0" } }, "sha512-lpzuel+Qb3QvGV0mnFOfeiyTq8pTGmnoGIX7p/enEgwjaCOUMSfOkbZkn6QJNAHOgNE1z5PAqzO1EBQPj2jrfA=="],
"@expo/sdk-runtime-versions": ["@expo/sdk-runtime-versions@1.0.0", "", {}, "sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ=="],
@@ -364,32 +373,36 @@
"@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w=="],
"@react-native/assets-registry": ["@react-native/assets-registry@0.79.4", "", {}, "sha512-7PjHNRtYlc36B7P1PHme8ZV0ZJ/xsA/LvMoXe6EX++t7tSPJ8iYCMBryZhcdnztgce73b94Hfx6TTGbLF+xtUg=="],
"@react-native-community/datetimepicker": ["@react-native-community/datetimepicker@8.4.1", "", { "dependencies": { "invariant": "^2.2.4" }, "peerDependencies": { "expo": ">=52.0.0", "react": "*", "react-native": "*", "react-native-windows": "*" }, "optionalPeers": ["expo", "react-native-windows"] }, "sha512-DrK+CUS5fZnz8dhzBezirkzQTcNDdaXer3oDLh0z4nc2tbdIdnzwvXCvi8IEOIvleoc9L95xS5tKUl0/Xv71Mg=="],
"@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.79.4", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@react-native/codegen": "0.79.4" } }, "sha512-quhytIlDedR3ircRwifa22CaWVUVnkxccrrgztroCZaemSJM+HLurKJrjKWm0J5jV9ed+d+9Qyb1YB0syTHDjg=="],
"@react-native/assets-registry": ["@react-native/assets-registry@0.79.5", "", {}, "sha512-N4Kt1cKxO5zgM/BLiyzuuDNquZPiIgfktEQ6TqJ/4nKA8zr4e8KJgU6Tb2eleihDO4E24HmkvGc73naybKRz/w=="],
"@react-native/babel-preset": ["@react-native/babel-preset@0.79.4", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-transform-arrow-functions": "^7.24.7", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-computed-properties": "^7.24.7", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", "@babel/plugin-transform-function-name": "^7.25.1", "@babel/plugin-transform-literals": "^7.25.2", "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-numeric-separator": "^7.24.7", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", "@babel/plugin-transform-react-jsx": "^7.25.2", "@babel/plugin-transform-react-jsx-self": "^7.24.7", "@babel/plugin-transform-react-jsx-source": "^7.24.7", "@babel/plugin-transform-regenerator": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-shorthand-properties": "^7.24.7", "@babel/plugin-transform-spread": "^7.24.7", "@babel/plugin-transform-sticky-regex": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/template": "^7.25.0", "@react-native/babel-plugin-codegen": "0.79.4", "babel-plugin-syntax-hermes-parser": "0.25.1", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" }, "peerDependencies": { "@babel/core": "*" } }, "sha512-El9JvYKiNfnkQ3qR7zJvvRdP3DX2i4BGYlIricWQishI3gWAfm88FQYFC2CcGoMQWJQEPN4jnDMpoISAJDEN4g=="],
"@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.79.5", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@react-native/codegen": "0.79.5" } }, "sha512-Rt/imdfqXihD/sn0xnV4flxxb1aLLjPtMF1QleQjEhJsTUPpH4TFlfOpoCvsrXoDl4OIcB1k4FVM24Ez92zf5w=="],
"@react-native/codegen": ["@react-native/codegen@0.79.4", "", { "dependencies": { "glob": "^7.1.1", "hermes-parser": "0.25.1", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "yargs": "^17.6.2" }, "peerDependencies": { "@babel/core": "*" } }, "sha512-K0moZDTJtqZqSs+u9tnDPSxNsdxi5irq8Nu4mzzOYlJTVNGy5H9BiIDg/NeKGfjAdo43yTDoaPSbUCvVV8cgIw=="],
"@react-native/babel-preset": ["@react-native/babel-preset@0.79.5", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-transform-arrow-functions": "^7.24.7", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-computed-properties": "^7.24.7", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", "@babel/plugin-transform-function-name": "^7.25.1", "@babel/plugin-transform-literals": "^7.25.2", "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-numeric-separator": "^7.24.7", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", "@babel/plugin-transform-react-jsx": "^7.25.2", "@babel/plugin-transform-react-jsx-self": "^7.24.7", "@babel/plugin-transform-react-jsx-source": "^7.24.7", "@babel/plugin-transform-regenerator": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-shorthand-properties": "^7.24.7", "@babel/plugin-transform-spread": "^7.24.7", "@babel/plugin-transform-sticky-regex": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/template": "^7.25.0", "@react-native/babel-plugin-codegen": "0.79.5", "babel-plugin-syntax-hermes-parser": "0.25.1", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" } }, "sha512-GDUYIWslMLbdJHEgKNfrOzXk8EDKxKzbwmBXUugoiSlr6TyepVZsj3GZDLEFarOcTwH1EXXHJsixihk8DCRQDA=="],
"@react-native/community-cli-plugin": ["@react-native/community-cli-plugin@0.79.4", "", { "dependencies": { "@react-native/dev-middleware": "0.79.4", "chalk": "^4.0.0", "debug": "^2.2.0", "invariant": "^2.2.4", "metro": "^0.82.0", "metro-config": "^0.82.0", "metro-core": "^0.82.0", "semver": "^7.1.3" }, "peerDependencies": { "@react-native-community/cli": "*" }, "optionalPeers": ["@react-native-community/cli"] }, "sha512-lx1RXEJwU9Tcs2B2uiDZBa6yghU6m6STvwYqHbJlFZyNN1k3JRa9j0/CDu+0fCFacIn7rEfZpb4UWi5YhsHpQg=="],
"@react-native/codegen": ["@react-native/codegen@0.79.5", "", { "dependencies": { "glob": "^7.1.1", "hermes-parser": "0.25.1", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "yargs": "^17.6.2" }, "peerDependencies": { "@babel/core": "*" } }, "sha512-FO5U1R525A1IFpJjy+KVznEinAgcs3u7IbnbRJUG9IH/MBXi2lEU2LtN+JarJ81MCfW4V2p0pg6t/3RGHFRrlQ=="],
"@react-native/debugger-frontend": ["@react-native/debugger-frontend@0.79.4", "", {}, "sha512-Gg4LhxHIK86Bi2RiT1rbFAB6fuwANRsaZJ1sFZ1OZEMQEx6stEnzaIrmfgzcv4z0bTQdQ8lzCrpsz0qtdaD4eA=="],
"@react-native/community-cli-plugin": ["@react-native/community-cli-plugin@0.79.5", "", { "dependencies": { "@react-native/dev-middleware": "0.79.5", "chalk": "^4.0.0", "debug": "^2.2.0", "invariant": "^2.2.4", "metro": "^0.82.0", "metro-config": "^0.82.0", "metro-core": "^0.82.0", "semver": "^7.1.3" }, "peerDependencies": { "@react-native-community/cli": "*" }, "optionalPeers": ["@react-native-community/cli"] }, "sha512-ApLO1ARS8JnQglqS3JAHk0jrvB+zNW3dvNJyXPZPoygBpZVbf8sjvqeBiaEYpn8ETbFWddebC4HoQelDndnrrA=="],
"@react-native/dev-middleware": ["@react-native/dev-middleware@0.79.4", "", { "dependencies": { "@isaacs/ttlcache": "^1.4.1", "@react-native/debugger-frontend": "0.79.4", "chrome-launcher": "^0.15.2", "chromium-edge-launcher": "^0.2.0", "connect": "^3.6.5", "debug": "^2.2.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "open": "^7.0.3", "serve-static": "^1.16.2", "ws": "^6.2.3" } }, "sha512-OWRDNkgrFEo+OSC5QKfiiBmGXKoU8gmIABK8rj2PkgwisFQ/22p7MzE5b6oB2lxWaeJT7jBX5KVniNqO46VhHA=="],
"@react-native/debugger-frontend": ["@react-native/debugger-frontend@0.79.5", "", {}, "sha512-WQ49TRpCwhgUYo5/n+6GGykXmnumpOkl4Lr2l2o2buWU9qPOwoiBqJAtmWEXsAug4ciw3eLiVfthn5ufs0VB0A=="],
"@react-native/gradle-plugin": ["@react-native/gradle-plugin@0.79.4", "", {}, "sha512-Gv5ryy23k7Sib2xVgqw65GTryg9YTij6URcMul5cI7LRcW0Aa1/FPb26l388P4oeNGNdDoAkkS+CuCWNunRuWg=="],
"@react-native/dev-middleware": ["@react-native/dev-middleware@0.79.5", "", { "dependencies": { "@isaacs/ttlcache": "^1.4.1", "@react-native/debugger-frontend": "0.79.5", "chrome-launcher": "^0.15.2", "chromium-edge-launcher": "^0.2.0", "connect": "^3.6.5", "debug": "^2.2.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "open": "^7.0.3", "serve-static": "^1.16.2", "ws": "^6.2.3" } }, "sha512-U7r9M/SEktOCP/0uS6jXMHmYjj4ESfYCkNAenBjFjjsRWekiHE+U/vRMeO+fG9gq4UCcBAUISClkQCowlftYBw=="],
"@react-native/js-polyfills": ["@react-native/js-polyfills@0.79.4", "", {}, "sha512-VyKPo/l9zP4+oXpQHrJq4vNOtxF7F5IMdQmceNzTnRpybRvGGgO/9jYu9mdmdKRO2KpQEc5dB4W2rYhVKdGNKg=="],
"@react-native/gradle-plugin": ["@react-native/gradle-plugin@0.79.5", "", {}, "sha512-K3QhfFNKiWKF3HsCZCEoWwJPSMcPJQaeqOmzFP4RL8L3nkpgUwn74PfSCcKHxooVpS6bMvJFQOz7ggUZtNVT+A=="],
"@react-native/normalize-colors": ["@react-native/normalize-colors@0.79.4", "", {}, "sha512-247/8pHghbYY2wKjJpUsY6ZNbWcdUa5j5517LZMn6pXrbSSgWuj3JA4OYibNnocCHBaVrt+3R8XC3VEJqLlHFg=="],
"@react-native/js-polyfills": ["@react-native/js-polyfills@0.79.5", "", {}, "sha512-a2wsFlIhvd9ZqCD5KPRsbCQmbZi6KxhRN++jrqG0FUTEV5vY7MvjjUqDILwJd2ZBZsf7uiDuClCcKqA+EEdbvw=="],
"@react-native/virtualized-lists": ["@react-native/virtualized-lists@0.79.4", "", { "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" }, "peerDependencies": { "@types/react": "^19.0.0", "react": "*", "react-native": "*" } }, "sha512-0Mdcox6e5PTonuM1WIo3ks7MBAa3IDzj0pKnE5xAwSgQ0DJW2P5dYf+KjWmpkE+Yb0w41ZbtXPhKq+U2JJ6C/Q=="],
"@react-native/normalize-colors": ["@react-native/normalize-colors@0.79.5", "", {}, "sha512-nGXMNMclZgzLUxijQQ38Dm3IAEhgxuySAWQHnljFtfB0JdaMwpe0Ox9H7Tp2OgrEA+EMEv+Od9ElKlHwGKmmvQ=="],
"@react-native/virtualized-lists": ["@react-native/virtualized-lists@0.79.5", "", { "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" }, "peerDependencies": { "@types/react": "^19.0.0", "react": "*", "react-native": "*" }, "optionalPeers": ["@types/react"] }, "sha512-EUPM2rfGNO4cbI3olAbhPkIt3q7MapwCwAJBzUfWlZ/pu0PRNOnMQ1IvaXTf3TpeozXV52K1OdprLEI/kI5eUA=="],
"@react-navigation/bottom-tabs": ["@react-navigation/bottom-tabs@7.4.2", "", { "dependencies": { "@react-navigation/elements": "^2.5.2", "color": "^4.2.3" }, "peerDependencies": { "@react-navigation/native": "^7.1.14", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-jyBux5l3qqEucY5M/ZWxVvfA8TQu7DVl2gK+xB6iKqRUfLf7dSumyVxc7HemDwGFoz3Ug8dVZFvSMEs+mfrieQ=="],
"@react-navigation/core": ["@react-navigation/core@7.11.0", "", { "dependencies": { "@react-navigation/routers": "^7.4.1", "escape-string-regexp": "^4.0.0", "nanoid": "^3.3.11", "query-string": "^7.1.3", "react-is": "^19.1.0", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": ">= 18.2.0" } }, "sha512-LfYPtxsMjldJ80BBeedaDCN0LE81WU1NP4V9Ia3wSrCPTAXt11y6holaBUrmUMVQVqpEyPRQrjwrT1QkfGKquw=="],
"@react-navigation/drawer": ["@react-navigation/drawer@7.5.2", "", { "dependencies": { "@react-navigation/elements": "^2.5.2", "color": "^4.2.3", "react-native-drawer-layout": "^4.1.11", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "@react-navigation/native": "^7.1.14", "react": ">= 18.2.0", "react-native": "*", "react-native-gesture-handler": ">= 2.0.0", "react-native-reanimated": ">= 2.0.0", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-1FSo+DyJe2zlIcQBhHcayZCe2aAF2JF+rpn7HnVnTV2HaCEpgJ0/zgHIa9MZByKntp/aSexL9YkDFQhQj2+7ag=="],
"@react-navigation/elements": ["@react-navigation/elements@2.4.5", "", { "dependencies": { "color": "^4.2.3", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "@react-native-masked-view/masked-view": ">= 0.2.0", "@react-navigation/native": "^7.1.12", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" }, "optionalPeers": ["@react-native-masked-view/masked-view"] }, "sha512-rzoQQ07dZGA3h608imB1nAZ2rPw9vZ2xc2K36XSZoV/7IZRDxI4BCIj38Wc4saQaYhfJIoeVssK4+6IwhZBedg=="],
"@react-navigation/native": ["@react-navigation/native@7.1.12", "", { "dependencies": { "@react-navigation/core": "^7.11.0", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*" } }, "sha512-ezHzrZN+9SE4Co6/H8MgDWlBxfJbVc5xi8szRi2QW8eJlsZsAvgGqtKs4YECraV4Yr9zW8RCzNuUxYiQiPMtEQ=="],
@@ -418,6 +431,8 @@
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
"@types/geojson": ["@types/geojson@7946.0.16", "", {}, "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="],
"@types/graceful-fs": ["@types/graceful-fs@4.1.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ=="],
"@types/hammerjs": ["@types/hammerjs@2.0.46", "", {}, "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw=="],
@@ -586,7 +601,7 @@
"babel-preset-current-node-syntax": ["babel-preset-current-node-syntax@1.1.0", "", { "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw=="],
"babel-preset-expo": ["babel-preset-expo@13.2.1", "", { "dependencies": { "@babel/helper-module-imports": "^7.25.9", "@babel/plugin-proposal-decorators": "^7.12.9", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-transform-export-namespace-from": "^7.25.9", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/preset-react": "^7.22.15", "@babel/preset-typescript": "^7.23.0", "@react-native/babel-preset": "0.79.4", "babel-plugin-react-native-web": "~0.19.13", "babel-plugin-syntax-hermes-parser": "^0.25.1", "babel-plugin-transform-flow-enums": "^0.0.2", "debug": "^4.3.4", "react-refresh": "^0.14.2", "resolve-from": "^5.0.0" }, "peerDependencies": { "babel-plugin-react-compiler": "^19.0.0-beta-e993439-20250405" }, "optionalPeers": ["babel-plugin-react-compiler"] }, "sha512-Ol3w0uLJNQ5tDfCf4L+IDTDMgJkVMQHhvYqMxs18Ib0DcaBQIfE8mneSSk7FcuI6FS0phw/rZhoEquQh1/Q3wA=="],
"babel-preset-expo": ["babel-preset-expo@13.2.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.25.9", "@babel/plugin-proposal-decorators": "^7.12.9", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-transform-export-namespace-from": "^7.25.9", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/preset-react": "^7.22.15", "@babel/preset-typescript": "^7.23.0", "@react-native/babel-preset": "0.79.5", "babel-plugin-react-native-web": "~0.19.13", "babel-plugin-syntax-hermes-parser": "^0.25.1", "babel-plugin-transform-flow-enums": "^0.0.2", "debug": "^4.3.4", "react-refresh": "^0.14.2", "resolve-from": "^5.0.0" }, "peerDependencies": { "babel-plugin-react-compiler": "^19.0.0-beta-e993439-20250405" }, "optionalPeers": ["babel-plugin-react-compiler"] }, "sha512-wQJn92lqj8GKR7Ojg/aW4+GkqI6ZdDNTDyOqhhl7A9bAqk6t0ukUOWLDXQb4p0qKJjMDV1F6gNWasI2KUbuVTQ=="],
"babel-preset-jest": ["babel-preset-jest@29.6.3", "", { "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA=="],
@@ -692,6 +707,8 @@
"data-view-byte-offset": ["data-view-byte-offset@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" } }, "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ=="],
"dayjs": ["dayjs@1.11.13", "", {}, "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="],
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
"decode-uri-component": ["decode-uri-component@0.2.2", "", {}, "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ=="],
@@ -802,39 +819,45 @@
"exec-async": ["exec-async@2.2.0", "", {}, "sha512-87OpwcEiMia/DeiKFzaQNBNFeN3XkkpYIh9FyOqq5mS2oKv3CBE67PXoEKcr6nodWdXNogTiQ0jE2NGuoffXPw=="],
"expo": ["expo@53.0.12", "", { "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "0.24.15", "@expo/config": "~11.0.10", "@expo/config-plugins": "~10.0.3", "@expo/fingerprint": "0.13.1", "@expo/metro-config": "0.20.15", "@expo/vector-icons": "^14.0.0", "babel-preset-expo": "~13.2.1", "expo-asset": "~11.1.5", "expo-constants": "~17.1.6", "expo-file-system": "~18.1.10", "expo-font": "~13.3.1", "expo-keep-awake": "~14.1.4", "expo-modules-autolinking": "2.1.12", "expo-modules-core": "2.4.0", "react-native-edge-to-edge": "1.6.0", "whatwg-url-without-unicode": "8.0.0-3" }, "peerDependencies": { "@expo/dom-webview": "*", "@expo/metro-runtime": "*", "react": "*", "react-native": "*", "react-native-webview": "*" }, "optionalPeers": ["@expo/dom-webview"], "bin": { "expo": "bin/cli", "expo-modules-autolinking": "bin/autolinking", "fingerprint": "bin/fingerprint" } }, "sha512-dtmED749hkxDWCcvtD++tb8bAm3Twv8qnUOXzVyXA5owNG0mwDIz0HveJTpWK1UzkY4HcTVRezDf0tflZJ+JXQ=="],
"expo": ["expo@53.0.17", "", { "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "0.24.18", "@expo/config": "~11.0.12", "@expo/config-plugins": "~10.1.1", "@expo/fingerprint": "0.13.4", "@expo/metro-config": "0.20.17", "@expo/vector-icons": "^14.0.0", "babel-preset-expo": "~13.2.3", "expo-asset": "~11.1.7", "expo-constants": "~17.1.7", "expo-file-system": "~18.1.11", "expo-font": "~13.3.2", "expo-keep-awake": "~14.1.4", "expo-modules-autolinking": "2.1.13", "expo-modules-core": "2.4.2", "react-native-edge-to-edge": "1.6.0", "whatwg-url-without-unicode": "8.0.0-3" }, "peerDependencies": { "@expo/dom-webview": "*", "@expo/metro-runtime": "*", "react": "*", "react-native": "*", "react-native-webview": "*" }, "optionalPeers": ["@expo/dom-webview", "@expo/metro-runtime", "react-native-webview"], "bin": { "expo": "bin/cli", "fingerprint": "bin/fingerprint", "expo-modules-autolinking": "bin/autolinking" } }, "sha512-I1z4X/BoirQeWH8VMcfW1N3OsKCY0LslGjhzDsBWomv4rzviLkcm7KNJBeYWddY7wGo0bljT8S57NGsIJS/f9g=="],
"expo-asset": ["expo-asset@11.1.5", "", { "dependencies": { "@expo/image-utils": "^0.7.4", "expo-constants": "~17.1.5" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-GEQDCqC25uDBoXHEnXeBuwpeXvI+3fRGvtzwwt0ZKKzWaN+TgeF8H7c76p3Zi4DfBMFDcduM0CmOvJX+yCCLUQ=="],
"expo-asset": ["expo-asset@11.1.7", "", { "dependencies": { "@expo/image-utils": "^0.7.6", "expo-constants": "~17.1.7" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-b5P8GpjUh08fRCf6m5XPVAh7ra42cQrHBIMgH2UXP+xsj4Wufl6pLy6jRF5w6U7DranUMbsXm8TOyq4EHy7ADg=="],
"expo-blur": ["expo-blur@14.1.5", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-CCLJHxN4eoAl06ESKT3CbMasJ98WsjF9ZQEJnuxtDb9ffrYbZ+g9ru84fukjNUOTtc8A8yXE5z8NgY1l0OMrmQ=="],
"expo-constants": ["expo-constants@17.1.6", "", { "dependencies": { "@expo/config": "~11.0.9", "@expo/env": "~1.0.5" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-q5mLvJiLtPcaZ7t2diSOlQ2AyxIO8YMVEJsEfI/ExkGj15JrflNQ7CALEW6IF/uNae/76qI/XcjEuuAyjdaCNw=="],
"expo-camera": ["expo-camera@16.1.10", "", { "dependencies": { "invariant": "^2.2.4" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native-web"] }, "sha512-qoRJeSwPmMbuu0VfnQTC+q79Kt2SqTWColEImgithL9u0qUQcC55U89IfhZk55Hpt6f1DgKuDzUOG5oY+snSWg=="],
"expo-file-system": ["expo-file-system@18.1.10", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-SyaWg+HitScLuyEeSG9gMSDT0hIxbM9jiZjSBP9l9zMnwZjmQwsusE6+7qGiddxJzdOhTP4YGUfvEzeeS0YL3Q=="],
"expo-constants": ["expo-constants@17.1.7", "", { "dependencies": { "@expo/config": "~11.0.12", "@expo/env": "~1.0.7" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-byBjGsJ6T6FrLlhOBxw4EaiMXrZEn/MlUYIj/JAd+FS7ll5X/S4qVRbIimSJtdW47hXMq0zxPfJX6njtA56hHA=="],
"expo-font": ["expo-font@13.3.1", "", { "dependencies": { "fontfaceobserver": "^2.1.0" }, "peerDependencies": { "expo": "*", "react": "*" } }, "sha512-d+xrHYvSM9WB42wj8vP9OOFWyxed5R1evphfDb6zYBmC1dA9Hf89FpT7TNFtj2Bk3clTnpmVqQTCYbbA2P3CLg=="],
"expo-file-system": ["expo-file-system@18.1.11", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-HJw/m0nVOKeqeRjPjGdvm+zBi5/NxcdPf8M8P3G2JFvH5Z8vBWqVDic2O58jnT1OFEy0XXzoH9UqFu7cHg9DTQ=="],
"expo-font": ["expo-font@13.3.2", "", { "dependencies": { "fontfaceobserver": "^2.1.0" }, "peerDependencies": { "expo": "*", "react": "*" } }, "sha512-wUlMdpqURmQ/CNKK/+BIHkDA5nGjMqNlYmW0pJFXY/KE/OG80Qcavdu2sHsL4efAIiNGvYdBS10WztuQYU4X0A=="],
"expo-haptics": ["expo-haptics@14.1.4", "", { "peerDependencies": { "expo": "*" } }, "sha512-QZdE3NMX74rTuIl82I+n12XGwpDWKb8zfs5EpwsnGi/D/n7O2Jd4tO5ivH+muEG/OCJOMq5aeaVDqqaQOhTkcA=="],
"expo-image": ["expo-image@2.3.0", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*", "react-native-web": "*" } }, "sha512-muL8OSbgCskQJsyqenKPNULWXwRm5BY2ruS6WMo/EzFyI3iXI/0mXgb2J/NXUa8xCEYxSyoGkGZFyCBvGY1ofA=="],
"expo-image": ["expo-image@2.3.2", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native-web"] }, "sha512-TOp7UR1mzeCxzs3c/6MV2Wy7jBfJpKq8aVC06gkLfxHsCVMeGqCXc+6GMrGIVrjG938LEub4dwnrE0OuSE2Qwg=="],
"expo-image-loader": ["expo-image-loader@5.1.0", "", { "peerDependencies": { "expo": "*" } }, "sha512-sEBx3zDQIODWbB5JwzE7ZL5FJD+DK3LVLWBVJy6VzsqIA6nDEnSFnsnWyCfCTSvbGigMATs1lgkC2nz3Jpve1Q=="],
"expo-image-picker": ["expo-image-picker@16.1.4", "", { "dependencies": { "expo-image-loader": "~5.1.0" }, "peerDependencies": { "expo": "*" } }, "sha512-bTmmxtw1AohUT+HxEBn2vYwdeOrj1CLpMXKjvi9FKSoSbpcarT4xxI0z7YyGwDGHbrJqyyic3I9TTdP2J2b4YA=="],
"expo-keep-awake": ["expo-keep-awake@14.1.4", "", { "peerDependencies": { "expo": "*", "react": "*" } }, "sha512-wU9qOnosy4+U4z/o4h8W9PjPvcFMfZXrlUoKTMBW7F4pLqhkkP/5G4EviPZixv4XWFMjn1ExQ5rV6BX8GwJsWA=="],
"expo-linking": ["expo-linking@7.1.5", "", { "dependencies": { "expo-constants": "~17.1.6", "invariant": "^2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-8g20zOpROW78bF+bLI4a3ZWj4ntLgM0rCewKycPL0jk9WGvBrBtFtwwADJgOiV1EurNp3lcquerXGlWS+SOQyA=="],
"expo-linking": ["expo-linking@7.1.7", "", { "dependencies": { "expo-constants": "~17.1.7", "invariant": "^2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-ZJaH1RIch2G/M3hx2QJdlrKbYFUTOjVVW4g39hfxrE5bPX9xhZUYXqxqQtzMNl1ylAevw9JkgEfWbBWddbZ3UA=="],
"expo-modules-autolinking": ["expo-modules-autolinking@2.1.12", "", { "dependencies": { "@expo/spawn-async": "^1.7.2", "chalk": "^4.1.0", "commander": "^7.2.0", "find-up": "^5.0.0", "glob": "^10.4.2", "require-from-string": "^2.0.2", "resolve-from": "^5.0.0" }, "bin": "bin/expo-modules-autolinking.js" }, "sha512-rW5YSW66pUx1nLqn7TO0eWRnP4LDvySW1Tom0wjexk3Tx/upg9LYE5tva7p5AX/cdFfiZcEqPcOxP4RyT++Xlg=="],
"expo-modules-autolinking": ["expo-modules-autolinking@2.1.13", "", { "dependencies": { "@expo/spawn-async": "^1.7.2", "chalk": "^4.1.0", "commander": "^7.2.0", "find-up": "^5.0.0", "glob": "^10.4.2", "require-from-string": "^2.0.2", "resolve-from": "^5.0.0" }, "bin": { "expo-modules-autolinking": "bin/expo-modules-autolinking.js" } }, "sha512-+SaiYkdP3LXOFm/26CHMHBof9Xq/0MHNDzL/K0OwPgHLhZ6wpDWIDYWRHNueWYtGpAeLw2XAhR0HX4FNtU09qw=="],
"expo-modules-core": ["expo-modules-core@2.4.0", "", { "dependencies": { "invariant": "^2.2.4" } }, "sha512-Ko5eHBdvuMykjw9P9C9PF54/wBSsGOxaOjx92I5BwgKvEmUwN3UrXFV4CXzlLVbLfSYUQaLcB220xmPfgvT7Fg=="],
"expo-modules-core": ["expo-modules-core@2.4.2", "", { "dependencies": { "invariant": "^2.2.4" } }, "sha512-RCb0wniYCJkxwpXrkiBA/WiNGxzYsEpL0sB50gTnS/zEfX3DImS2npc4lfZ3hPZo1UF9YC6OSI9Do+iacV0NUg=="],
"expo-router": ["expo-router@5.1.0", "", { "dependencies": { "@expo/metro-runtime": "5.0.4", "@expo/server": "^0.6.2", "@radix-ui/react-slot": "1.2.0", "@react-navigation/bottom-tabs": "^7.3.10", "@react-navigation/native": "^7.1.6", "@react-navigation/native-stack": "^7.3.10", "client-only": "^0.0.1", "invariant": "^2.2.4", "react-fast-compare": "^3.2.2", "react-native-is-edge-to-edge": "^1.1.6", "schema-utils": "^4.0.1", "semver": "~7.6.3", "server-only": "^0.0.1", "shallowequal": "^1.1.0" }, "peerDependencies": { "@react-navigation/drawer": "^7.3.9", "expo": "*", "expo-constants": "*", "expo-linking": "*", "react-native-reanimated": "*", "react-native-safe-area-context": "*", "react-native-screens": "*" }, "optionalPeers": ["@react-navigation/drawer"] }, "sha512-mnKpw35W6kKPpZm+ZxQei6HGUx2JO3znzqJZInzqrTZMgfAcHGgvP9AQFjg/Qi/Qy1CxunB9aQnqE9JPbSwbpw=="],
"expo-router": ["expo-router@5.1.3", "", { "dependencies": { "@expo/metro-runtime": "5.0.4", "@expo/server": "^0.6.3", "@radix-ui/react-slot": "1.2.0", "@react-navigation/bottom-tabs": "^7.3.10", "@react-navigation/native": "^7.1.6", "@react-navigation/native-stack": "^7.3.10", "client-only": "^0.0.1", "invariant": "^2.2.4", "react-fast-compare": "^3.2.2", "react-native-is-edge-to-edge": "^1.1.6", "schema-utils": "^4.0.1", "semver": "~7.6.3", "server-only": "^0.0.1", "shallowequal": "^1.1.0" }, "peerDependencies": { "@react-navigation/drawer": "^7.3.9", "expo": "*", "expo-constants": "*", "expo-linking": "*", "react-native-reanimated": "*", "react-native-safe-area-context": "*", "react-native-screens": "*" }, "optionalPeers": ["@react-navigation/drawer", "react-native-reanimated"] }, "sha512-zoAU0clwEj569PpGOzc06wCcxOskHLEyonJhLNPsweJgu+vE010d6XW+yr5ODR6F3ViFJpfcjbe7u3SaTjl24Q=="],
"expo-splash-screen": ["expo-splash-screen@0.30.9", "", { "dependencies": { "@expo/prebuild-config": "^9.0.6" }, "peerDependencies": { "expo": "*" } }, "sha512-curHUaZxUTZ2dWvz32ao3xPv5mJr1LBqn5V8xm/IULAehB9RGCn8iKiROMN1PYebSG+56vPMuJmBm9P+ayvJpA=="],
"expo-splash-screen": ["expo-splash-screen@0.30.10", "", { "dependencies": { "@expo/prebuild-config": "^9.0.10" }, "peerDependencies": { "expo": "*" } }, "sha512-Tt9va/sLENQDQYeOQ6cdLdGvTZ644KR3YG9aRlnpcs2/beYjOX1LHT510EGzVN9ljUTg+1ebEo5GGt2arYtPjw=="],
"expo-status-bar": ["expo-status-bar@2.2.3", "", { "dependencies": { "react-native-edge-to-edge": "1.6.0", "react-native-is-edge-to-edge": "^1.1.6" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-+c8R3AESBoduunxTJ8353SqKAKpxL6DvcD8VKBuh81zzJyUUbfB4CVjr1GufSJEKsMzNPXZU+HJwXx7Xh7lx8Q=="],
"expo-symbols": ["expo-symbols@0.4.5", "", { "dependencies": { "sf-symbols-typescript": "^2.0.0" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-ZbgvJfACPfWaJxJrUd0YzDmH9X0Ci7vb5m0/ZpDz/tnF1vQJlkovvpFEHLUmCDSLIN7/fNK8t696KSpzfm8/kg=="],
"expo-system-ui": ["expo-system-ui@5.0.9", "", { "dependencies": { "@react-native/normalize-colors": "0.79.4", "debug": "^4.3.2" }, "peerDependencies": { "expo": "*", "react-native": "*", "react-native-web": "*" } }, "sha512-behQ4uP384++U9GjgXmyEno1Z8raN1/tZv7ZJhYkQkRj1g2xXvNltXN/PDRcOm/wCNqStVhDpYo2OOQm5E5/lQ=="],
"expo-system-ui": ["expo-system-ui@5.0.10", "", { "dependencies": { "@react-native/normalize-colors": "0.79.5", "debug": "^4.3.2" }, "peerDependencies": { "expo": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native-web"] }, "sha512-BTXbSyJr80yuN6VO4XQKZj7BjesZQLHgOYZ0bWyf4VB19GFZq7ZnZOEc/eoKk1B3eIocOMKUfNCrg/Wn8Kfcuw=="],
"expo-web-browser": ["expo-web-browser@14.2.0", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-6S51d8pVlDRDsgGAp8BPpwnxtyKiMWEFdezNz+5jVIyT+ctReW42uxnjRgtsdn5sXaqzhaX+Tzk/CWaKCyC0hw=="],
@@ -1348,10 +1371,12 @@
"react-is": ["react-is@19.1.0", "", {}, "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg=="],
"react-native": ["react-native@0.79.4", "", { "dependencies": { "@jest/create-cache-key-function": "^29.7.0", "@react-native/assets-registry": "0.79.4", "@react-native/codegen": "0.79.4", "@react-native/community-cli-plugin": "0.79.4", "@react-native/gradle-plugin": "0.79.4", "@react-native/js-polyfills": "0.79.4", "@react-native/normalize-colors": "0.79.4", "@react-native/virtualized-lists": "0.79.4", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", "babel-jest": "^29.7.0", "babel-plugin-syntax-hermes-parser": "0.25.1", "base64-js": "^1.5.1", "chalk": "^4.0.0", "commander": "^12.0.0", "event-target-shim": "^5.0.1", "flow-enums-runtime": "^0.0.6", "glob": "^7.1.1", "invariant": "^2.2.4", "jest-environment-node": "^29.7.0", "memoize-one": "^5.0.0", "metro-runtime": "^0.82.0", "metro-source-map": "^0.82.0", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", "promise": "^8.3.0", "react-devtools-core": "^6.1.1", "react-refresh": "^0.14.0", "regenerator-runtime": "^0.13.2", "scheduler": "0.25.0", "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0", "ws": "^6.2.3", "yargs": "^17.6.2" }, "peerDependencies": { "@types/react": "^19.0.0", "react": "^19.0.0" }, "bin": "cli.js" }, "sha512-CfxYMuszvnO/33Q5rB//7cU1u9P8rSOvzhE2053Phdb8+6bof9NLayCllU2nmPrm8n9o6RU1Fz5H0yquLQ0DAw=="],
"react-native": ["react-native@0.79.5", "", { "dependencies": { "@jest/create-cache-key-function": "^29.7.0", "@react-native/assets-registry": "0.79.5", "@react-native/codegen": "0.79.5", "@react-native/community-cli-plugin": "0.79.5", "@react-native/gradle-plugin": "0.79.5", "@react-native/js-polyfills": "0.79.5", "@react-native/normalize-colors": "0.79.5", "@react-native/virtualized-lists": "0.79.5", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", "babel-jest": "^29.7.0", "babel-plugin-syntax-hermes-parser": "0.25.1", "base64-js": "^1.5.1", "chalk": "^4.0.0", "commander": "^12.0.0", "event-target-shim": "^5.0.1", "flow-enums-runtime": "^0.0.6", "glob": "^7.1.1", "invariant": "^2.2.4", "jest-environment-node": "^29.7.0", "memoize-one": "^5.0.0", "metro-runtime": "^0.82.0", "metro-source-map": "^0.82.0", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", "promise": "^8.3.0", "react-devtools-core": "^6.1.1", "react-refresh": "^0.14.0", "regenerator-runtime": "^0.13.2", "scheduler": "0.25.0", "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0", "ws": "^6.2.3", "yargs": "^17.6.2" }, "peerDependencies": { "@types/react": "^19.0.0", "react": "^19.0.0" }, "optionalPeers": ["@types/react"], "bin": { "react-native": "cli.js" } }, "sha512-jVihwsE4mWEHZ9HkO1J2eUZSwHyDByZOqthwnGrVZCh6kTQBCm4v8dicsyDa6p0fpWNE5KicTcpX/XXl0ASJFg=="],
"react-native-country-codes-picker": ["react-native-country-codes-picker@2.3.5", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-dDQhd0bVvlmgb84NPhTOmTk5UVYPHtk3lqZI+BPb61H1rC2IDrTvPWENg6u1DMGliqWHQDBYpeH37zvxxQL71w=="],
"react-native-drawer-layout": ["react-native-drawer-layout@4.1.11", "", { "dependencies": { "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*", "react-native-gesture-handler": ">= 2.0.0", "react-native-reanimated": ">= 2.0.0" } }, "sha512-31gilubSKPLToy31/bb0hhgOOenHYJq4JC7g/JkIEqBqSWzoCgiOlccDHlBRG+MV37UtXZnJN2spj3VusdCd4A=="],
"react-native-edge-to-edge": ["react-native-edge-to-edge@1.6.0", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-2WCNdE3Qd6Fwg9+4BpbATUxCLcouF6YRY7K+J36KJ4l3y+tWN6XCqAC4DuoGblAAbb2sLkhEDp4FOlbOIot2Og=="],
"react-native-gesture-handler": ["react-native-gesture-handler@2.24.0", "", { "dependencies": { "@egjs/hammerjs": "^2.0.17", "hoist-non-react-statics": "^3.3.0", "invariant": "^2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-ZdWyOd1C8axKJHIfYxjJKCcxjWEpUtUWgTOVY2wynbiveSQDm8X/PDyAKXSer/GOtIpjudUbACOndZXCN3vHsw=="],
@@ -1360,8 +1385,12 @@
"react-native-is-edge-to-edge": ["react-native-is-edge-to-edge@1.1.7", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-EH6i7E8epJGIcu7KpfXYXiV2JFIYITtq+rVS8uEb+92naMRBdxhTuS8Wn2Q7j9sqyO0B+Xbaaf9VdipIAmGW4w=="],
"react-native-maps": ["react-native-maps@1.20.1", "", { "dependencies": { "@types/geojson": "^7946.0.13" }, "peerDependencies": { "react": ">= 17.0.1", "react-native": ">= 0.64.3", "react-native-web": ">= 0.11" }, "optionalPeers": ["react-native-web"] }, "sha512-NZI3B5Z6kxAb8gzb2Wxzu/+P2SlFIg1waHGIpQmazDSCRkNoHNY4g96g+xS0QPSaG/9xRBbDNnd2f2/OW6t6LQ=="],
"react-native-otp-entry": ["react-native-otp-entry@1.8.5", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-TZNkIuUzZKAAWrC8X/A22ZHJdycLysxUNysrGf0yTmDLRUyf4zLXwVFcDYUcRNe763Hjaf5qvtKGILb6lDGzoA=="],
"react-native-paper": ["react-native-paper@5.14.5", "", { "dependencies": { "@callstack/react-theme-provider": "^3.0.9", "color": "^3.1.2", "use-latest-callback": "^0.2.3" }, "peerDependencies": { "react": "*", "react-native": "*", "react-native-safe-area-context": "*" } }, "sha512-eaIH5bUQjJ/mYm4AkI6caaiyc7BcHDwX6CqNDi6RIxfxfWxROsHpll1oBuwn/cFvknvA8uEAkqLk/vzVihI3AQ=="],
"react-native-reanimated": ["react-native-reanimated@3.17.5", "", { "dependencies": { "@babel/plugin-transform-arrow-functions": "^7.0.0-0", "@babel/plugin-transform-class-properties": "^7.0.0-0", "@babel/plugin-transform-classes": "^7.0.0-0", "@babel/plugin-transform-nullish-coalescing-operator": "^7.0.0-0", "@babel/plugin-transform-optional-chaining": "^7.0.0-0", "@babel/plugin-transform-shorthand-properties": "^7.0.0-0", "@babel/plugin-transform-template-literals": "^7.0.0-0", "@babel/plugin-transform-unicode-regex": "^7.0.0-0", "@babel/preset-typescript": "^7.16.7", "convert-source-map": "^2.0.0", "invariant": "^2.2.4", "react-native-is-edge-to-edge": "1.1.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0", "react": "*", "react-native": "*" } }, "sha512-SxBK7wQfJ4UoWoJqQnmIC7ZjuNgVb9rcY5Xc67upXAFKftWg0rnkknTw6vgwnjRcvYThrjzUVti66XoZdDJGtw=="],
"react-native-safe-area-context": ["react-native-safe-area-context@5.4.0", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-JaEThVyJcLhA+vU0NU8bZ0a1ih6GiF4faZ+ArZLqpYbL6j7R3caRqj+mE3lEtKCuHgwjLg3bCxLL1GPUJZVqUA=="],
@@ -1694,6 +1723,8 @@
"@babel/traverse--for-generate-function-map/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
"@callstack/react-theme-provider/deepmerge": ["deepmerge@3.3.0", "", {}, "sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA=="],
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
@@ -1712,8 +1743,6 @@
"@expo/config/@babel/code-frame": ["@babel/code-frame@7.10.4", "", { "dependencies": { "@babel/highlight": "^7.10.4" } }, "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg=="],
"@expo/config/getenv": ["getenv@1.0.0", "", {}, "sha512-7yetJWqbS9sbn0vIfliPsFgoXMKn/YMF+Wuiog97x+urnSRRRZ7xB+uVkwGKzRgq9CDFfMQnE9ruL5DHv9c6Xg=="],
"@expo/config/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": "dist/esm/bin.mjs" }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
"@expo/config/semver": ["semver@7.7.2", "", { "bin": "bin/semver.js" }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
@@ -1726,16 +1755,12 @@
"@expo/devcert/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": "dist/esm/bin.mjs" }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
"@expo/env/getenv": ["getenv@1.0.0", "", {}, "sha512-7yetJWqbS9sbn0vIfliPsFgoXMKn/YMF+Wuiog97x+urnSRRRZ7xB+uVkwGKzRgq9CDFfMQnE9ruL5DHv9c6Xg=="],
"@expo/fingerprint/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": "dist/esm/bin.mjs" }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
"@expo/fingerprint/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"@expo/fingerprint/semver": ["semver@7.7.2", "", { "bin": "bin/semver.js" }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"@expo/image-utils/getenv": ["getenv@1.0.0", "", {}, "sha512-7yetJWqbS9sbn0vIfliPsFgoXMKn/YMF+Wuiog97x+urnSRRRZ7xB+uVkwGKzRgq9CDFfMQnE9ruL5DHv9c6Xg=="],
"@expo/image-utils/semver": ["semver@7.7.2", "", { "bin": "bin/semver.js" }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"@expo/json-file/@babel/code-frame": ["@babel/code-frame@7.10.4", "", { "dependencies": { "@babel/highlight": "^7.10.4" } }, "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg=="],
@@ -1770,6 +1795,8 @@
"@react-navigation/bottom-tabs/@react-navigation/elements": ["@react-navigation/elements@2.5.2", "", { "dependencies": { "color": "^4.2.3", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@react-native-masked-view/masked-view": ">= 0.2.0", "@react-navigation/native": "^7.1.14", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" }, "optionalPeers": ["@react-native-masked-view/masked-view"] }, "sha512-aGC3ukF5+lXuiF5bK7bJyRuWCE+Tk4MZ3GoQpAb7u7+m0KmsquliDhj4UCWEUU5kUoCeoRAUvv+1lKcYKf+WTQ=="],
"@react-navigation/drawer/@react-navigation/elements": ["@react-navigation/elements@2.5.2", "", { "dependencies": { "color": "^4.2.3", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@react-native-masked-view/masked-view": ">= 0.2.0", "@react-navigation/native": "^7.1.14", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" }, "optionalPeers": ["@react-native-masked-view/masked-view"] }, "sha512-aGC3ukF5+lXuiF5bK7bJyRuWCE+Tk4MZ3GoQpAb7u7+m0KmsquliDhj4UCWEUU5kUoCeoRAUvv+1lKcYKf+WTQ=="],
"@react-navigation/native-stack/@react-navigation/elements": ["@react-navigation/elements@2.5.2", "", { "dependencies": { "color": "^4.2.3", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@react-native-masked-view/masked-view": ">= 0.2.0", "@react-navigation/native": "^7.1.14", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" }, "optionalPeers": ["@react-native-masked-view/masked-view"] }, "sha512-aGC3ukF5+lXuiF5bK7bJyRuWCE+Tk4MZ3GoQpAb7u7+m0KmsquliDhj4UCWEUU5kUoCeoRAUvv+1lKcYKf+WTQ=="],
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
@@ -1820,10 +1847,6 @@
"expo-modules-autolinking/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": "dist/esm/bin.mjs" }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
"expo-router/@react-navigation/bottom-tabs": ["@react-navigation/bottom-tabs@7.3.16", "", { "dependencies": { "@react-navigation/elements": "^2.4.5", "color": "^4.2.3" }, "peerDependencies": { "@react-navigation/native": "^7.1.12", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-BOVtrq5J3zV3T9CeQexX20JrjECWwIgUJj0Uj0DTVuZdvPIf1AHKHDQSWcG6XqDsbqFQu9yCAfRlnt0WwdRP2w=="],
"expo-router/@react-navigation/native-stack": ["@react-navigation/native-stack@7.3.17", "", { "dependencies": { "@react-navigation/elements": "^2.4.5", "warn-once": "^0.1.1" }, "peerDependencies": { "@react-navigation/native": "^7.1.12", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-nPJv5E/7MYZ5NPD0sFP9DjSawEQ1fYXe0sCZT1C5EHGWK08p3+5HkVScXofDDqUtI/q6UU23uE1YoxVWgRbDRw=="],
"expo-router/semver": ["semver@7.6.3", "", { "bin": "bin/semver.js" }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="],
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
@@ -1882,6 +1905,8 @@
"react-native/semver": ["semver@7.7.2", "", { "bin": "bin/semver.js" }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"react-native-paper/color": ["color@3.2.1", "", { "dependencies": { "color-convert": "^1.9.3", "color-string": "^1.6.0" } }, "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA=="],
"react-native-vector-icons/yargs": ["yargs@16.2.0", "", { "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw=="],
"react-native-web/@react-native/normalize-colors": ["@react-native/normalize-colors@0.74.89", "", {}, "sha512-qoMMXddVKVhZ8PA1AbUCk83trpd6N+1nF2A6k1i6LsQObyS92fELuk8kU/lQs6M7BsMHwqyLCpQJ1uFgNvIQXg=="],
@@ -2002,6 +2027,8 @@
"ora/chalk/supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="],
"react-native-paper/color/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
"react-native-vector-icons/yargs/cliui": ["cliui@7.0.4", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" } }, "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ=="],
"react-native-vector-icons/yargs/yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="],
@@ -2038,6 +2065,8 @@
"ora/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
"react-native-paper/color/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
"react-native-vector-icons/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"serve-static/send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],

View File

@@ -0,0 +1,127 @@
import { AccentColor, MainColor } from "@/constants/color-palet";
import { TEXT_SIZE_LARGE } from "@/constants/constans-value";
import React from "react";
import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
interface AlertCustomProps {
isVisible: boolean;
onLeftPress: () => void;
onRightPress: () => void;
title?: string;
message?: string;
textLeft?: string;
textRight?: string;
colorLeft?: string;
colorRight?: string;
}
export default function AlertCustom({
isVisible,
onLeftPress,
onRightPress,
title,
message,
textLeft,
textRight,
colorLeft,
colorRight,
}: AlertCustomProps) {
if (!isVisible) return null;
return (
<View style={styles.overlay}>
<View style={styles.alertBox}>
{title && message ? (
<>
<Text style={styles.alertTitle}>{title}</Text>
<Text style={styles.alertMessage}>{message}</Text>
</>
) : title ? (
<Text style={styles.alertTitle}>{title}</Text>
) : (
<Text style={styles.alertMessage}>{message}</Text>
)}
<View style={styles.alertButtons}>
<TouchableOpacity
style={[
styles.alertButton,
colorLeft ? { backgroundColor: colorLeft } : styles.leftButton,
]}
onPress={onLeftPress}
>
<Text style={styles.buttonText}>{textLeft}</Text>
</TouchableOpacity>
<TouchableOpacity
style={[
styles.alertButton,
colorRight ? { backgroundColor: colorRight } : styles.rightButton,
]}
onPress={onRightPress}
>
<Text style={styles.buttonText}>{textRight}</Text>
</TouchableOpacity>
</View>
</View>
</View>
);
}
const styles = StyleSheet.create({
overlay: {
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "rgba(0,0,0,0.5)",
justifyContent: "center",
alignItems: "center",
zIndex: 999,
},
alertBox: {
width: "90%",
backgroundColor: MainColor.darkblue,
borderColor: AccentColor.blue,
borderWidth: 1,
borderRadius: 10,
padding: 20,
alignItems: "center",
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
elevation: 5,
},
alertTitle: {
fontSize: TEXT_SIZE_LARGE,
fontWeight: "bold",
marginBottom: 20,
color: MainColor.white_gray,
},
alertMessage: {
textAlign: "center",
marginBottom: 20,
color: MainColor.white_gray,
},
alertButtons: {
flexDirection: "row",
justifyContent: "space-between",
width: "100%",
},
alertButton: {
flex: 1,
padding: 10,
borderRadius: 50,
marginHorizontal: 5,
alignItems: "center",
},
leftButton: {
backgroundColor: "gray",
},
rightButton: {
backgroundColor: MainColor.green,
},
buttonText: {
color: "white",
fontWeight: "bold",
},
});

View File

@@ -0,0 +1,64 @@
import { AccentColor } from "@/constants/color-palet";
import { PADDING_EXTRA_SMALL, PADDING_MEDIUM, PADDING_SMALL } from "@/constants/constans-value";
import { StyleProp, TouchableHighlight, View, ViewStyle } from "react-native";
interface BaseBoxProps {
children: React.ReactNode;
style?: StyleProp<ViewStyle>;
onPress?: () => void;
marginBottom?: number;
padding?: number;
paddingInline?: number;
paddingBlock?: number;
}
export default function BaseBox({
children,
style,
onPress,
marginBottom = PADDING_MEDIUM,
paddingBlock = PADDING_EXTRA_SMALL,
paddingInline = PADDING_SMALL,
}: BaseBoxProps) {
return (
<>
{onPress ? (
<TouchableHighlight
onPress={onPress}
style={[
{
backgroundColor: AccentColor.darkblue,
borderColor: AccentColor.blue,
borderWidth: 1,
borderRadius: 10,
marginBottom,
paddingBlock,
paddingInline,
},
style,
]}
// activeOpacity={0.7}
>
<View>{children}</View>
</TouchableHighlight>
) : (
<View
style={[
{
backgroundColor: AccentColor.darkblue,
borderColor: AccentColor.blue,
borderWidth: 1,
borderRadius: 10,
marginBottom,
paddingBlock,
paddingInline,
},
style,
]}
>
{children}
</View>
)}
</>
);
}

View File

@@ -0,0 +1,16 @@
import { GStyles } from "@/styles/global-styles";
import { StyleProp, View, ViewStyle } from "react-native";
export default function BoxButtonOnFooter({
children,
style,
}: {
children: React.ReactNode;
style?: StyleProp<ViewStyle>;
}) {
return (
<View style={GStyles.bottomBar}>
<View style={[GStyles.bottomBarContainer, style]}>{children}</View>
</View>
);
}

View File

@@ -0,0 +1,33 @@
import { MainColor } from "@/constants/color-palet";
import { Ionicons } from "@expo/vector-icons";
import Grid from "../Grid/GridCustom";
import TextCustom from "../Text/TextCustom";
import BaseBox from "./BaseBox";
export default function InformationBox({ text }: { text: string }) {
return (
<>
<BaseBox>
<Grid>
<Grid.Col
span={2}
style={{ alignItems: "center", justifyContent: "center" }}
>
<Ionicons
name="information-circle-outline"
size={24}
color={MainColor.white_gray}
/>
</Grid.Col>
<Grid.Col span={10} style={{ justifyContent: "center" }}>
<TextCustom>
{text
? text
: "Lorem ipsum dolor sit amet consectetur adipisicing elit."}
</TextCustom>
</Grid.Col>
</Grid>
</BaseBox>
</>
);
}

View File

@@ -0,0 +1,36 @@
import { MainColor } from "@/constants/color-palet";
import { Ionicons } from "@expo/vector-icons";
import { Href, router } from "expo-router";
/**
*
* @param path - path to navigate to ?
* @default router.back()
* @returns if path : router.replace(path) else router.back()
*/
const LeftButtonCustom = ({
path,
icon = "arrow-back",
iconCustom,
}: {
path?: Href;
icon?: React.ReactNode | any;
iconCustom?: React.ReactNode;
}) => {
return (
<>
{iconCustom ? (
iconCustom
) : (
<Ionicons
name={icon}
size={20}
color={MainColor.yellow}
onPress={() => (path ? router.replace(path) : router.back())}
/>
)}
</>
);
};
export default LeftButtonCustom;

View File

@@ -0,0 +1,29 @@
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { GStyles } from "@/styles/global-styles";
import { Feather } from "@expo/vector-icons";
import React from "react";
import ButtonCustom from "./ButtonCustom";
interface ButtonCenteredOnlyProps {
children?: React.ReactNode;
icon?: "plus" | "upload";
onPress: () => void;
}
export default function ButtonCenteredOnly({
onPress,
children,
icon = "plus"
}: ButtonCenteredOnlyProps) {
return (
<ButtonCustom
onPress={onPress}
iconLeft={
<Feather name={icon} size={ICON_SIZE_BUTTON} color={MainColor.black} />
}
style={[GStyles.buttonCentered50Percent]}
>
{children}
</ButtonCustom>
);
}

View File

@@ -1,57 +1,67 @@
// components/Button/Button.tsx
import React from "react";
import { Text, TouchableOpacity } from "react-native";
import buttonStyles from "./buttonStyles";
import { StyleProp, Text, TouchableOpacity, ViewStyle } from "react-native";
import { radiusMap } from "@/constants/radius-value";
import { MainColor } from "@/constants/color-palet";
import { stylesButton } from "./buttonCustomStyles";
import { Href, router } from "expo-router";
// Definisi props dengan TypeScript
// Import radiusMap
// Definisi type untuk radius
type RadiusType = keyof typeof radiusMap | number;
interface ButtonProps {
onPress: () => void;
children?: React.ReactNode;
href?: Href;
onPress?: () => void;
title?: string;
backgroundColor?: string;
textColor?: string;
radius?: number;
radius?: RadiusType; // ← bisa string enum atau number
disabled?: boolean;
iconLeft?: React.ReactNode;
style?: StyleProp<ViewStyle>;
}
/**
* Props untuk ButtonCustom
* @param onPress: () => void
* @param title?: string
* @param backgroundColor?: string
* @param textColor?: string
* @param radius?: number
* @param disabled?: boolean
* @param iconLeft?: React.ReactNode
* @example iconLeft={<Icon name="arrow-right" size={20} color={MainColor.black}/>
*/
const ButtonCustom: React.FC<ButtonProps> = ({
children,
href,
onPress,
title = "Button",
backgroundColor = "#007AFF",
textColor = "#FFFFFF",
radius = 8,
backgroundColor = MainColor.yellow,
textColor = MainColor.black,
radius = 50, // default md
disabled = false,
iconLeft,
style,
}) => {
const styles = buttonStyles({
backgroundColor,
textColor,
borderRadius: radius,
});
return (
<TouchableOpacity
style={[styles.button, disabled && styles.disabled]}
onPress={onPress}
style={[
stylesButton.button,
{ borderRadius: radius },
disabled
? [stylesButton.disabled, { backgroundColor: MainColor.disabled }]
: { backgroundColor },
style,
]}
onPress={() => {
if (href) {
router.push(href);
} else {
onPress?.();
}
}}
disabled={disabled}
activeOpacity={0.8}
>
{/* Render icon jika tersedia */}
{iconLeft && iconLeft}
<Text style={styles.buttonText}>{title}</Text>
<Text style={[stylesButton.buttonText, { color: textColor }]}>
{children || title}
</Text>
</TouchableOpacity>
);
};

View File

@@ -0,0 +1,45 @@
// components/FloatingButton.tsx
import { AccentColor, MainColor } from "@/constants/color-palet";
import React from "react";
import { StyleSheet, ViewStyle } from "react-native";
import { FAB } from "react-native-paper";
// Props untuk komponen
interface FloatingButtonProps {
onPress: () => void;
// label?: string;
icon?: string; // MaterialCommunityIcons
style?: ViewStyle;
}
const FloatingButton: React.FC<FloatingButtonProps> = ({
onPress,
// label = "Buat",
icon = "pencil-plus-outline",
style,
}) => {
return (
<FAB
style={[styles.fab, style]}
icon={icon}
color={MainColor.white}
// label={label}
onPress={onPress}
/>
);
};
const styles = StyleSheet.create({
fab: {
position: "absolute",
margin: 16,
right: 0,
bottom: 0,
backgroundColor: AccentColor.softblue, // Warna Twitter biru
borderRadius: 50,
borderColor: AccentColor.blue,
borderWidth: 1,
},
});
export default FloatingButton;

View File

@@ -1,31 +1,25 @@
// components/Button/buttonStyles.js
import { MainColor } from "@/constants/color-palet";
import { TEXT_SIZE_MEDIUM } from "@/constants/constans-value";
import { StyleSheet } from "react-native";
export default function buttonStyles({
backgroundColor = "#007AFF",
textColor = "#FFFFFF",
borderRadius = 8,
}) {
return StyleSheet.create({
export const stylesButton = StyleSheet.create({
button: {
backgroundColor,
paddingVertical: 12,
backgroundColor: MainColor.yellow,
paddingVertical: 10,
paddingHorizontal: 20,
borderRadius,
flexDirection: "row", // 👈 Tambahkan baris ini
alignItems: "center",
justifyContent: "center",
gap: 8,
},
buttonText: {
color: textColor,
fontSize: 16,
color: MainColor.black,
fontSize: TEXT_SIZE_MEDIUM,
fontWeight: "600",
},
disabled: {
backgroundColor: MainColor.disabled,
},
});
}

View File

View File

@@ -0,0 +1,51 @@
// Center.tsx
import React from "react";
import { View, StyleSheet, ViewStyle } from "react-native";
type JustifyContent =
| "flex-start"
| "flex-end"
| "center"
| "space-between"
| "space-around"
| "space-evenly";
type AlignItems = "flex-start" | "flex-end" | "center" | "stretch" | "baseline";
interface CenterProps {
children: React.ReactNode;
style?: ViewStyle;
direction?: "row" | "column";
justifyContent?: JustifyContent;
alignItems?: AlignItems;
}
const CenterCustom: React.FC<CenterProps> = ({
children,
style,
direction = "column",
justifyContent = "center",
alignItems = "center",
}) => {
return (
<View
style={[
styles.container,
{ flexDirection: direction },
{ justifyContent },
{ alignItems },
style,
]}
>
{children}
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
});
export default CenterCustom;

View File

@@ -0,0 +1,36 @@
import { StyleSheet, TouchableOpacity } from "react-native";
export default function ClickableCustom({
children,
onPress,
disabled,
style,
...props
}: {
children: React.ReactNode;
onPress: () => void;
disabled?: boolean;
style?: any;
[key: string]: any;
}) {
return (
<>
<TouchableOpacity
activeOpacity={0.7}
onPress={onPress}
disabled={disabled}
style={[styles.container, style]}
{...props}
>
{children}
</TouchableOpacity>
</>
);
}
const styles = StyleSheet.create({
container: {
width: "100%",
height: "auto",
},
});

View File

@@ -0,0 +1,205 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
// DateTimeInput.tsx
import { MainColor } from "@/constants/color-palet";
import { GStyles } from "@/styles/global-styles";
import { Ionicons } from "@expo/vector-icons";
import DateTimePicker, {
DateTimePickerEvent,
} from "@react-native-community/datetimepicker";
import React, { useCallback, useState } from "react";
import { Pressable, StyleProp, Text, View, ViewStyle } from "react-native";
import Grid from "../Grid/GridCustom";
import TextCustom from "../Text/TextCustom";
interface DateTimeInputProps {
// Main
value?: DateTimePickerEvent;
mode?: "date" | "time";
onChange: (selectedDate: DateTimePickerEvent) => void;
maximumDate?: Date;
minimumDate?: Date;
// Main
label?: string;
required?: boolean;
disabled?: boolean;
iconLeft?: React.ReactNode;
style?: StyleProp<ViewStyle>;
borderRadius?: number;
externalError?: string;
internalError?: string;
containerStyle?: StyleProp<ViewStyle>;
}
const DateTimeInput_Android: React.FC<DateTimeInputProps> = ({
// Main
value,
mode,
onChange,
maximumDate,
minimumDate,
// Main
label,
required,
disabled,
iconLeft,
style,
borderRadius = 8,
externalError,
internalError,
containerStyle,
}) => {
const [showDate, setShowDate] = useState(false);
const [showTime, setShowTime] = useState(false);
const [selectedDate, setSelectedDate] = useState<Date>(value as any);
const [selectedTime, setSelectedTime] = useState<Date>(value as any);
// Fungsi untuk menggabungkan tanggal dan waktu
const combineDateAndTime = useCallback((date: Date, time: Date): Date => {
const combined = new Date(date);
combined.setHours(
time.getHours(),
time.getMinutes(),
time.getSeconds(),
time.getMilliseconds()
);
return combined;
}, []);
// Handler untuk tanggal
const handleConfirmDate = (event: DateTimePickerEvent, date?: Date) => {
if (event.type === "set" && date) {
setSelectedDate(date);
if (selectedTime) {
const combined = combineDateAndTime(date, selectedTime);
onChange?.(combined as any);
}
}
setShowDate(false);
};
// Handler untuk waktu
const handleConfirmTime = (event: DateTimePickerEvent, time?: Date) => {
if (event.type === "set" && time) {
setSelectedTime(time);
if (selectedDate) {
const combined = combineDateAndTime(selectedDate, time);
onChange?.(combined as any);
}
}
setShowTime(false);
};
const toggleDatePicker = () => {
setShowDate(!showDate);
};
const toggleTimePicker = () => {
setShowTime(!showTime);
};
return (
<>
<View style={[GStyles.inputContainerArea, containerStyle]}>
{label && (
<Text style={GStyles.inputLabel}>
{label}
{required && <Text style={GStyles.inputRequired}> *</Text>}
</Text>
)}
<View
style={[
style,
{ borderRadius },
externalError || internalError ? GStyles.inputErrorBorder : null,
GStyles.inputContainerInput,
disabled && GStyles.disabledBox,
]}
>
<View style={GStyles.inputIcon}>
<Ionicons
name="calendar-outline"
size={20}
color={MainColor.placeholder}
/>
</View>
<Grid
containerStyle={{
borderRadius: 8,
justifyContent: "center",
alignItems: "center",
}}
>
<Grid.Col span={6} style={{}}>
<Pressable onPress={toggleDatePicker}>
<TextCustom color="gray">
{selectedDate ? (
<TextCustom color="black">
{selectedDate.toLocaleDateString()}
</TextCustom>
) : (
"Pilih tanggal"
)}
</TextCustom>
</Pressable>
</Grid.Col>
<Grid.Col span={1} style={{ alignItems: "center" }}>
<TextCustom color="gray">|</TextCustom>
</Grid.Col>
<Grid.Col span={5} style={{}}>
<Pressable onPress={toggleTimePicker}>
<TextCustom color="gray">
{selectedTime ? (
<TextCustom color="black">
{selectedTime.toLocaleTimeString("id-ID", {
minute: "2-digit",
hour: "2-digit",
})}
</TextCustom>
) : (
"Pilih waktu"
)}
</TextCustom>
</Pressable>
</Grid.Col>
</Grid>
</View>
{externalError ||
(internalError && (
<Text style={GStyles.inputErrorMessage}>
{externalError || internalError}
</Text>
))}
</View>
{showDate && (
<DateTimePicker
testID="dateTimePicker"
value={selectedDate || new Date()}
mode="date"
is24Hour={true}
display="default"
onChange={handleConfirmDate}
minimumDate={minimumDate}
maximumDate={maximumDate}
/>
)}
{showTime && (
<DateTimePicker
testID="dateTimePicker"
value={selectedTime || new Date()}
mode="time"
is24Hour={true}
display="default"
onChange={handleConfirmTime}
minimumDate={minimumDate}
maximumDate={maximumDate}
/>
)}
</>
);
};
export default DateTimeInput_Android;

View File

@@ -0,0 +1,161 @@
// DateTimeInput.tsx
import { MainColor } from "@/constants/color-palet";
import { GStyles } from "@/styles/global-styles";
import { Ionicons } from "@expo/vector-icons";
import DateTimePicker, {
DateTimePickerEvent,
} from "@react-native-community/datetimepicker";
import dayjs from "dayjs";
import React, { useState } from "react";
import {
StyleProp,
Text,
View,
ViewStyle
} from "react-native";
import ClickableCustom from "../Clickable/ClickableCustom";
import TextCustom from "../Text/TextCustom";
interface DateTimeInputProps {
// Main
value?: DateTimePickerEvent;
mode?: "date" | "time";
onChange: (selectedDate: DateTimePickerEvent) => void;
maximumDate?: Date;
minimumDate?: Date;
// Main
label?: string;
required?: boolean;
disabled?: boolean;
iconLeft?: React.ReactNode;
style?: StyleProp<ViewStyle>;
borderRadius?: number;
externalError?: string;
internalError?: string;
containerStyle?: StyleProp<ViewStyle>;
}
const DateTimeInput_IOS: React.FC<DateTimeInputProps> = ({
// Main
value,
mode,
onChange,
maximumDate,
minimumDate,
// Main
label,
required,
disabled,
iconLeft,
style,
borderRadius = 8,
externalError,
internalError,
containerStyle,
}) => {
const [show, setShow] = useState(false);
const [selectedDate, setSelectedDate] = useState<Date | undefined>(
value as any
);
const handleConfirm = (event: any, date?: Date) => {
if (event.type === "set" && date !== undefined) {
setSelectedDate(date);
onChange(date as any);
}
};
const handlePress = () => {
setShow(!show);
};
return (
<>
<ClickableCustom
activeOpacity={0.8}
style={[GStyles.inputContainerArea, containerStyle]}
onPress={handlePress}
>
{label && (
<Text style={GStyles.inputLabel}>
{label}
{required && <Text style={GStyles.inputRequired}> *</Text>}
</Text>
)}
<View
style={[
style,
{ borderRadius },
externalError || internalError ? GStyles.inputErrorBorder : null,
GStyles.inputContainerInput,
disabled && GStyles.disabledBox,
]}
>
<View style={GStyles.inputIcon}>
<Ionicons
name="calendar-outline"
size={20}
color={MainColor.placeholder}
/>
</View>
<TextCustom color="gray">
{selectedDate ? (
<TextCustom color="black">
{dayjs(selectedDate).format("DD-MM-YYYY HH:mm")}
</TextCustom>
) : (
"Pilih tanggal"
)}
</TextCustom>
</View>
{externalError ||
(internalError && (
<Text style={GStyles.inputErrorMessage}>
{externalError || internalError}
</Text>
))}
</ClickableCustom>
{show && (
<>
<View
style={{
position: "absolute",
zIndex: 15,
backgroundColor: "white",
borderRadius: 8,
padding: 10,
// top: 0,
bottom: 0,
left: 0,
right: 0,
borderColor: "#ccc",
borderWidth: 1,
}}
>
<View style={{ alignItems: "flex-end" }}>
<Ionicons
name="close"
size={20}
color="black"
onPress={() => setShow(false)}
/>
</View>
<DateTimePicker
value={selectedDate || new Date()}
mode={"datetime"}
display="inline"
onChange={handleConfirm}
minimumDate={minimumDate}
maximumDate={maximumDate}
/>
</View>
</>
)}
</>
);
};
export default DateTimeInput_IOS;

View File

@@ -0,0 +1,54 @@
import {
DateTimePickerEvent,
} from "@react-native-community/datetimepicker";
import React from "react";
import { Platform } from "react-native";
import DateTimeInput_Android from "./DataTimeAndroid";
import DateTimeInput_IOS from "./DateTimeIOS";
type Props = {
value?: Date;
onChange?: (date: Date) => void;
label?: string;
required?: boolean;
maximumDate?: Date;
minimumDate?: Date;
};
const DateTimePickerCustom: React.FC<Props> = ({
value,
onChange,
label,
required,
maximumDate,
minimumDate,
}) => {
return (
<>
{Platform.OS === "ios" ? (
<DateTimeInput_IOS
label={label}
onChange={(date: DateTimePickerEvent) => {
onChange?.(date as any);
}}
required={required}
maximumDate={maximumDate}
minimumDate={minimumDate}
/>
) : (
<DateTimeInput_Android
label={label}
onChange={(date: DateTimePickerEvent) => {
onChange?.(date as any);
}}
required={required}
maximumDate={maximumDate}
minimumDate={minimumDate}
/>
)}
</>
);
};
export default DateTimePickerCustom;

View File

@@ -0,0 +1,70 @@
import React, { useState } from "react";
import { Pressable, Text, StyleSheet } from "react-native";
import DateTimePicker, { Event } from "@react-native-community/datetimepicker";
type Props = {
value?: Date;
mode?: "date" | "time" | "datetime";
onChange: (date: Date) => void;
};
const DateTimePickerTry: React.FC<Props> = ({
value = new Date(),
mode = "date",
onChange,
}) => {
const [show, setShow] = useState(false);
const toggleDatePicker = () => {
setShow(!show);
};
const handleConfirm = (event: Event, selectedDate?: Date) => {
if (event.type === "set" && selectedDate !== undefined) {
onChange(selectedDate);
}
setShow(false);
};
return (
<>
<Pressable onPress={toggleDatePicker} style={styles.button}>
<Text style={styles.buttonText}>
{value ? value.toLocaleDateString() : "Pilih tanggal"}
</Text>
</Pressable>
{show && (
<DateTimePicker
// style={styles.button}
textColor="white"
testID="dateTimePicker"
value={value}
mode={mode}
is24Hour={true}
display="default"
onChange={handleConfirm as any}
/>
)}
</>
);
};
const styles = StyleSheet.create({
button: {
paddingVertical: 12,
paddingHorizontal: 20,
backgroundColor: "white",
borderRadius: 8,
alignItems: "center",
justifyContent: "center",
marginVertical: 10,
},
buttonText: {
color: "white",
fontSize: 16,
fontWeight: "bold",
},
});
export default DateTimePickerTry;

View File

@@ -0,0 +1,186 @@
import React from "react";
import {
StyleProp,
StyleSheet,
Text,
TextStyle,
View,
ViewStyle,
} from "react-native";
// Define types for props
type Orientation = "horizontal" | "vertical";
type HorizontalLabelPosition = "center" | "left" | "right";
type VerticalLabelPosition = "center" | "top" | "bottom";
type LabelPosition = HorizontalLabelPosition | VerticalLabelPosition;
type Size = number | "xs" | "sm" | "md" | "lg" | "xl";
interface DividerProps {
orientation?: Orientation;
color?: string;
size?: Size;
label?: React.ReactNode;
labelPosition?: LabelPosition;
labelStyle?: StyleProp<TextStyle>;
style?: StyleProp<ViewStyle>;
}
const DividerCustom: React.FC<DividerProps> = ({
orientation = "horizontal",
color = "#DADADA",
size = "xs",
label,
labelPosition = "center",
labelStyle,
style,
}) => {
const isHorizontal = orientation === "horizontal";
// Convert size to actual dimensions
const getSize = (): number => {
if (typeof size === "number") return size;
switch (size) {
case "xs":
return 1;
case "sm":
return 2;
case "md":
return 3;
case "lg":
return 4;
case "xl":
return 5;
default:
return 1; // Default size
}
};
const thickness = getSize();
const capitalize = (str: string): string =>
str.charAt(0).toUpperCase() + str.slice(1);
const renderLabel = () => {
if (!label) return null;
return (
<View
style={[
styles.labelContainer,
isHorizontal
? styles[
`label${capitalize(
labelPosition as string
)}` as keyof typeof styles
]
: styles[
`label${capitalize(
labelPosition as string
)}` as keyof typeof styles
],
]}
>
<Text style={[styles.labelText, labelStyle]}>{label}</Text>
</View>
);
};
return (
<View
style={[
isHorizontal ? styles.horizontalDivider : styles.verticalDivider,
{ backgroundColor: color },
style,
]}
>
{isHorizontal ? (
labelPosition !== "center" ? (
<View style={{ flex: 1, flexDirection: "row", alignItems: "center" }}>
{labelPosition === "left" && renderLabel()}
<View
style={{ flex: 1, backgroundColor: color, height: thickness }}
/>
{labelPosition === "right" && renderLabel()}
</View>
) : (
<>
{renderLabel()}
<View style={{ flex: 1 }} />
</>
)
) : labelPosition !== "center" && !isHorizontal ? (
<View
style={{ flex: 1, flexDirection: "column", justifyContent: "center" }}
>
{labelPosition === "top" && renderLabel()}
<View style={{ flex: 1, backgroundColor: color, width: thickness }} />
{labelPosition === "bottom" && renderLabel()}
</View>
) : (
<>
{renderLabel()}
<View style={{ flex: 1 }} />
</>
)}
</View>
);
};
const styles = StyleSheet.create({
horizontalDivider: {
flexDirection: "row",
alignItems: "center",
marginVertical: 8,
position: "relative",
},
verticalDivider: {
flexDirection: "column",
justifyContent: "center",
marginHorizontal: 8,
position: "relative",
},
labelText: {
fontSize: 14,
fontWeight: "500",
color: "#666",
marginHorizontal: 12,
marginVertical: 4,
backgroundColor: "white",
paddingHorizontal: 8,
},
labelContainer: {
position: "absolute",
left: 0,
right: 0,
justifyContent: "center",
alignItems: "center",
},
labelLeft: {
left: 12,
right: null,
alignItems: "flex-start",
},
labelRight: {
right: 12,
left: null,
alignItems: "flex-end",
},
labelCenter: {
justifyContent: "center",
alignItems: "center",
},
labelTop: {
top: 12,
bottom: null,
justifyContent: "flex-start",
},
labelBottom: {
bottom: 12,
top: null,
justifyContent: "flex-end",
},
});
export default DividerCustom;

View File

@@ -0,0 +1,183 @@
import React, { useEffect, useRef } from "react";
import {
Animated,
PanResponder,
StyleSheet,
View,
InteractionManager,
} from "react-native";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { DRAWER_HEIGHT } from "@/constants/constans-value";
interface DrawerCustomProps {
children?: React.ReactNode;
height?: number;
isVisible: boolean;
drawerAnim?: Animated.Value;
closeDrawer: () => void;
// openLogoutAlert: () => void;
}
/**
*
* @param drawerAnim
* @example const drawerAnim = useRef(new Animated.Value(DRAWER_HEIGHT)).current; // mulai di luar bawah layar
*/
export default function DrawerCustom({
children,
height,
isVisible,
drawerAnim,
closeDrawer,
}: // openLogoutAlert,
DrawerCustomProps) {
const drawerAnima = useRef(
new Animated.Value(height || DRAWER_HEIGHT)
).current;
// Efek untuk handle open/close drawer
useEffect(() => {
if (isVisible) {
Animated.timing(drawerAnima, {
toValue: 0,
duration: 300,
useNativeDriver: true,
}).start();
} else {
Animated.timing(drawerAnima, {
toValue: height || DRAWER_HEIGHT,
duration: 300,
useNativeDriver: true,
}).start();
}
}, [isVisible, drawerAnim, height, closeDrawer, drawerAnima]);
const panResponder = useRef(
PanResponder.create({
onMoveShouldSetPanResponder: (_, gestureState) => {
return gestureState.dy > 10; // gesek ke bawah
},
onPanResponderMove: (_, gestureState) => {
const offset = gestureState.dy;
if (offset >= 0 && offset <= DRAWER_HEIGHT) {
drawerAnima.setValue(offset);
}
},
onPanResponderRelease: (_, gestureState) => {
if (gestureState.dy > 200) {
InteractionManager.runAfterInteractions(() => {
closeDrawer();
});
} else {
Animated.spring(drawerAnima, {
toValue: 0,
useNativeDriver: true,
}).start();
}
},
})
).current;
if (!isVisible) return null;
return (
<>
{/* Overlay Gelap */}
<View
style={styles.overlay}
pointerEvents="auto"
onTouchStart={() => {
InteractionManager.runAfterInteractions(() => {
closeDrawer();
});
}}
/>
{/* Custom Bottom Drawer */}
<Animated.View
style={[
styles.drawer,
{
height: height || DRAWER_HEIGHT,
transform: [{ translateY: drawerAnima }],
},
]}
{...panResponder.panHandlers}
>
<View
style={[styles.headerBar, { backgroundColor: MainColor.white }]}
/>
{children}
{/* <TouchableOpacity
style={styles.menuItem}
onPress={() => {
alert("Pilihan 1 diklik");
closeDrawer();
}}
>
<Text>Menu Item 1</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.menuItem}
onPress={() => {
alert("Pilihan 2 diklik");
closeDrawer();
}}
>
<Text>Menu Item 2</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.menuItem}
onPress={() => alert("Logout via Alert bawaan")}
>
<Text style={{ color: "red" }}>Keluar</Text>
</TouchableOpacity> */}
</Animated.View>
</>
);
}
const styles = StyleSheet.create({
overlay: {
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "black",
opacity: 0.6,
zIndex: 998,
},
drawer: {
position: "absolute",
left: 0,
right: 0,
bottom: 0,
backgroundColor: AccentColor.darkblue,
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
padding: 20,
shadowColor: "#000",
shadowOffset: { width: 0, height: -2 },
shadowOpacity: 0.2,
elevation: 5,
zIndex: 999,
},
headerBar: {
width: 40,
height: 5,
backgroundColor: MainColor.white,
borderRadius: 5,
alignSelf: "center",
marginVertical: 10,
},
menuItem: {
padding: 15,
},
});

View File

@@ -0,0 +1,67 @@
import { AccentColor, MainColor } from "@/constants/color-palet";
import { TEXT_SIZE_SMALL } from "@/constants/constans-value";
import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
import { IMenuDrawerItem } from "../_Interface/types";
const MenuDrawerDynamicGrid = ({ data, columns = 3, onPressItem }: any) => {
const numColumns = columns;
return (
<View style={styles.container}>
{data.map((item: IMenuDrawerItem, index: any) => (
<TouchableOpacity
key={index}
style={[styles.itemContainer, { flexBasis: `${100 / numColumns}%` }]}
onPress={() => onPressItem?.(item)}
>
<View
style={[
styles.iconContainer,
{ backgroundColor: item.color || AccentColor.blue },
]}
>
{item.icon}
{/* <Ionicons
name={item.icon}
size={ICON_SIZE_MEDIUM}
color={item.color || MainColor.white_gray}
/> */}
</View>
<Text
style={[styles.label, { color: item.color || AccentColor.white }]}
>
{item.label}
</Text>
</TouchableOpacity>
))}
</View>
);
};
export default MenuDrawerDynamicGrid;
const styles = StyleSheet.create({
container: {
flexDirection: "row",
flexWrap: "wrap",
padding: 0,
},
itemContainer: {
padding: 10,
alignItems: "center",
},
iconContainer: {
width: 56,
height: 56,
borderRadius: 28,
backgroundColor: AccentColor.blue,
justifyContent: "center",
alignItems: "center",
},
label: {
marginTop: 10,
fontSize: TEXT_SIZE_SMALL,
textAlign: "center",
color: MainColor.white_gray,
},
});

View File

@@ -0,0 +1,114 @@
import React, { createContext, useContext, useMemo } from "react";
import { StyleSheet, useWindowDimensions, View, ViewStyle } from "react-native";
// Tipe untuk span
type SpanValue = {
base?: number;
md?: number;
lg?: number;
};
// Props untuk Grid.Col
interface ColProps {
children: React.ReactNode;
span?: number | SpanValue;
style?: ViewStyle;
}
// Props untuk Grid
interface GridProps {
children: React.ReactNode;
gap?: number;
columns?: number;
containerStyle?: ViewStyle;
}
// Context untuk menyimpan konfigurasi grid
type GridContextType = {
gap: number;
columns: number;
};
const GridContext = createContext<GridContextType>({
gap: 0,
columns: 12,
});
const useGrid = () => useContext(GridContext);
// Helper untuk menentukan span berdasarkan lebar layar
const getSpan = (
spanProp: number | SpanValue | undefined,
width: number
): number => {
if (typeof spanProp === "number") return spanProp;
const span = spanProp || { base: 12 };
if (width >= 992 && span.lg) return span.lg;
if (width >= 768 && span.md) return span.md;
return span.base ?? 12;
};
// Grid Component
const GridComponent: React.FC<GridProps> = ({
children,
gap = 6,
columns = 12,
containerStyle,
}) => {
const contextValue = useMemo(() => ({ gap, columns }), [gap, columns]);
return (
<GridContext.Provider value={contextValue}>
<View
style={[
styles.container,
{ marginHorizontal: -gap / 2 },
containerStyle,
]}
>
{React.Children.map(children, (child) =>
React.isValidElement(child)
? React.cloneElement(child as React.ReactElement<ColProps>, {})
: child
)}
</View>
</GridContext.Provider>
);
};
// Grid.Col Component
const Col: React.FC<ColProps> = ({ children, span, style }) => {
const { gap, columns } = useGrid();
const { width } = useWindowDimensions();
const colSpan = getSpan(span, width);
const margin = gap / 2;
const styles = StyleSheet.create({
col: {
flexBasis: `${(100 / columns) * colSpan}%`,
paddingVertical: margin,
// marginBottom: gap,
marginBlock: gap,
},
});
return <View style={[styles.col, style]}>{children}</View>;
};
// Export bersama-sama
const Grid = Object.assign(GridComponent, { Col });
export default Grid;
// Styles
const styles = StyleSheet.create({
container: {
flexDirection: "row",
flexWrap: "wrap",
justifyContent: "flex-start",
marginInline: 0.1
},
});

View File

@@ -0,0 +1,84 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { MainColor } from "@/constants/color-palet";
import DUMMY_IMAGE from "@/constants/dummy-image-value";
import { Href, router } from "expo-router";
import {
Image,
ImageSourcePropType,
StyleSheet,
TouchableOpacity,
} from "react-native";
type Size = "base" | "sm" | "md" | "lg" | "xl";
interface AvatarCustomProps {
source?: ImageSourcePropType;
size?: Size;
onPress?: () => void;
href?: Href | undefined;
}
const sizeMap = {
base: 40,
sm: 60,
md: 80,
lg: 100,
xl: 120,
};
export default function AvatarCustom({
source = DUMMY_IMAGE.avatar,
size = "base",
onPress,
href,
}: AvatarCustomProps) {
const dimension = sizeMap[size];
const ImageView = ({source}: {source: ImageSourcePropType}) => {
return (
<Image
source={source}
style={[
// styles.overlappingAvatar,
{
width: dimension,
height: dimension,
borderRadius: dimension / 2,
},
]}
resizeMode="cover"
/>
)
}
return (
<>
{onPress || href ? (
<TouchableOpacity
onPress={href ? () => router.navigate(href as any) : onPress}
>
<ImageView source={source} />
</TouchableOpacity>
) : (
<ImageView source={source} />
)}
</>
);
}
const styles = StyleSheet.create({
container: {
alignItems: "center",
justifyContent: "center",
},
overlappingAvatar: {
borderWidth: 2,
borderColor: "#fff",
backgroundColor: MainColor.white_gray,
// shadowColor: "#000",
// shadowOffset: { width: 0, height: 2 },
// shadowOpacity: 0.2,
shadowRadius: 3,
elevation: 3,
},
});

View File

@@ -0,0 +1,20 @@
import DUMMY_IMAGE from "@/constants/dummy-image-value";
import { Image } from "react-native";
import BaseBox from "../Box/BaseBox";
export default function LandscapeFrameUploaded() {
return (
<BaseBox
style={{
height: 250,
width: "100%",
}}
>
<Image
source={DUMMY_IMAGE.background}
resizeMode="cover"
style={{ width: "100%", height: "100%", borderRadius: 10 }}
/>
</BaseBox>
);
}

View File

@@ -0,0 +1,61 @@
// components/MapComponent.js
import React from "react";
import { DimensionValue, StyleSheet, View } from "react-native";
import MapView, { Marker } from "react-native-maps";
interface MapComponentProps {
latitude?: number;
longitude?: number;
latitudeDelta?: number;
longitudeDelta?: number;
height?: DimensionValue;
}
const MapCustom = ({
latitude = -8.737109,
longitude = 115.1756897,
latitudeDelta = 0.1,
longitudeDelta = 0.1,
height = 300,
}: MapComponentProps) => {
const initialRegion = {
latitude,
longitude, // Jakarta sebagai default lokasi
latitudeDelta,
longitudeDelta,
};
return (
<View style={styles.container}>
<MapView
style={[styles.map, { height }]}
initialRegion={initialRegion}
showsUserLocation={true}
loadingEnabled={true}
>
{/* Contoh marker */}
<Marker
coordinate={{
latitude,
longitude,
}}
title="Bali"
description="Badung, Bali, Indonesia"
/>
</MapView>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
borderRadius: 8,
},
map: {
width: "100%",
},
});
export default MapCustom;

View File

@@ -0,0 +1,132 @@
// components/Radio.tsx
import { GStyles } from '@/styles/global-styles';
import React, { createContext, useCallback, useContext } from 'react';
import {
StyleSheet,
Text,
TextStyle,
TouchableOpacity,
View,
ViewStyle
} from 'react-native';
// ========================
// Types
// ========================
type RadioContextType = {
value: string;
onChange: (value: string) => void;
};
const RadioContext = createContext<RadioContextType | undefined>(undefined);
interface RadioGroupProps {
value: string;
onChange: (value: string) => void;
children: React.ReactNode;
style?: ViewStyle;
}
interface RadioProps {
label: string;
value: string | number;
disabled?: boolean;
style?: ViewStyle;
labelStyle?: TextStyle;
}
// ========================
// Components
// ========================
export const RadioGroup: React.FC<RadioGroupProps> = ({ value, onChange, children, style }) => {
const contextValue = {
value,
onChange,
};
return <RadioContext.Provider value={contextValue}>{children}</RadioContext.Provider>;
};
export const RadioCustom: React.FC<RadioProps> = ({ label, value, disabled = false, style, labelStyle }) => {
const context = useContext(RadioContext);
if (!context) {
throw new Error('Radio must be used inside a RadioGroup');
}
const { value: selectedValue, onChange } = context;
const handlePress = useCallback(() => {
if (!disabled && selectedValue !== value) {
onChange(value as any);
}
}, [disabled, selectedValue, value, onChange]);
const isSelected = selectedValue === value;
return (
<TouchableOpacity
style={[styles.radioContainer, style]}
onPress={handlePress}
disabled={disabled}
>
{/* Circle */}
<View
style={[styles.outerCircle, isSelected && styles.outerCircleChecked]}
>
<View
style={[styles.innerCircle, isSelected && styles.innerCircleChecked]}
/>
</View>
{/* Label */}
<Text
style={[
GStyles.textLabelBold,
labelStyle,
disabled && GStyles.inputTextDisabled,
]}
>
{label}
</Text>
</TouchableOpacity>
);
};
// ========================
// Styles
// ========================
const styles = StyleSheet.create({
radioContainer: {
flexDirection: 'row',
alignItems: 'center',
marginVertical: 8,
},
outerCircle: {
height: 24,
width: 24,
borderRadius: 12,
borderWidth: 2,
borderColor: '#3B82F6',
justifyContent: 'center',
alignItems: 'center',
marginRight: 10,
},
outerCircleChecked: {
backgroundColor: '#3B82F6',
},
innerCircle: {
height: 12,
width: 12,
borderRadius: 6,
backgroundColor: 'transparent',
},
innerCircleChecked: {
backgroundColor: 'white',
borderColor: 'white',
borderRadius: 6,
},
});

View File

@@ -0,0 +1,60 @@
import { AccentColor, MainColor } from "@/constants/color-palet";
import React from "react";
import { ScrollView, StyleSheet } from "react-native";
import ButtonCustom from "../Button/ButtonCustom";
interface ButtonData {
id: string | number;
label: string;
}
interface ScrollableCustomProps {
data: ButtonData[];
onButtonPress: (item: ButtonData) => void;
activeId?: string | number;
}
const ScrollableCustom = ({
data,
onButtonPress,
activeId,
}: ScrollableCustomProps) => {
return (
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.buttonContainer}
style={styles.scrollView}
>
{data.map((item) => {
const isActive = activeId === item.id;
return (
<ButtonCustom
key={item.id}
backgroundColor={isActive ? MainColor.yellow : AccentColor.blue}
textColor={isActive ? MainColor.black : MainColor.white}
onPress={() => onButtonPress(item)}
>
{item.label}
</ButtonCustom>
);
})}
</ScrollView>
);
};
export default ScrollableCustom;
const styles = StyleSheet.create({
scrollView: {
// maxHeight: 50,
},
buttonContainer: {
flexDirection: "row",
alignItems: "center",
// paddingHorizontal: 16,
// paddingVertical: 10,
gap: 12,
},
});

View File

@@ -0,0 +1,111 @@
// components/Select.tsx
import { GStyles } from "@/styles/global-styles";
import React, { useState } from "react";
import {
FlatList,
Modal,
Pressable,
Text,
TouchableOpacity,
View,
} from "react-native";
type SelectItem = {
label: string;
value: string | number;
};
type SelectProps = {
label?: string;
placeholder?: string;
data: SelectItem[];
value?: string | number | null;
required?: boolean; // <-- new prop
disabled?: boolean; // <-- tambahkan prop disabled
onChange: (value: string | number) => void;
borderRadius?: number;
};
const SelectCustom: React.FC<SelectProps> = ({
label,
placeholder = "Pilih opsi",
data,
value,
required = false, // <-- default false
disabled = false, // <-- default false
onChange,
borderRadius = 8,
}) => {
const [modalVisible, setModalVisible] = useState(false);
const selectedItem = data.find((item) => item.value === value);
const hasError = required && value === null; // <-- check if empty and required
return (
<View style={GStyles.inputContainerArea}>
{label && (
<Text style={GStyles.inputLabel}>
{label}
{required && <Text style={GStyles.inputRequired}> *</Text>}
</Text>
)}
<Pressable
style={[
{ borderRadius },
hasError ? GStyles.inputErrorBorder : null,
GStyles.inputContainerInput,
disabled && GStyles.disabledBox,
]} // <-- add error style
onPress={() => !disabled && setModalVisible(true)}
>
<Text
style={
selectedItem
? disabled
? GStyles.inputTextDisabled
: GStyles.inputText
: disabled
? GStyles.inputPlaceholderDisabled
: GStyles.inputPlaceholder
}
>
{selectedItem?.label || placeholder}
</Text>
</Pressable>
<Modal visible={modalVisible} transparent animationType="fade">
<TouchableOpacity
style={GStyles.selectModalOverlay}
activeOpacity={1}
onPressOut={() => setModalVisible(false)}
>
<View style={GStyles.selectModalContent}>
<FlatList
data={data}
keyExtractor={(item) => String(item.value)}
renderItem={({ item }) => (
<TouchableOpacity
style={GStyles.selectOption}
onPress={() => {
onChange(item.value);
setModalVisible(false);
}}
>
<Text>{item.label}</Text>
</TouchableOpacity>
)}
/>
</View>
</TouchableOpacity>
</Modal>
{/* Optional Error Message */}
{hasError && (
<Text style={GStyles.inputErrorMessage}>Harap pilih salah satu</Text>
)}
</View>
);
};
export default SelectCustom;

View File

@@ -0,0 +1,67 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
// components/Stack.tsx
import React from "react";
import { View, ViewStyle, StyleSheet } from "react-native";
import { AlignType, GapSizeType, JustifyType } from "@/components/Stack/stack-types";
interface StackProps {
children: React.ReactNode;
align?: AlignType;
justify?: JustifyType;
gap?: GapSizeType;
direction?: "row" | "column";
style?: ViewStyle | ViewStyle[];
}
const StackCustom: React.FC<StackProps> = ({
children,
align = "stretch",
justify = "flex-start",
gap = "md",
direction = "column",
style,
}) => {
const spacing = convertToSpacing(gap);
return (
<View
style={[
// styles.stack,
{
flexDirection: direction,
alignItems: align,
justifyContent: justify,
gap: spacing,
},
style,
]}
>
{children}
</View>
);
};
// Fungsi untuk mengubah nilai gap ke dalam ukuran pixel
const convertToSpacing = (value: GapSizeType): number => {
if (typeof value === "number") return value;
const sizes: Record<string, number> = {
xs: 4,
sm: 8,
md: 16,
lg: 24,
xl: 32,
};
return sizes[value] || 16; // default md
};
// const styles = StyleSheet.create({
// stack: {
// flex: 1,
// },
// });
export default StackCustom;

View File

@@ -0,0 +1,19 @@
// types/stack.types.ts
export type AlignType =
| "stretch"
| "flex-start"
| "center"
| "flex-end"
| "baseline";
export type JustifyType =
| "flex-start"
| "center"
| "flex-end"
| "space-between"
| "space-around"
| "space-evenly";
export type GapSizeType = "xs" | "sm" | "md" | "lg" | "xl" | number;
// | string;

View File

@@ -0,0 +1,137 @@
import { MainColor } from "@/constants/color-palet";
import {
TEXT_SIZE_LARGE,
TEXT_SIZE_MEDIUM,
TEXT_SIZE_SMALL,
} from "@/constants/constans-value";
import React from "react";
import {
Text as RNText,
StyleProp,
StyleSheet,
TextStyle,
TouchableOpacity,
} from "react-native";
// Tambahkan type TextAlignProps agar lebih type-safe
type TextAlign = "left" | "center" | "right";
interface TextCustomProps {
children: string | React.ReactNode;
style?: StyleProp<TextStyle>;
bold?: boolean;
semiBold?: boolean;
size?: "default" | "large" | "small";
color?: "default" | "yellow" | "red" | "gray" | "green" | "black"
align?: TextAlign; // Prop untuk alignment
truncate?: boolean | number;
onPress?: () => void;
}
const TextCustom: React.FC<TextCustomProps> = ({
children,
style,
bold = false,
semiBold = false,
size = "default",
color = "default",
align = "left", // Default alignment
truncate = false,
onPress,
}) => {
const getStyle = () => {
let selectedStyles = [];
// Base style
selectedStyles.push(styles.default);
// Font weight
if (bold) selectedStyles.push(styles.bold);
else if (semiBold) selectedStyles.push(styles.semiBold);
// Size
if (size === "large") selectedStyles.push(styles.large);
else if (size === "small") selectedStyles.push(styles.small);
// Color
if (color === "yellow") selectedStyles.push(styles.yellow);
else if (color === "red") selectedStyles.push(styles.red);
else if (color === "gray") selectedStyles.push(styles.gray);
else if (color === "green") selectedStyles.push(styles.green);
else if (color === "black") selectedStyles.push(styles.black);
// Alignment
if (align) {
selectedStyles.push({ textAlign: align });
}
// Override with passed style
if (style) selectedStyles.push(style);
return selectedStyles;
};
return onPress ? (
<TouchableOpacity onPress={onPress}>
<RNText
numberOfLines={
typeof truncate === "number" ? truncate : truncate ? 1 : undefined
}
ellipsizeMode="tail"
style={getStyle()}
>
{children}
</RNText>
</TouchableOpacity>
) : (
<RNText
numberOfLines={
typeof truncate === "number" ? truncate : truncate ? 1 : undefined
}
ellipsizeMode="tail"
style={getStyle()}
>
{children}
</RNText>
);
};
export default TextCustom;
export const styles = StyleSheet.create({
default: {
fontSize: TEXT_SIZE_MEDIUM,
color: MainColor.white,
fontFamily: "Poppins-Regular",
lineHeight: 20,
},
bold: {
fontFamily: "Poppins-Bold",
fontWeight: "700",
},
semiBold: {
fontFamily: "Poppins-SemiBold",
fontWeight: "500",
},
large: {
fontSize: TEXT_SIZE_LARGE,
},
small: {
fontSize: TEXT_SIZE_SMALL,
},
yellow: {
color: MainColor.yellow,
},
red: {
color: MainColor.red,
},
gray: {
color: MainColor.placeholder,
},
green: {
color: MainColor.green,
},
black: {
color: MainColor.black,
},
});

View File

@@ -0,0 +1,146 @@
import { GStyles } from "@/styles/global-styles";
import React, { useEffect, useState } from "react";
import {
TextInput as RNTextInput,
StyleProp,
Text,
View,
ViewStyle,
} from "react-native";
type IconType = React.ReactNode | string;
type BaseProps = {
iconLeft?: IconType;
iconRight?: IconType;
label?: string;
required?: boolean;
error?: string;
fontColor?: string;
disabled?: boolean;
borderRadius?: number;
autosize?: boolean;
minRows?: number;
maxRows?: number;
showCount?: boolean;
maxLength?: number;
height?: number;
style?: StyleProp<ViewStyle>;
};
type NativeTextInputProps = Omit<
React.ComponentProps<typeof RNTextInput>,
"style"
>;
export type TextAreaCustomProps = BaseProps & NativeTextInputProps;
const TextAreaCustom: React.FC<TextAreaCustomProps> = ({
iconLeft,
iconRight,
label,
required = false,
error = "",
fontColor = "#000",
disabled = false,
borderRadius = 8,
autosize = false,
minRows = 4,
maxRows = 6,
showCount = false,
maxLength,
value,
onChangeText,
height = 100,
style,
...rest
}) => {
const [numberOfLines, setNumberOfLines] = useState(minRows);
// Autosizing logic
useEffect(() => {
if (!autosize || !value) return;
const text = value as string;
const lines = text.split("\n").length;
const newLines = Math.max(minRows, Math.min(maxRows, lines));
setNumberOfLines(newLines);
}, [value, autosize, minRows, maxRows]);
const hasError = Boolean(error);
const renderIcon = (icon: IconType) => {
if (!icon) return null;
return typeof icon === "string" ? (
<Text style={GStyles.inputIconText}>{icon}</Text>
) : (
icon
);
};
return (
<View style={[GStyles.inputContainerArea]}>
{label && (
<Text style={GStyles.inputLabel}>
{label}
{required && <Text style={GStyles.inputRequired}> *</Text>}
</Text>
)}
<View
style={[
GStyles.inputContainerInput,
disabled && GStyles.disabledBox,
hasError ? GStyles.inputErrorBorder : {},
{ borderRadius },
{ height },
style,
]}
>
{iconLeft && (
<View style={GStyles.inputIcon}>{renderIcon(iconLeft)}</View>
)}
<RNTextInput
maxLength={maxLength}
multiline
numberOfLines={numberOfLines}
style={[
// GStyles.inputText,
GStyles.textAreaInput,
{ color: fontColor },
]}
editable={!disabled}
value={value as string}
onChangeText={onChangeText}
{...rest}
/>
{iconRight && (
<View style={GStyles.inputIcon}>{renderIcon(iconRight)}</View>
)}
</View>
{/* Error Message atau Counter */}
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
marginTop: 4,
minHeight: 16,
}}
>
{hasError ? (
<Text style={GStyles.inputErrorMessage}>{error}</Text>
) : null}
{showCount && maxLength ? (
<Text style={GStyles.inputMaxLength}>
{(value as string)?.length || 0}/{maxLength}
</Text>
) : null}
</View>
</View>
);
};
export default TextAreaCustom;

View File

@@ -1,5 +1,4 @@
// components/TextInputCustom.tsx
import { GStyles } from "@/styles/global-styles";
import Ionicons from "@expo/vector-icons/Ionicons";
import React, { useState } from "react";
import {
@@ -10,7 +9,6 @@ import {
View,
ViewStyle,
} from "react-native";
import { textInputStyles } from "./textInputStyles";
type IconType = React.ReactNode | string;
@@ -25,63 +23,94 @@ type Props = {
disabled?: boolean;
borderRadius?: number;
style?: StyleProp<ViewStyle>;
maxLength?: number;
containerStyle?: StyleProp<ViewStyle>;
} & Omit<React.ComponentProps<typeof RNTextInput>, "style">;
export const TextInputCustom = ({
const TextInputCustom = ({
iconLeft,
iconRight,
label,
required = false,
error = "",
error: externalError = "",
secureTextEntry = false,
fontColor = "#000",
disabled = false,
borderRadius = 8,
style,
keyboardType,
onChangeText,
maxLength,
containerStyle,
...rest
}: Props) => {
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
const [internalError, setInternalError] = useState("");
// Helper untuk render ikon
const renderIcon = (icon: IconType) => {
if (!icon) return null;
return typeof icon === "string" ? (
<Text style={textInputStyles.iconText}>{icon}</Text>
<Text style={GStyles.inputIconText}>{icon}</Text>
) : (
icon
);
};
// Validasi email jika keyboardType = email-address
const handleTextChange = (text: string) => {
if (keyboardType === "email-address") {
const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(text);
if (!isValid) {
setInternalError("Masukkan email yang valid");
} else {
setInternalError("");
}
}
// Panggil onChangeText eksternal jika ada
if (onChangeText) {
onChangeText(text);
}
};
return (
<View style={textInputStyles.container}>
<View style={[GStyles.inputContainerArea, containerStyle]}>
{label && (
<Text style={textInputStyles.label}>
<Text style={GStyles.inputLabel}>
{label}
{required && <Text style={textInputStyles.required}> *</Text>}
{required && <Text style={GStyles.inputRequired}> *</Text>}
</Text>
)}
<View
style={[
textInputStyles.inputContainer,
disabled && textInputStyles.disabled,
{ borderRadius },
error ? textInputStyles.errorBorder : null,
style,
{ borderRadius },
externalError || internalError ? GStyles.inputErrorBorder : null,
GStyles.inputContainerInput,
disabled && GStyles.disabledBox,
]}
>
{iconLeft && (
<View style={textInputStyles.icon}>{renderIcon(iconLeft)}</View>
<View style={GStyles.inputIcon}>{renderIcon(iconLeft)}</View>
)}
<RNTextInput
style={[textInputStyles.input, { color: fontColor }]}
style={[
GStyles.inputText,
{ color: fontColor },
disabled && GStyles.inputPlaceholderDisabled, // <-- placeholder saat disabled
]}
editable={!disabled}
secureTextEntry={secureTextEntry && !isPasswordVisible}
keyboardType={keyboardType}
onChangeText={handleTextChange}
maxLength={maxLength}
{...rest}
/>
{secureTextEntry && (
<TouchableOpacity
onPress={() => setIsPasswordVisible((prev) => !prev)}
style={textInputStyles.icon}
style={GStyles.inputIcon}
>
<Ionicons
name={isPasswordVisible ? "eye-off" : "eye"}
@@ -91,10 +120,17 @@ export const TextInputCustom = ({
</TouchableOpacity>
)}
{iconRight && (
<View style={textInputStyles.icon}>{renderIcon(iconRight)}</View>
<View style={GStyles.inputIcon}>{renderIcon(iconRight)}</View>
)}
</View>
{error ? <Text style={textInputStyles.errorMessage}>{error}</Text> : null}
{/* Prioritaskan error eksternal */}
{externalError || internalError ? (
<Text style={GStyles.inputErrorMessage}>
{externalError || internalError}
</Text>
) : null}
</View>
);
};
export default TextInputCustom;

View File

@@ -1,71 +0,0 @@
// components/text-input.styles.ts
import { AccentColor, MainColor } from "@/constants/color-palet";
import { StyleSheet } from "react-native";
export const textInputStyles = StyleSheet.create({
// Container utama input (View luar)
container: {
marginBottom: 16,
},
// Label di atas input
label: {
fontSize: 14,
marginBottom: 6,
fontWeight: "500",
color: MainColor.white,
},
// Tanda bintang merah untuk required
required: {
color: "red",
},
// Pesan error di bawah input
errorMessage: {
marginTop: 4,
fontSize: 12,
color: MainColor.red,
},
// Wrapper input (View pembungkus TextInput)
inputContainer: {
flexDirection: "row",
alignItems: "center",
borderWidth: 1,
borderColor: AccentColor.white,
backgroundColor: MainColor.login,
paddingHorizontal: 12,
height: 50,
},
// Style saat disabled
disabled: {
backgroundColor: "#f9f9f9",
borderColor: "#e0e0e0",
},
// Input utama (TextInput)
input: {
flex: 1,
fontSize: 16,
paddingVertical: 0,
},
// Ikon di kiri/kanan
icon: {
marginHorizontal: 4,
justifyContent: "center",
},
// Teks ikon jika berupa string
iconText: {
fontSize: 16,
color: "#000",
},
// Border merah jika ada error
errorBorder: {
borderColor: "red",
},
});

View File

@@ -0,0 +1,27 @@
import { Href } from "expo-router";
export { ICustomTab, ITabs, IMenuDrawerItem };
interface ICustomTab {
icon: string;
label: string;
isActive: boolean;
onPress: () => void;
}
interface ITabs {
id: string;
icon: string;
activeIcon: string;
label: string;
path: Href;
isActive: boolean;
disabled?: boolean;
}
interface IMenuDrawerItem {
icon: React.ReactNode;
label: string;
path?: string;
color?: string;
}

View File

@@ -1,48 +1,95 @@
import { Styles } from "@/styles/global-styles";
import { ImageBackground, ScrollView, View } from "react-native";
import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
import { MainColor } from "@/constants/color-palet";
import { OS_HEIGHT } from "@/constants/constans-value";
import { GStyles } from "@/styles/global-styles";
import {
ImageBackground,
Keyboard,
KeyboardAvoidingView,
Platform,
ScrollView,
TouchableWithoutFeedback,
View,
StyleProp,
ViewStyle,
} from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
interface ViewWrapperProps {
children: React.ReactNode;
withBackground?: boolean;
tabBarComponent?: React.ReactNode;
headerComponent?: React.ReactNode;
footerComponent?: React.ReactNode;
floatingButton?: React.ReactNode;
style?: StyleProp<ViewStyle>;
}
const ViewWrapper = ({
children,
withBackground = false,
tabBarComponent,
headerComponent,
footerComponent,
floatingButton,
style,
}: ViewWrapperProps) => {
const assetBackground = require("../../assets/images/main-background.png");
return (
<SafeAreaProvider>
<SafeAreaView
edges={[
"bottom",
// "top",
]}
style={{
flex: 1,
// paddingTop: StatusBar.currentHeight,
}}
<>
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
style={{ flex: 1, backgroundColor: MainColor.darkblue }}
>
<ScrollView contentContainerStyle={{ flexGrow: 1 }}>
{withBackground ? (
<ImageBackground
source={assetBackground}
resizeMode="cover"
style={Styles.imageBackground}
>
<View style={Styles.containerWithBackground}>{children}</View>
</ImageBackground>
) : (
<View style={Styles.container}>{children}</View>
)}
{/* Header Sticky */}
{headerComponent && (
<View style={GStyles.stickyHeader}>{headerComponent}</View>
)}
<ScrollView
contentContainerStyle={{ flexGrow: 1 }}
keyboardShouldPersistTaps="handled"
>
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<View style={{ flex: 1 }}>
{withBackground ? (
<ImageBackground
source={assetBackground}
resizeMode="cover"
style={[GStyles.imageBackground]}
>
<View style={[GStyles.containerWithBackground, style]}>
{children}
</View>
</ImageBackground>
) : (
<View style={[GStyles.container, style]}>{children}</View>
)}
</View>
</TouchableWithoutFeedback>
</ScrollView>
{tabBarComponent}
</SafeAreaView>
</SafeAreaProvider>
{footerComponent ? (
<SafeAreaView
edges={["bottom"]}
style={{
backgroundColor: MainColor.darkblue,
height: OS_HEIGHT
}}
>
{footerComponent}
</SafeAreaView>
) : (
<SafeAreaView
edges={["bottom"]}
style={{ backgroundColor: MainColor.darkblue }}
/>
)}
{/* Floating Component (misal: FAB) */}
{floatingButton && (
<View style={GStyles.floatingContainer}>{floatingButton}</View>
)}
</KeyboardAvoidingView>
</>
);
};

View File

@@ -0,0 +1,51 @@
import React from "react";
import {
View,
ScrollView,
KeyboardAvoidingView,
Platform,
TouchableWithoutFeedback,
Keyboard,
StyleSheet,
} from "react-native";
import { SafeAreaView } from "react-native-safe-area-context"; // <- Diubah ke sini
interface ViewWrapperProps {
children: React.ReactNode;
footerComponent?: React.ReactNode;
}
const Try_ViewWrapper = ({ children, footerComponent }: ViewWrapperProps) => {
return (
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
style={{ flex: 1 }}
>
<ScrollView contentContainerStyle={{ flexGrow: 1 }}>
<TouchableWithoutFeedback onPress={() => Keyboard.dismiss()}>
<View style={{ flex: 1 }}>{children}</View>
</TouchableWithoutFeedback>
</ScrollView>
{/* Footer Component */}
{footerComponent && (
<SafeAreaView edges={["bottom"]} style={styles.footerContainer}>
{footerComponent}
</SafeAreaView>
)}
</KeyboardAvoidingView>
);
};
export default Try_ViewWrapper;
// File: ViewWrapper.styles.js
const styles = StyleSheet.create({
footerContainer: {
backgroundColor: "#fff",
borderTopWidth: 1,
borderColor: "#ddd",
},
});

85
components/index.ts Normal file
View File

@@ -0,0 +1,85 @@
// Alert
import AlertCustom from "./Alert/AlertCustom";
// Button
import LeftButtonCustom from "./Button/BackButton";
import ButtonCenteredOnly from "./Button/ButtonCenteredOnly";
import ButtonCustom from "./Button/ButtonCustom";
// Drawer
import DrawerCustom from "./Drawer/DrawerCustom";
import MenuDrawerDynamicGrid from "./Drawer/MenuDrawerDynamicGird";
// ShareComponent
import Spacing from "./_ShareComponent/Spacing";
import ViewWrapper from "./_ShareComponent/ViewWrapper";
// Text
import TextCustom from "./Text/TextCustom";
// TextInput
import TextInputCustom from "./TextInput/TextInputCustom";
// TextArea
import TextAreaCustom from "./TextArea/TextAreaCustom";
// Grid
import Grid from "./Grid/GridCustom";
// Box
import BaseBox from "./Box/BaseBox";
import BoxButtonOnFooter from "./Box/BoxButtonOnFooter";
import InformationBox from "./Box/InformationBox";
// Stack
import StackCustom from "./Stack/StackCustom";
// Select
import SelectCustom from "./Select/SelectCustom";
// Image
import AvatarCustom from "./Image/AvatarCustom";
import LandscapeFrameUploaded from "./Image/LandscapeFrameUploaded";
// Divider
import DividerCustom from "./Divider/DividerCustom";
// Map
import MapCustom from "./Map/MapCustom";
// Center
import CenterCustom from "./Center/CenterCustom";
// Clickable
import ClickableCustom from "./Clickable/ClickableCustom";
// Scroll
import ScrollableCustom from "./Scroll/ScrollCustom";
export {
AlertCustom,
// Image
AvatarCustom,
LandscapeFrameUploaded,
// Box
BaseBox,
BoxButtonOnFooter,
ButtonCenteredOnly,
InformationBox,
LeftButtonCustom as BackButton,
// Button
ButtonCustom,
// Drawer
DrawerCustom,
MenuDrawerDynamicGrid,
// Grid
Grid,
// Map
MapCustom,
// Select
SelectCustom,
// ShareComponent
Spacing,
// Stack
StackCustom,
// TextArea
TextAreaCustom,
// Text
TextCustom,
// TextInput
TextInputCustom,
// ViewWrapper
ViewWrapper,
// Divider
DividerCustom,
// Center
CenterCustom,
// Clickable
ClickableCustom,
// Scroll
ScrollableCustom,
};

View File

@@ -3,22 +3,27 @@ export const MainColor = {
darkblue: "#001D3D",
soft_darkblue: "#0e3763",
yellow: "#E1B525",
white: "#D4D0D0",
red: "#FF4B4C",
orange: "#FF7043",
green: "#4CAF4F",
login: "#EDEBEBFF",
disabled: "#606360",
white_gray: "#D4D0D0",
text_input: "#EDEBEBFF",
placeholder: "#888",
white: "#ffffff",
// disabled color
disabled: "#777",
};
export const AccentColor = {
blackgray: "#333533FF",
blackgray: "#aaa",
darkblue: "#002E59",
blue: "#00447D",
softblue: "#007CBA",
skyblue: "#00BFFF",
yellow: "#F8A824",
white: "#FEFFFE",
// disable color
disabledBorder: "#aaa",
};
export const AdminColor = {

View File

@@ -0,0 +1,47 @@
import { Platform } from "react-native";
export {
OS_ANDROID_HEIGHT,
OS_IOS_HEIGHT,
OS_HEIGHT,
TEXT_SIZE_SMALL,
TEXT_SIZE_MEDIUM,
TEXT_SIZE_LARGE,
ICON_SIZE_SMALL,
ICON_SIZE_MEDIUM,
DRAWER_HEIGHT,
RADIUS_BUTTON,
ICON_SIZE_BUTTON,
PADDING_EXTRA_SMALL,
PADDING_SMALL,
PADDING_MEDIUM,
PADDING_LARGE,
};
// OS Height
const OS_ANDROID_HEIGHT = 115
const OS_IOS_HEIGHT = 65
const OS_HEIGHT = Platform.OS === "ios" ? OS_IOS_HEIGHT : OS_ANDROID_HEIGHT
// Text Size
const TEXT_SIZE_SMALL = 12;
const TEXT_SIZE_MEDIUM = 14;
const TEXT_SIZE_LARGE = 16;
// Icon Size
const ICON_SIZE_BUTTON = 18
const ICON_SIZE_SMALL = 20;
const ICON_SIZE_MEDIUM = 24;
// Drawer Height
const DRAWER_HEIGHT = 500; // tinggi drawer5
// Radius Button
const RADIUS_BUTTON = 50
// Padding
const PADDING_EXTRA_SMALL = 10
const PADDING_SMALL = 12
const PADDING_MEDIUM = 16
const PADDING_LARGE = 20

View File

@@ -0,0 +1,6 @@
const DUMMY_IMAGE = {
avatar: require("@/assets/images/dummy/dummy-avatar.png"),
background: require("@/assets/images/dummy/dummy-image-background.jpg"),
};
export default DUMMY_IMAGE;

10
constants/radius-value.ts Normal file
View File

@@ -0,0 +1,10 @@
// constants/radius.ts
export const radiusMap = {
xs: 2,
sm: 4,
md: 6,
lg: 8,
xl: 12,
full: 100,
} as const;

1
eas.build.android Normal file
View File

@@ -0,0 +1 @@
eas build --profile preview

View File

@@ -1,6 +1,6 @@
{
"cli": {
"version": ">= 16.12.0",
"version": ">= 16.10.0",
"appVersionSource": "remote"
},
"build": {
@@ -9,7 +9,10 @@
"distribution": "internal"
},
"preview": {
"distribution": "internal"
"distribution": "internal",
"android": {
"buildType": "apk"
}
},
"production": {
"autoIncrement": true

View File

@@ -0,0 +1,8 @@
export const masterTypeEvent = [
{ label: "Seminar", value: "seminar" },
{ label: "Workshop", value: "workshop" },
{ label: "Lomba", value: "lomba" },
{ label: "Pameran", value: "pameran" },
{ label: "Kegiatan Sosial", value: "kegiatan_sosial" },
{ label: "Musyawarah", value: "musyawarah" },
];

View File

@@ -0,0 +1,26 @@
export const listDummyReportForum = [
{
title: "Kebencian",
desc: "Cercaan, Stereotip rasis atau seksis, Dehumanisasi, Menyulut ketakutan atau diskriminasi, Referensi kebencian, Simbol & logo kebencian",
},
{
title: "Penghinaan & Pelecehan secara Online",
desc: "Penghinaan, Konten Seksual yang Tidak Diinginkan, Penyangkalan Peristiwa Kekerasan, Pelecehan Bertarget dan Memprovokasi Pelecehan",
},
{
title: "Tutur Kekerasan",
desc: "Ancaman Kekerasan, Berharap Terjadinya Celaka, Mengagungkan Kekerasan, Penghasutan Kekerasan, Penghasutan Kekerasan dengan Kode",
},
{
title: "Keselamatan Anak",
desc: "Eksploitasi seks anak di bawah umur, grooming, kekerasan fisik terhadap anak, pengguna di bawah umur",
},
{
title: "Privasi",
desc: "Membagikan informasi pribadi, mengancam akan membagikan/menyebarkan informasi pribadi, membagikan gambar intim tanpa persetujuan, membagikan gambar saya yang tidak saya kehendaki di platform ini",
},
{
title: "Spam",
desc: "Akun palsu, penipuan keuangan, memposting tautan berbahaya, menyalahgunakan hashtag, keterlibatan palsu, balasan berulang, Posting Ulang, atau Direct Message",
},
];

View File

@@ -0,0 +1,100 @@
const dummyMasterBidangBisnis = [
{
id: "1",
name: "Teknologi dan Digital",
slug: "teknologi_dan_digital",
active: true,
createdAt: "2025-06-16T09:57:16.403Z",
updatedAt: "2025-06-16T09:57:16.403Z",
},
{
id: "2",
name: "Kuliner",
slug: "kuliner",
active: true,
createdAt: "2025-06-16T09:57:16.424Z",
updatedAt: "2025-06-16T09:57:16.424Z",
},
{
id: "3",
name: "Fashion dan Kecantikan",
slug: "fashion_dan_kecantikan",
active: true,
createdAt: "2025-06-16T09:57:16.442Z",
updatedAt: "2025-06-16T09:57:16.442Z",
},
{
id: "4",
name: "Otomotif",
slug: "otomotif",
active: true,
createdAt: "2025-06-16T09:57:16.459Z",
updatedAt: "2025-06-16T09:57:16.459Z",
},
{
id: "5",
name: "Industri Kreatif",
slug: "industri_kreatif",
active: true,
createdAt: "2025-06-16T09:57:16.471Z",
updatedAt: "2025-06-16T09:57:16.471Z",
},
{
id: "6",
name: "Konstruksi dan Pertukangan",
slug: "konstruksi_dan_pertukangan",
active: true,
createdAt: "2025-06-16T09:57:16.483Z",
updatedAt: "2025-06-16T09:57:16.483Z",
},
{
id: "7",
name: "Agribisnis dan Pertanian",
slug: "agribisnis_dan_pertanian",
active: true,
createdAt: "2025-06-16T09:57:16.492Z",
updatedAt: "2025-06-16T09:57:16.492Z",
},
{
id: "8",
name: "Jasa Umum",
slug: "jasa_umum",
active: true,
createdAt: "2025-06-16T09:57:16.502Z",
updatedAt: "2025-06-16T09:57:16.502Z",
},
{
id: "9",
name: "Edukasi dan Pelatihan",
slug: "edukasi_dan_pelatihan",
active: true,
createdAt: "2025-06-16T09:57:16.517Z",
updatedAt: "2025-06-16T09:57:16.517Z",
},
{
id: "10",
name: "Keuangan dan Investasi",
slug: "keuangan_dan_investasi",
active: true,
createdAt: "2025-06-16T09:57:16.527Z",
updatedAt: "2025-06-16T09:57:16.527Z",
},
{
id: "11",
name: "Perdagangan Umum",
slug: "perdagangan_umum",
active: true,
createdAt: "2025-06-16T09:57:16.537Z",
updatedAt: "2025-06-16T09:57:16.537Z",
},
{
id: "12",
name: "Pariwisata dan Hospitality",
slug: "pariwisata_dan_hospitality",
active: true,
createdAt: "2025-06-16T09:57:16.547Z",
updatedAt: "2025-06-16T09:57:16.547Z",
},
];
export default dummyMasterBidangBisnis;

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