Compare commits

..

26 Commits

Author SHA1 Message Date
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
108 changed files with 5479 additions and 1304 deletions

View File

@@ -1,6 +1,6 @@
{
"expo": {
"name": "hipmi-mobile",
"name": "HIPMI BADUNG",
"slug": "hipmi-mobile",
"version": "1.0.0",
"orientation": "portrait",

View File

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

View File

@@ -67,7 +67,7 @@ export default function TakePicture() {
/>
<Spacing />
<StackCustom>
<StackCustom >
<ButtonCustom onPress={() => setUri(null)} title="Foto ulang" />
<ButtonCustom
onPress={() => {

View File

@@ -0,0 +1,159 @@
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/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 />,
}}
/>
{/* ========== 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

@@ -8,7 +8,7 @@ export default function EventLayout() {
screenOptions={{
headerShown: false,
tabBarActiveTintColor: MainColor.yellow,
tabBarInactiveTintColor: MainColor.white,
tabBarInactiveTintColor: MainColor.white_gray,
tabBarStyle: {
backgroundColor: MainColor.darkblue,
},

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,111 @@
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 { listDataDummyCommentarForum } 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>
{listDataDummyCommentarForum.map((e, i) => (
<Forum_BoxDetailSection
key={i}
data={e}
setOpenDrawer={setOpenDrawer}
setStatus={setStatus}
/>
))}
</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,160 @@
import {
AlertCustom,
ButtonCustom,
DrawerCustom,
Spacing,
StackCustom,
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 { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import { Divider } from "react-native-paper";
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 [statusCommentar, setStatusCommentar] = useState("");
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}
setStatus={setStatusCommentar}
/>
))}
</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_MenuDrawerBerandaSection
id={id as string}
status={statusCommentar}
setIsDrawerOpen={() => {
setOpenDrawerCommentar(false);
}}
setShowDeleteAlert={setDeleteAlert}
setShowAlertStatus={setAlertStatus}
/>
</DrawerCustom>
</>
);
}

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

@@ -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,61 @@
import { 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 } 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 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 />
</ViewWrapper>
{/* Drawer Komponen Eksternal */}
<DrawerCustom
isVisible={isDrawerOpen}
closeDrawer={closeDrawer}
height={350}
>
<Portofolio_MenuDrawerSection
drawerItems={drawerItemsPortofolio({ id: id as string })}
setIsDrawerOpen={setIsDrawerOpen}
/>
</DrawerCustom>
</>
);
}

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

@@ -1,5 +1,6 @@
import {
AvatarCustom,
ButtonCenteredOnly,
ButtonCustom,
SelectCustom,
Spacing,
@@ -7,14 +8,15 @@ import {
TextInputCustom,
ViewWrapper,
} from "@/components";
import BoxButtonOnFooter from "@/components/Box/BoxButtonOnFooter";
import InformationBox from "@/components/Box/InformationBox";
import ButtonUpload from "@/components/Button/ButtonUpload";
import LandscapeFrameUploaded from "@/components/Image/LandscapeFrameUploaded";
import { GStyles } from "@/styles/global-styles";
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: "",
@@ -24,20 +26,18 @@ export default function CreateProfile() {
const handlerSave = () => {
console.log("data create profile >>", data);
// router.back();
router.back();
};
const footerComponent = (
<View style={GStyles.bottomBar}>
<View style={GStyles.bottomBarContainer}>
<ButtonCustom
onPress={handlerSave}
disabled={!data.name || !data.email || !data.address || !data.gender}
>
Simpan
</ButtonCustom>
</View>
</View>
<BoxButtonOnFooter>
<ButtonCustom
onPress={handlerSave}
// disabled={!data.name || !data.email || !data.address || !data.gender}
>
Simpan
</ButtonCustom>
</BoxButtonOnFooter>
);
return (
@@ -47,7 +47,12 @@ export default function CreateProfile() {
<View style={{ alignItems: "center" }}>
<AvatarCustom size="xl" />
<Spacing />
<ButtonUpload onPress={() => console.log("pressed")} />
<ButtonCenteredOnly
icon="upload"
onPress={() => router.navigate(`/take-picture/${id}`)}
>
Upload
</ButtonCenteredOnly>
</View>
<Spacing />
@@ -56,7 +61,12 @@ export default function CreateProfile() {
<InformationBox text="Upload foto latar belakang anda." />
<LandscapeFrameUploaded />
<Spacing />
<ButtonUpload onPress={() => console.log("pressed")} />
<ButtonCenteredOnly
icon="upload"
onPress={() => router.navigate(`/take-picture/${id}`)}
>
Upload
</ButtonCenteredOnly>
</View>
<Spacing />

View File

@@ -6,6 +6,7 @@ import {
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";
@@ -44,15 +45,17 @@ export default function ProfileEdit() {
return (
<ViewWrapper
bottomBarComponent={
<ButtonCustom
disabled={
!data.nama || !data.email || !data.alamat || !data.selectedValue
}
onPress={handleSave}
>
Simpan
</ButtonCustom>
footerComponent={
<BoxButtonOnFooter>
<ButtonCustom
// disabled={
// !data.nama || !data.email || !data.alamat || !data.selectedValue
// }
onPress={handleSave}
>
Simpan
</ButtonCustom>
</BoxButtonOnFooter>
}
>
<StackCustom gap={"xs"}>

View File

@@ -1,79 +1,28 @@
import { IMenuDrawerItem } from "@/components/_Interface/types";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import AlertCustom from "@/components/Alert/AlertCustom";
import BackButton from "@/components/Button/BackButton";
import LeftButtonCustom from "@/components/Button/BackButton";
import DrawerCustom from "@/components/Drawer/DrawerCustom";
import { MainColor } from "@/constants/color-palet";
import { DRAWER_HEIGHT } from "@/constants/constans-value";
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, { useRef, useState } from "react";
import { Animated, InteractionManager, TouchableOpacity } from "react-native";
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 drawerItems: IMenuDrawerItem[] = [
{
icon: "create",
label: "Edit profile",
path: `/(application)/profile/${id}/edit`,
},
{
icon: "camera",
label: "Ubah foto profile",
path: `/(application)/profile/${id}/update-photo`,
},
{
icon: "image",
label: "Ubah latar belakang",
path: `/(application)/profile/${id}/update-background`,
},
{
icon: "add-circle",
label: "Tambah portofolio",
path: `/(application)/portofolio/${id}/create`,
},
// {
// icon: "settings",
// label: "Dashboard Admin",
// path: `/(application)/profile/dashboard-admin`,
// },
{ icon: "log-out", label: "Keluar", color: "red", path: "" },
{
icon: "create-outline",
label: "Create profile",
path: `/(application)/profile/${id}/create`,
},
];
// Animasi menggunakan translateY (lebih kompatibel)
const drawerAnim = useRef(new Animated.Value(DRAWER_HEIGHT)).current; // mulai di luar bawah layar
const openDrawer = () => {
setIsDrawerOpen(true);
Animated.timing(drawerAnim, {
toValue: 0,
duration: 300,
useNativeDriver: true,
}).start();
};
const closeDrawer = () => {
Animated.timing(drawerAnim, {
toValue: DRAWER_HEIGHT, // sesuaikan dengan tinggi drawer Anda
duration: 300,
useNativeDriver: true,
}).start(() => {
InteractionManager.runAfterInteractions(() => {
setIsDrawerOpen(false); // baru ganti state setelah animasi selesai
});
});
setIsDrawerOpen(false);
};
const handleLogout = () => {
@@ -89,7 +38,7 @@ export default function Profile() {
<Stack.Screen
options={{
title: "Profile",
headerLeft: () => <BackButton />,
headerLeft: () => <LeftButtonCustom />,
headerRight: () => (
<TouchableOpacity onPress={openDrawer}>
<Ionicons
@@ -104,18 +53,16 @@ export default function Profile() {
}}
/>
<ProfilSection />
</ViewWrapper>
{/* Drawer Komponen Eksternal */}
<DrawerCustom
height={350}
isVisible={isDrawerOpen}
drawerAnim={drawerAnim}
closeDrawer={closeDrawer}
>
<Profile_MenuDrawerSection
drawerItems={drawerItems}
drawerItems={drawerItemsProfile({ id: id as string })}
setShowLogoutAlert={setShowLogoutAlert}
setIsDrawerOpen={setIsDrawerOpen}
/>

View File

@@ -1,25 +1,31 @@
import { BaseBox, ButtonCustom } from "@/components";
import {
BaseBox,
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
} from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import ButtonUpload from "@/components/Button/ButtonUpload";
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
bottomBarComponent={
<ButtonCustom
onPress={() => {
console.log("Simpan foto background >>", id);
router.back();
}}
>
Simpan
</ButtonCustom>
}
>
<ViewWrapper footerComponent={buttonFooter}>
<BaseBox
style={{ alignItems: "center", justifyContent: "center", height: 250 }}
>
@@ -30,12 +36,12 @@ export default function UpdateBackgroundProfile() {
/>
</BaseBox>
<ButtonUpload
title="Update"
onPress={() =>
router.navigate(`/(application)/take-picture/${id}`)
}
/>
<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,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,185 +1,28 @@
import { AccentColor, MainColor } from "@/constants/color-palet";
import { GStyles } from "@/styles/global-styles";
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: GStyles.headerStyle,
headerTitleStyle: GStyles.headerTitleStyle,
headerTitleAlign: "center",
contentStyle: {
borderBottomColor: AccentColor.blue,
borderBottomWidth: 2,
},
// headerLargeStyle: {
// backgroundColor: MainColor.darkblue,
// },
}}
>
<Stack.Screen
name="home"
options={{
title: "HIPMI",
headerLeft: () => (
<Ionicons
name="search"
size={20}
color={MainColor.yellow}
onPress={() => router.push("/(application)/user-search")}
/>
),
headerRight: () => (
<Ionicons
name="notifications"
size={20}
color={MainColor.yellow}
onPress={() => router.push("/(application)/notifications")}
/>
),
}}
/>
<Stack.Screen
name="forum/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="marketplace/index"
options={{
title: "Market Place",
headerLeft: () => (
<Ionicons
name="arrow-back"
size={20}
color={MainColor.yellow}
onPress={() => router.back()}
/>
),
}}
/>
{/* Profile */}
<Stack.Screen
name="profile"
options={{
headerShown: false,
}}
/>
{/* Portofolio */}
<Stack.Screen
name="portofolio"
options={{
headerShown: false,
}}
/>
{/* Event */}
<Stack.Screen
name="event/(tabs)"
options={{
title: "Event",
headerLeft: () => (
<Ionicons
name="arrow-back"
size={20}
color={MainColor.yellow}
onPress={() => router.push("/(application)/home")}
/>
),
}}
/>
<Stack.Screen
name="event/detail/[id]"
options={{
title: "Detail",
headerLeft: () => (
<Ionicons
name="arrow-back"
size={20}
color={MainColor.yellow}
onPress={() => router.back()}
/>
),
}}
/>
{/* User Search */}
<Stack.Screen
name="user-search/index"
options={{
title: "Pencarian Pengguna",
headerLeft: () => (
<Ionicons
name="arrow-back"
size={20}
color={MainColor.yellow}
onPress={() => router.back()}
/>
),
}}
/>
{/* Notification */}
<Stack.Screen
name="notifications/index"
options={{
title: "Notifikasi",
headerLeft: () => (
<Ionicons
name="arrow-back"
size={20}
color={MainColor.yellow}
onPress={() => router.back()}
/>
),
}}
/>
<Stack screenOptions={HeaderStyles}>
<Stack.Screen name="(user)" options={{ headerShown: false }} />
{/* Take Picture */}
<Stack.Screen
name="take-picture/[id]/index"
name="(image)/take-picture/[id]/index"
options={{
title: "Ambil Gambar",
headerLeft: () => (
<Ionicons
name="arrow-back"
size={20}
color={MainColor.yellow}
onPress={() => router.back()}
/>
),
headerLeft: () => <BackButton />,
}}
/>
{/* Preview Image */}
<Stack.Screen
name="(image)/preview-image/[id]/index"
options={{
title: "Preview Gambar",
headerLeft: () => <BackButton />,
}}
/>
</Stack>

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 Notifications() {
return (
<View>
<Text>Notifications</Text>
</View>
);
}

View File

@@ -1,11 +0,0 @@
import { Text, View } from "react-native";
import { useLocalSearchParams } from "expo-router";
export default function PortofolioCreate() {
const { id } = useLocalSearchParams();
return (
<View>
<Text>Portofolio Create {id}</Text>
</View>
);
}

View File

@@ -1,32 +0,0 @@
import BackButton from "@/components/Button/BackButton";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { GStyles } from "@/styles/global-styles";
import { Stack, useLocalSearchParams } from "expo-router";
import { Text } from "react-native";
export default function Portofolio() {
const { id } = useLocalSearchParams();
return (
<ViewWrapper>
{/* Header */}
<Stack.Screen
options={{
title: "Portofolio",
headerLeft: () => <BackButton />,
// headerRight: () => (
// <TouchableOpacity onPress={openDrawer}>
// <Ionicons
// name="ellipsis-vertical"
// size={20}
// color={MainColor.yellow}
// />
// </TouchableOpacity>
// ),
headerStyle: GStyles.headerStyle,
headerTitleStyle: GStyles.headerTitleStyle,
}}
/>
<Text style={GStyles.textLabel}>Portofolio {id}</Text>
</ViewWrapper>
);
}

View File

@@ -1,26 +0,0 @@
import BackButton from "@/components/Button/BackButton";
import { GStyles } from "@/styles/global-styles";
import { Stack } from "expo-router";
export default function PortofolioLayout() {
return (
<>
<Stack
screenOptions={{
headerStyle: GStyles.headerStyle,
headerTitleStyle: GStyles.headerTitleStyle,
headerTitleAlign: "center",
headerBackButtonDisplayMode: "minimal",
headerLeft: () => <BackButton />,
}}
>
{/* <Stack.Screen name="[id]/index" options={{ title: "Portofolio" }} /> */}
<Stack.Screen
name="[id]/create"
options={{ title: "Tambah Portofolio" }}
/>
</Stack>
</>
);
}

View File

@@ -1,42 +0,0 @@
import { BaseBox, ButtonCustom } from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import ButtonUpload from "@/components/Button/ButtonUpload";
import DUMMY_IMAGE from "@/constants/dummy-image-value";
import { router, useLocalSearchParams } from "expo-router";
import { Image } from "react-native";
export default function UpdatePhotoProfile() {
const { id } = useLocalSearchParams();
return (
<ViewWrapper
bottomBarComponent={
<ButtonCustom
onPress={() => {
console.log("Simpan foto profile >>", id);
router.back();
}}
>
Simpan
</ButtonCustom>
}
>
<BaseBox
style={{ alignItems: "center", justifyContent: "center", height: 250 }}
>
<Image
source={DUMMY_IMAGE.avatar}
resizeMode="cover"
style={{ width: 200, height: 200 }}
/>
</BaseBox>
<ButtonUpload
title="Update"
onPress={() => {
console.log("Update photo >>", id);
router.navigate(`/(application)/take-picture/${id}`);
}}
/>
</ViewWrapper>
);
}

View File

@@ -1,9 +0,0 @@
import { Text, View } from "react-native";
export default function UserSearch() {
return (
<View>
<Text>User Search</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

@@ -15,6 +15,7 @@ export default function RootLayout() {
}}
>
<Stack.Screen name="index" options={{ title: "" }} />
<Stack.Screen name="+not-found" options={{ title: "" }} />
<Stack.Screen
name="verification"
options={{ title: "", headerBackVisible: false }}

View File

@@ -11,6 +11,7 @@
"@react-navigation/native": "^7.1.6",
"@react-navigation/native-stack": "^7.3.21",
"@types/react-native-vector-icons": "^6.4.18",
"dayjs": "^1.11.13",
"expo": "53.0.17",
"expo-blur": "~14.1.5",
"expo-camera": "~16.1.10",
@@ -31,7 +32,9 @@
"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",
@@ -237,6 +240,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=="],
@@ -423,6 +428,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=="],
@@ -697,6 +704,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=="],
@@ -1373,8 +1382,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=="],
@@ -1707,6 +1720,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=="],
@@ -1887,6 +1902,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=="],
@@ -2007,6 +2024,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=="],
@@ -2043,6 +2062,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

@@ -95,12 +95,12 @@ const styles = StyleSheet.create({
fontSize: TEXT_SIZE_LARGE,
fontWeight: "bold",
marginBottom: 20,
color: MainColor.white,
color: MainColor.white_gray,
},
alertMessage: {
textAlign: "center",
marginBottom: 20,
color: MainColor.white,
color: MainColor.white_gray,
},
alertButtons: {
flexDirection: "row",

View File

@@ -1,4 +1,5 @@
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 {
@@ -8,14 +9,16 @@ interface BaseBoxProps {
marginBottom?: number;
padding?: number;
paddingInline?: number;
paddingBlock?: number;
}
export default function BaseBox({
children,
style,
onPress,
marginBottom = 16,
padding = 12,
marginBottom = PADDING_MEDIUM,
paddingBlock = PADDING_EXTRA_SMALL,
paddingInline = PADDING_SMALL,
}: BaseBoxProps) {
return (
<>
@@ -29,7 +32,8 @@ export default function BaseBox({
borderWidth: 1,
borderRadius: 10,
marginBottom,
padding,
paddingBlock,
paddingInline,
},
style,
]}
@@ -46,7 +50,8 @@ export default function BaseBox({
borderWidth: 1,
borderRadius: 10,
marginBottom,
padding,
paddingBlock,
paddingInline,
},
style,
]}

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

@@ -16,7 +16,7 @@ export default function InformationBox({ text }: { text: string }) {
<Ionicons
name="information-circle-outline"
size={24}
color={MainColor.white}
color={MainColor.white_gray}
/>
</Grid.Col>
<Grid.Col span={10} style={{ justifyContent: "center" }}>

View File

@@ -1,16 +1,36 @@
import { Ionicons } from "@expo/vector-icons";
import { MainColor } from "@/constants/color-palet";
import { router } from "expo-router";
import { Ionicons } from "@expo/vector-icons";
import { Href, router } from "expo-router";
const BackButton = () => {
/**
*
* @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 (
<Ionicons
name="arrow-back"
size={20}
color={MainColor.yellow}
onPress={() => router.back()}
/>
<>
{iconCustom ? (
iconCustom
) : (
<Ionicons
name={icon}
size={20}
color={MainColor.yellow}
onPress={() => (path ? router.replace(path) : router.back())}
/>
)}
</>
);
};
export default BackButton;
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

@@ -2,18 +2,19 @@
import React from "react";
import { StyleProp, Text, TouchableOpacity, ViewStyle } from "react-native";
import buttonStyles from "./buttonCustomStyles";
import { radiusMap } from "@/constants/radius-value";
import { MainColor } from "@/constants/color-palet";
import { stylesButton } from "./buttonCustomStyles";
import { Href, router } from "expo-router";
// Import radiusMap
// Definisi type untuk radius
type RadiusType = keyof typeof radiusMap | number;
interface ButtonProps {
children?: React.ReactNode;
href?: Href;
onPress?: () => void;
title?: string;
backgroundColor?: string;
@@ -26,34 +27,41 @@ interface ButtonProps {
const ButtonCustom: React.FC<ButtonProps> = ({
children,
href,
onPress,
title = "Button",
backgroundColor = MainColor.yellow,
textColor = MainColor.black,
radius = "full", // default md
radius = 50, // default md
disabled = false,
iconLeft,
style,
}) => {
const borderRadius =
typeof radius === "number" ? radius : radiusMap[radius ?? "md"]; // fallback ke 'md'
const styles = buttonStyles({
backgroundColor,
textColor,
borderRadius,
});
return (
<TouchableOpacity
style={[styles.button, disabled && styles.disabled, style]}
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}>{children || title}</Text>
<Text style={[stylesButton.buttonText, { color: textColor }]}>
{children || title}
</Text>
</TouchableOpacity>
);
};

View File

@@ -1,21 +0,0 @@
import { MainColor } from "@/constants/color-palet";
import { GStyles } from "@/styles/global-styles";
import { Feather } from "@expo/vector-icons";
import React from "react";
import ButtonCustom from "./ButtonCustom";
interface ButtonUploadProps {
title?: string;
onPress: () => void;
}
export default function ButtonUpload({ onPress, title = "Upload" }: ButtonUploadProps) {
return (
<ButtonCustom
onPress={onPress}
iconLeft={<Feather name="upload" size={20} color={MainColor.black} />}
style={GStyles.buttonCentered50Percent}
>
{title}
</ButtonCustom>
);
}

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

@@ -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,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

@@ -1,4 +1,4 @@
import React, { useRef } from "react";
import React, { useEffect, useRef } from "react";
import {
Animated,
PanResponder,
@@ -14,7 +14,7 @@ interface DrawerCustomProps {
children?: React.ReactNode;
height?: number;
isVisible: boolean;
drawerAnim: Animated.Value;
drawerAnim?: Animated.Value;
closeDrawer: () => void;
// openLogoutAlert: () => void;
}
@@ -33,6 +33,26 @@ export default function DrawerCustom({
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) => {
@@ -41,7 +61,7 @@ DrawerCustomProps) {
onPanResponderMove: (_, gestureState) => {
const offset = gestureState.dy;
if (offset >= 0 && offset <= DRAWER_HEIGHT) {
drawerAnim.setValue(offset);
drawerAnima.setValue(offset);
}
},
onPanResponderRelease: (_, gestureState) => {
@@ -50,7 +70,7 @@ DrawerCustomProps) {
closeDrawer();
});
} else {
Animated.spring(drawerAnim, {
Animated.spring(drawerAnima, {
toValue: 0,
useNativeDriver: true,
}).start();
@@ -80,7 +100,7 @@ DrawerCustomProps) {
styles.drawer,
{
height: height || DRAWER_HEIGHT,
transform: [{ translateY: drawerAnim }],
transform: [{ translateY: drawerAnima }],
},
]}
{...panResponder.panHandlers}

View File

@@ -1,27 +1,37 @@
import { AccentColor, MainColor } from "@/constants/color-palet";
import { ICON_SIZE_MEDIUM, TEXT_SIZE_SMALL } from "@/constants/constans-value";
import { Ionicons } from "@expo/vector-icons";
import { View, TouchableOpacity, Text, StyleSheet } from "react-native";
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: any, index: any) => (
{data.map((item: IMenuDrawerItem, index: any) => (
<TouchableOpacity
key={index}
style={[styles.itemContainer, { flexBasis: `${100 / numColumns}%` }]}
onPress={() => onPressItem?.(item)}
>
<View style={styles.iconContainer}>
<Ionicons
<View
style={[
styles.iconContainer,
{ backgroundColor: item.color || AccentColor.blue },
]}
>
{item.icon}
{/* <Ionicons
name={item.icon}
size={ICON_SIZE_MEDIUM}
color={item.color || MainColor.white}
/>
color={item.color || MainColor.white_gray}
/> */}
</View>
<Text style={styles.label}>{item.label}</Text>
<Text
style={[styles.label, { color: item.color || AccentColor.white }]}
>
{item.label}
</Text>
</TouchableOpacity>
))}
</View>
@@ -52,6 +62,6 @@ const styles = StyleSheet.create({
marginTop: 10,
fontSize: TEXT_SIZE_SMALL,
textAlign: "center",
color: MainColor.white,
color: MainColor.white_gray,
},
});
});

View File

@@ -109,5 +109,6 @@ const styles = StyleSheet.create({
flexDirection: "row",
flexWrap: "wrap",
justifyContent: "flex-start",
marginInline: 0.1
},
});

View File

@@ -1,12 +1,21 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { MainColor } from "@/constants/color-palet";
import DUMMY_IMAGE from "@/constants/dummy-image-value";
import { Image, ImageSourcePropType, StyleSheet } from "react-native";
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 = {
@@ -20,24 +29,42 @@ const sizeMap = {
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 (
<Image
source={source}
style={[
styles.overlappingAvatar,
{
width: dimension,
height: dimension,
borderRadius: dimension / 2,
},
]}
resizeMode="cover"
/>
<>
{onPress || href ? (
<TouchableOpacity
onPress={href ? () => router.navigate(href as any) : onPress}
>
<ImageView source={source} />
</TouchableOpacity>
) : (
<ImageView source={source} />
)}
</>
);
}
}
const styles = StyleSheet.create({
container: {
@@ -47,7 +74,7 @@ const styles = StyleSheet.create({
overlappingAvatar: {
borderWidth: 2,
borderColor: "#fff",
backgroundColor: MainColor.white,
backgroundColor: MainColor.white_gray,
// shadowColor: "#000",
// shadowOffset: { width: 0, height: 2 },
// shadowOpacity: 0.2,

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,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

@@ -1,15 +1,13 @@
// components/Select.tsx
import { MainColor } from "@/constants/color-palet";
import { TEXT_SIZE_MEDIUM } from "@/constants/constans-value";
import { GStyles } from "@/styles/global-styles";
import React, { useState } from "react";
import {
View,
Text,
Pressable,
Modal,
FlatList,
StyleSheet,
Modal,
Pressable,
Text,
TouchableOpacity,
View,
} from "react-native";
type SelectItem = {
@@ -23,7 +21,9 @@ type SelectProps = {
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> = ({
@@ -32,7 +32,9 @@ const SelectCustom: React.FC<SelectProps> = ({
data,
value,
required = false, // <-- default false
disabled = false, // <-- default false
onChange,
borderRadius = 8,
}) => {
const [modalVisible, setModalVisible] = useState(false);
@@ -41,35 +43,50 @@ const SelectCustom: React.FC<SelectProps> = ({
const hasError = required && value === null; // <-- check if empty and required
return (
<View style={styles.container}>
<View style={GStyles.inputContainerArea}>
{label && (
<Text style={styles.label}>
<Text style={GStyles.inputLabel}>
{label}
{required && <Text style={styles.requiredIndicator}> *</Text>}
{required && <Text style={GStyles.inputRequired}> *</Text>}
</Text>
)}
<Pressable
style={[styles.input, hasError ? styles.inputError : null]} // <-- add error style
onPress={() => setModalVisible(true)}
style={[
{ borderRadius },
hasError ? GStyles.inputErrorBorder : null,
GStyles.inputContainerInput,
disabled && GStyles.disabledBox,
]} // <-- add error style
onPress={() => !disabled && setModalVisible(true)}
>
<Text style={selectedItem ? styles.text : styles.placeholder}>
<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={styles.modalOverlay}
style={GStyles.selectModalOverlay}
activeOpacity={1}
onPressOut={() => setModalVisible(false)}
>
<View style={styles.modalContent}>
<View style={GStyles.selectModalContent}>
<FlatList
data={data}
keyExtractor={(item) => String(item.value)}
renderItem={({ item }) => (
<TouchableOpacity
style={styles.option}
style={GStyles.selectOption}
onPress={() => {
onChange(item.value);
setModalVisible(false);
@@ -85,68 +102,10 @@ const SelectCustom: React.FC<SelectProps> = ({
{/* Optional Error Message */}
{hasError && (
<Text style={styles.errorMessage}>Harap pilih salah satu</Text>
<Text style={GStyles.inputErrorMessage}>Harap pilih salah satu</Text>
)}
</View>
);
};
export default SelectCustom;
const styles = StyleSheet.create({
container: {
marginBottom: 16,
},
label: {
fontSize: TEXT_SIZE_MEDIUM,
marginBottom: 4,
color: MainColor.white,
fontWeight: "500",
},
requiredIndicator: {
color: "red",
fontWeight: "bold",
},
input: {
borderWidth: 1,
borderColor: "#ccc",
padding: 12,
borderRadius: 8,
minHeight: 48,
justifyContent: "center",
backgroundColor: MainColor.white,
},
inputError: {
borderColor: "red",
},
text: {
fontSize: TEXT_SIZE_MEDIUM,
},
placeholder: {
fontSize: TEXT_SIZE_MEDIUM,
color: MainColor.placeholder,
},
modalOverlay: {
flex: 1,
backgroundColor: "rgba(0,0,0,0.3)",
justifyContent: "center",
alignItems: "center",
},
modalContent: {
width: "80%",
maxHeight: 300,
backgroundColor: "white",
borderRadius: 8,
overflow: "hidden",
},
option: {
padding: 16,
borderBottomWidth: 1,
borderBottomColor: "#eee",
},
errorMessage: {
marginTop: 4,
fontSize: 12,
color: "red",
},
});

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
// components/Stack.tsx
import React from "react";
@@ -57,10 +58,10 @@ const convertToSpacing = (value: GapSizeType): number => {
return sizes[value] || 16; // default md
};
const styles = StyleSheet.create({
stack: {
flex: 1,
},
});
// const styles = StyleSheet.create({
// stack: {
// flex: 1,
// },
// });
export default StackCustom;

View File

@@ -5,7 +5,13 @@ import {
TEXT_SIZE_SMALL,
} from "@/constants/constans-value";
import React from "react";
import { Text as RNText, StyleProp, StyleSheet, TextStyle } from "react-native";
import {
Text as RNText,
StyleProp,
StyleSheet,
TextStyle,
TouchableOpacity,
} from "react-native";
// Tambahkan type TextAlignProps agar lebih type-safe
type TextAlign = "left" | "center" | "right";
@@ -16,9 +22,10 @@ interface TextCustomProps {
bold?: boolean;
semiBold?: boolean;
size?: "default" | "large" | "small";
color?: "default" | "yellow" | "red";
color?: "default" | "yellow" | "red" | "gray" | "green";
align?: TextAlign; // Prop untuk alignment
truncate?: boolean | number;
onPress?: () => void;
}
const TextCustom: React.FC<TextCustomProps> = ({
@@ -30,6 +37,7 @@ const TextCustom: React.FC<TextCustomProps> = ({
color = "default",
align = "left", // Default alignment
truncate = false,
onPress,
}) => {
const getStyle = () => {
let selectedStyles = [];
@@ -48,6 +56,8 @@ const TextCustom: React.FC<TextCustomProps> = ({
// 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);
// Alignment
if (align) {
@@ -60,7 +70,19 @@ const TextCustom: React.FC<TextCustomProps> = ({
return selectedStyles;
};
return (
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
@@ -80,6 +102,7 @@ export const styles = StyleSheet.create({
fontSize: TEXT_SIZE_MEDIUM,
color: MainColor.white,
fontFamily: "Poppins-Regular",
lineHeight: 20,
},
bold: {
fontFamily: "Poppins-Bold",
@@ -101,4 +124,10 @@ export const styles = StyleSheet.create({
red: {
color: MainColor.red,
},
gray: {
color: MainColor.placeholder,
},
green: {
color: MainColor.green,
},
});

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,3 +1,4 @@
import { GStyles } from "@/styles/global-styles";
import Ionicons from "@expo/vector-icons/Ionicons";
import React, { useState } from "react";
import {
@@ -8,7 +9,6 @@ import {
View,
ViewStyle,
} from "react-native";
import { textInputStyles } from "./textInputStyles";
type IconType = React.ReactNode | string;
@@ -23,9 +23,11 @@ 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,
@@ -38,6 +40,8 @@ export const TextInputCustom = ({
style,
keyboardType,
onChangeText,
maxLength,
containerStyle,
...rest
}: Props) => {
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
@@ -47,7 +51,7 @@ export const TextInputCustom = ({
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
);
@@ -71,37 +75,42 @@ export const TextInputCustom = ({
};
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 },
externalError || internalError ? 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"}
@@ -111,15 +120,17 @@ export const TextInputCustom = ({
</TouchableOpacity>
)}
{iconRight && (
<View style={textInputStyles.icon}>{renderIcon(iconRight)}</View>
<View style={GStyles.inputIcon}>{renderIcon(iconRight)}</View>
)}
</View>
{/* Prioritaskan error eksternal */}
{externalError || internalError ? (
<Text style={textInputStyles.errorMessage}>
<Text style={GStyles.inputErrorMessage}>
{externalError || internalError}
</Text>
) : null}
</View>
);
};
export default TextInputCustom;

View File

@@ -1,3 +0,0 @@
import { TextInputCustom } from "./TextInputCustom";
export { 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.white,
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

@@ -20,7 +20,7 @@ interface ITabs {
}
interface IMenuDrawerItem {
icon: string;
icon: React.ReactNode;
label: string;
path?: string;
color?: string;

View File

@@ -8,19 +8,27 @@ import {
ScrollView,
TouchableWithoutFeedback,
View,
StyleProp,
ViewStyle,
} from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
interface ViewWrapperProps {
children: React.ReactNode;
withBackground?: boolean;
headerComponent?: React.ReactNode;
footerComponent?: React.ReactNode;
floatingButton?: React.ReactNode;
style?: StyleProp<ViewStyle>;
}
const ViewWrapper = ({
children,
withBackground = false,
headerComponent,
footerComponent,
floatingButton,
style,
}: ViewWrapperProps) => {
const assetBackground = require("../../assets/images/main-background.png");
@@ -28,8 +36,13 @@ const ViewWrapper = ({
<>
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
style={{ flex: 1 }}
style={{ flex: 1, backgroundColor: MainColor.darkblue }}
>
{/* Header Sticky */}
{headerComponent && (
<View style={GStyles.stickyHeader}>{headerComponent}</View>
)}
<ScrollView
contentContainerStyle={{ flexGrow: 1 }}
keyboardShouldPersistTaps="handled"
@@ -40,14 +53,14 @@ const ViewWrapper = ({
<ImageBackground
source={assetBackground}
resizeMode="cover"
style={GStyles.imageBackground}
style={[GStyles.imageBackground]}
>
<View style={GStyles.containerWithBackground}>
<View style={[GStyles.containerWithBackground, style]}>
{children}
</View>
</ImageBackground>
) : (
<View style={GStyles.container}>{children}</View>
<View style={[GStyles.container, style]}>{children}</View>
)}
</View>
</TouchableWithoutFeedback>
@@ -57,7 +70,6 @@ const ViewWrapper = ({
<SafeAreaView
edges={["bottom"]}
style={{
flex: 1,
backgroundColor: MainColor.darkblue,
}}
>
@@ -69,43 +81,12 @@ const ViewWrapper = ({
style={{ backgroundColor: MainColor.darkblue }}
/>
)}
{/* Floating Component (misal: FAB) */}
{floatingButton && (
<View style={GStyles.floatingContainer}>{floatingButton}</View>
)}
</KeyboardAvoidingView>
{/* <SafeAreaView
edges={["bottom"]}
style={{ flex: 1, backgroundColor: MainColor.soft_darkblue }}
>
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
style={{ flex: 1 }}
>
<ScrollView contentContainerStyle={{ flexGrow: 1 }}>
{withBackground ? (
<ImageBackground
source={assetBackground}
resizeMode="cover"
style={GStyles.imageBackground}
>
<View style={GStyles.containerWithBackground}>{children}</View>
</ImageBackground>
) : (
<View style={GStyles.container}>{children}</View>
)}
</ScrollView>
{footerComponent ? (
<SafeAreaView
edges={["bottom"]}
style={{
// flex: 1,
backgroundColor: MainColor.darkblue,
}}
>
{footerComponent}
</SafeAreaView>
) : null}
</KeyboardAvoidingView>
</SafeAreaView> */}
</>
);
};

View File

@@ -1,52 +1,85 @@
// Alert
import AlertCustom from "./Alert/AlertCustom";
// Button
import BackButton from "./Button/BackButton";
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 ViewWrapper from "./_ShareComponent/ViewWrapper";
import Spacing from "./_ShareComponent/Spacing";
import ViewWrapper from "./_ShareComponent/ViewWrapper";
// Text
import TextCustom from "./Text/TextCustom";
// TextInput
import { TextInputCustom } from "./TextInput/TextInputCustom";
import TextInputCustom from "./TextInput/TextInputCustom";
// TextArea
import TextAreaCustom from "./TextArea/TextAreaCustom";
// Grid
import Grid from "./Grid/GridCustom";
// Box
import BaseBox from "./Box/BaseBox";
// Avatar
import AvatarCustom from "./Image/AvatarCustom"
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
BackButton,
ButtonCustom,
// Drawer
DrawerCustom,
MenuDrawerDynamicGrid,
// Grid
Grid,
// Map
MapCustom,
// Select
SelectCustom,
// ShareComponent
Spacing,
ViewWrapper,
// Stack
StackCustom,
// TextArea
TextAreaCustom,
// Text
TextCustom,
// TextInput
TextInputCustom,
// Grid
Grid,
// Box
BaseBox,
// Avatar
AvatarCustom,
// Stack
StackCustom,
// Select
SelectCustom,
// ViewWrapper
ViewWrapper,
// Divider
DividerCustom,
// Center
CenterCustom,
// Clickable
ClickableCustom,
// Scroll
ScrollableCustom,
};

View File

@@ -3,23 +3,27 @@ export const MainColor = {
darkblue: "#001D3D",
soft_darkblue: "#0e3763",
yellow: "#E1B525",
white: "#D4D0D0",
red: "#FF4B4C",
orange: "#FF7043",
green: "#4CAF4F",
white_gray: "#D4D0D0",
text_input: "#EDEBEBFF",
placeholder: "#999",
disabled: "#606360",
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

@@ -6,12 +6,32 @@ export {
ICON_SIZE_MEDIUM,
DRAWER_HEIGHT,
RADIUS_BUTTON,
ICON_SIZE_BUTTON,
PADDING_EXTRA_SMALL,
PADDING_SMALL,
PADDING_MEDIUM,
PADDING_LARGE,
};
// 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
const RADIUS_BUTTON = 50
// 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,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;

View File

@@ -0,0 +1,734 @@
const dummyMasterSubBidangBisnis = [
{
id: "TEK-01",
name: "Software Developer",
slug: "software_developer",
isActive: true,
createdAt: "2025-06-16T09:57:16.406Z",
updatedAt: "2025-06-16T09:57:16.406Z",
masterBidangBisnisId: "1",
},
{
id: "TEK-02",
name: "Web Developer",
slug: "web_developer",
isActive: true,
createdAt: "2025-06-16T09:57:16.408Z",
updatedAt: "2025-06-16T09:57:16.408Z",
masterBidangBisnisId: "1",
},
{
id: "TEK-03",
name: "Mobile App Developer",
slug: "mobile_app_developer",
isActive: true,
createdAt: "2025-06-16T09:57:16.411Z",
updatedAt: "2025-06-16T09:57:16.411Z",
masterBidangBisnisId: "1",
},
{
id: "TEK-04",
name: "Konsultan IT",
slug: "konsultan_it",
isActive: true,
createdAt: "2025-06-16T09:57:16.413Z",
updatedAt: "2025-06-16T09:57:16.413Z",
masterBidangBisnisId: "1",
},
{
id: "TEK-05",
name: "Digital Marketing",
slug: "digital_marketing",
isActive: true,
createdAt: "2025-06-16T09:57:16.415Z",
updatedAt: "2025-06-16T09:57:16.415Z",
masterBidangBisnisId: "1",
},
{
id: "TEK-06",
name: "Cybersecurity",
slug: "cybersecurity",
isActive: true,
createdAt: "2025-06-16T09:57:16.417Z",
updatedAt: "2025-06-16T09:57:16.417Z",
masterBidangBisnisId: "1",
},
{
id: "TEK-07",
name: "AI & Machine Learning Services",
slug: "ai_and_machine_learning_services",
isActive: true,
createdAt: "2025-06-16T09:57:16.419Z",
updatedAt: "2025-06-16T09:57:16.419Z",
masterBidangBisnisId: "1",
},
{
id: "TEK-08",
name: "Data Analyst/Data Scientist",
slug: "data_analyst_data_scientist",
isActive: true,
createdAt: "2025-06-16T09:57:16.421Z",
updatedAt: "2025-06-16T09:57:16.421Z",
masterBidangBisnisId: "1",
},
{
id: "TEK-09",
name: "Blockchain Developer",
slug: "blockchain_developer",
isActive: true,
createdAt: "2025-06-16T09:57:16.423Z",
updatedAt: "2025-06-16T09:57:16.423Z",
masterBidangBisnisId: "1",
},
{
id: "KUL-01",
name: "Restoran",
slug: "restoran",
isActive: true,
createdAt: "2025-06-16T09:57:16.426Z",
updatedAt: "2025-06-16T09:57:16.426Z",
masterBidangBisnisId: "2",
},
{
id: "KUL-02",
name: "Kafe",
slug: "kafe",
isActive: true,
createdAt: "2025-06-16T09:57:16.428Z",
updatedAt: "2025-06-16T09:57:16.428Z",
masterBidangBisnisId: "2",
},
{
id: "KUL-03",
name: "Warung Makan",
slug: "warung_makan",
isActive: true,
createdAt: "2025-06-16T09:57:16.431Z",
updatedAt: "2025-06-16T09:57:16.431Z",
masterBidangBisnisId: "2",
},
{
id: "KUL-04",
name: "Catering",
slug: "catering",
isActive: true,
createdAt: "2025-06-16T09:57:16.433Z",
updatedAt: "2025-06-16T09:57:16.433Z",
masterBidangBisnisId: "2",
},
{
id: "KUL-05",
name: "Food Truck",
slug: "food_truck",
isActive: true,
createdAt: "2025-06-16T09:57:16.435Z",
updatedAt: "2025-06-16T09:57:16.435Z",
masterBidangBisnisId: "2",
},
{
id: "KUL-06",
name: "Minuman Kekinian",
slug: "minuman_kekinian",
isActive: true,
createdAt: "2025-06-16T09:57:16.437Z",
updatedAt: "2025-06-16T09:57:16.437Z",
masterBidangBisnisId: "2",
},
{
id: "KUL-07",
name: "Toko Roti & Kue",
slug: "toko_roti_kue",
isActive: true,
createdAt: "2025-06-16T09:57:16.438Z",
updatedAt: "2025-06-16T09:57:16.438Z",
masterBidangBisnisId: "2",
},
{
id: "KUL-08",
name: "Supplier Bahan Makanan",
slug: "supplier_bahan_makanan",
isActive: true,
createdAt: "2025-06-16T09:57:16.440Z",
updatedAt: "2025-06-16T09:57:16.440Z",
masterBidangBisnisId: "2",
},
{
id: "FAS-01",
name: "Pakaian Dewasa & Anak",
slug: "pakaian_dewasa_anak",
isActive: true,
createdAt: "2025-06-16T09:57:16.444Z",
updatedAt: "2025-06-16T09:57:16.444Z",
masterBidangBisnisId: "3",
},
{
id: "FAS-02",
name: "Butik",
slug: "butik",
isActive: true,
createdAt: "2025-06-16T09:57:16.445Z",
updatedAt: "2025-06-16T09:57:16.445Z",
masterBidangBisnisId: "3",
},
{
id: "FAS-03",
name: "Desainer Fashion",
slug: "desainer_fashion",
isActive: true,
createdAt: "2025-06-16T09:57:16.448Z",
updatedAt: "2025-06-16T09:57:16.448Z",
masterBidangBisnisId: "3",
},
{
id: "FAS-04",
name: "Aksesoris & Perhiasan",
slug: "aksesoris_perhiasan",
isActive: true,
createdAt: "2025-06-16T09:57:16.449Z",
updatedAt: "2025-06-16T09:57:16.449Z",
masterBidangBisnisId: "3",
},
{
id: "FAS-05",
name: "Kosmetik",
slug: "kosmetik",
isActive: true,
createdAt: "2025-06-16T09:57:16.451Z",
updatedAt: "2025-06-16T09:57:16.451Z",
masterBidangBisnisId: "3",
},
{
id: "FAS-06",
name: "Skincare",
slug: "skincare",
isActive: true,
createdAt: "2025-06-16T09:57:16.453Z",
updatedAt: "2025-06-16T09:57:16.453Z",
masterBidangBisnisId: "3",
},
{
id: "FAS-07",
name: "Salon Kecantikan",
slug: "salon_kecantikan",
isActive: true,
createdAt: "2025-06-16T09:57:16.455Z",
updatedAt: "2025-06-16T09:57:16.455Z",
masterBidangBisnisId: "3",
},
{
id: "FAS-08",
name: "Barbershop",
slug: "barbershop",
isActive: true,
createdAt: "2025-06-16T09:57:16.456Z",
updatedAt: "2025-06-16T09:57:16.456Z",
masterBidangBisnisId: "3",
},
{
id: "OTO-01",
name: "Jual Beli Mobil/Motor",
slug: "jual_beli_mobil_motor",
isActive: true,
createdAt: "2025-06-16T09:57:16.461Z",
updatedAt: "2025-06-16T09:57:16.461Z",
masterBidangBisnisId: "4",
},
{
id: "OTO-02",
name: "Bengkel Mobil/Motor",
slug: "bengkel_mobil_motor",
isActive: true,
createdAt: "2025-06-16T09:57:16.463Z",
updatedAt: "2025-06-16T09:57:16.463Z",
masterBidangBisnisId: "4",
},
{
id: "OTO-03",
name: "Aksesori Kendaraan",
slug: "aksesori_kendaraan",
isActive: true,
createdAt: "2025-06-16T09:57:16.465Z",
updatedAt: "2025-06-16T09:57:16.465Z",
masterBidangBisnisId: "4",
},
{
id: "OTO-04",
name: "Rental Kendaraan",
slug: "rental_kendaraan",
isActive: true,
createdAt: "2025-06-16T09:57:16.466Z",
updatedAt: "2025-06-16T09:57:16.466Z",
masterBidangBisnisId: "4",
},
{
id: "OTO-05",
name: "Cuci Mobil/Motor",
slug: "cuci_mobil_motor",
isActive: true,
createdAt: "2025-06-16T09:57:16.468Z",
updatedAt: "2025-06-16T09:57:16.468Z",
masterBidangBisnisId: "4",
},
{
id: "OTO-06",
name: "Spare Part & Mesin Mobil",
slug: "spare_part_mesin_mobil",
isActive: true,
createdAt: "2025-06-16T09:57:16.469Z",
updatedAt: "2025-06-16T09:57:16.469Z",
masterBidangBisnisId: "4",
},
{
id: "INK-01",
name: "Fotografi & Videografi",
slug: "fotografi_videografi",
isActive: true,
createdAt: "2025-06-16T09:57:16.472Z",
updatedAt: "2025-06-16T09:57:16.472Z",
masterBidangBisnisId: "5",
},
{
id: "INK-02",
name: "Event Organizer",
slug: "event_organizer",
isActive: true,
createdAt: "2025-06-16T09:57:16.473Z",
updatedAt: "2025-06-16T09:57:16.473Z",
masterBidangBisnisId: "5",
},
{
id: "INK-03",
name: "Desain Grafis",
slug: "desain_grafis",
isActive: true,
createdAt: "2025-06-16T09:57:16.475Z",
updatedAt: "2025-06-16T09:57:16.475Z",
masterBidangBisnisId: "5",
},
{
id: "INK-04",
name: "Advertising & Branding",
slug: "advertising_branding",
isActive: true,
createdAt: "2025-06-16T09:57:16.476Z",
updatedAt: "2025-06-16T09:57:16.476Z",
masterBidangBisnisId: "5",
},
{
id: "INK-05",
name: "Jasa Percetakan",
slug: "jasa_percetakan",
isActive: true,
createdAt: "2025-06-16T09:57:16.477Z",
updatedAt: "2025-06-16T09:57:16.477Z",
masterBidangBisnisId: "5",
},
{
id: "INK-06",
name: "Dekorasi & Wedding Planner",
slug: "dekorasi_wedding_planner",
isActive: true,
createdAt: "2025-06-16T09:57:16.480Z",
updatedAt: "2025-06-16T09:57:16.480Z",
masterBidangBisnisId: "5",
},
{
id: "INK-07",
name: "Studio Musik & Produksi",
slug: "studio_musik_produksi",
isActive: true,
createdAt: "2025-06-16T09:57:16.481Z",
updatedAt: "2025-06-16T09:57:16.481Z",
masterBidangBisnisId: "5",
},
{
id: "KON-01",
name: "Kontraktor Bangunan",
slug: "kontraktor_bangunan",
isActive: true,
createdAt: "2025-06-16T09:57:16.484Z",
updatedAt: "2025-06-16T09:57:16.484Z",
masterBidangBisnisId: "6",
},
{
id: "KON-02",
name: "Arsitek",
slug: "arsitek",
isActive: true,
createdAt: "2025-06-16T09:57:16.486Z",
updatedAt: "2025-06-16T09:57:16.486Z",
masterBidangBisnisId: "6",
},
{
id: "KON-03",
name: "Desain Interior",
slug: "desain_interior",
isActive: true,
createdAt: "2025-06-16T09:57:16.487Z",
updatedAt: "2025-06-16T09:57:16.487Z",
masterBidangBisnisId: "6",
},
{
id: "KON-04",
name: "Supplier Material Bangunan",
slug: "supplier_material_bangunan",
isActive: true,
createdAt: "2025-06-16T09:57:16.489Z",
updatedAt: "2025-06-16T09:57:16.489Z",
masterBidangBisnisId: "6",
},
{
id: "KON-05",
name: "Tukang & Renovasi Rumah",
slug: "tukang_renovasi_rumah",
isActive: true,
createdAt: "2025-06-16T09:57:16.490Z",
updatedAt: "2025-06-16T09:57:16.490Z",
masterBidangBisnisId: "6",
},
{
id: "AGR-01",
name: "Perkebunan",
slug: "perkebunan",
isActive: true,
createdAt: "2025-06-16T09:57:16.493Z",
updatedAt: "2025-06-16T09:57:16.493Z",
masterBidangBisnisId: "7",
},
{
id: "AGR-02",
name: "Peternakan",
slug: "peternakan",
isActive: true,
createdAt: "2025-06-16T09:57:16.496Z",
updatedAt: "2025-06-16T09:57:16.496Z",
masterBidangBisnisId: "7",
},
{
id: "AGR-03",
name: "Perikanan",
slug: "perikanan",
isActive: true,
createdAt: "2025-06-16T09:57:16.497Z",
updatedAt: "2025-06-16T09:57:16.497Z",
masterBidangBisnisId: "7",
},
{
id: "AGR-04",
name: "Supplier Bibit & Pupuk",
slug: "supplier_bibit_pupuk",
isActive: true,
createdAt: "2025-06-16T09:57:16.498Z",
updatedAt: "2025-06-16T09:57:16.498Z",
masterBidangBisnisId: "7",
},
{
id: "AGR-05",
name: "Hasil Tani Organik",
slug: "hasil_tani_organik",
isActive: true,
createdAt: "2025-06-16T09:57:16.500Z",
updatedAt: "2025-06-16T09:57:16.500Z",
masterBidangBisnisId: "7",
},
{
id: "AGR-06",
name: "Alat & Mesin Pertanian",
slug: "alat_mesin_pertanian",
isActive: true,
createdAt: "2025-06-16T09:57:16.501Z",
updatedAt: "2025-06-16T09:57:16.501Z",
masterBidangBisnisId: "7",
},
{
id: "JAS-01",
name: "Jasa Kebersihan",
slug: "jasa_kebersihan",
isActive: true,
createdAt: "2025-06-16T09:57:16.504Z",
updatedAt: "2025-06-16T09:57:16.504Z",
masterBidangBisnisId: "8",
},
{
id: "JAS-02",
name: "Laundry",
slug: "laundry",
isActive: true,
createdAt: "2025-06-16T09:57:16.505Z",
updatedAt: "2025-06-16T09:57:16.505Z",
masterBidangBisnisId: "8",
},
{
id: "JAS-03",
name: "Penitipan Anak",
slug: "penitipan_anak",
isActive: true,
createdAt: "2025-06-16T09:57:16.506Z",
updatedAt: "2025-06-16T09:57:16.506Z",
masterBidangBisnisId: "8",
},
{
id: "JAS-04",
name: "Jasa Keamanan",
slug: "jasa_keamanan",
isActive: true,
createdAt: "2025-06-16T09:57:16.508Z",
updatedAt: "2025-06-16T09:57:16.508Z",
masterBidangBisnisId: "8",
},
{
id: "JAS-05",
name: "Jasa Pengiriman/Logistik",
slug: "jasa_pengiriman_logistik",
isActive: true,
createdAt: "2025-06-16T09:57:16.511Z",
updatedAt: "2025-06-16T09:57:16.511Z",
masterBidangBisnisId: "8",
},
{
id: "JAS-06",
name: "Jasa Ekspedisi",
slug: "jasa_ekspedisi",
isActive: true,
createdAt: "2025-06-16T09:57:16.513Z",
updatedAt: "2025-06-16T09:57:16.513Z",
masterBidangBisnisId: "8",
},
{
id: "JAS-07",
name: "Konsultan Bisnis",
slug: "konsultan_bisnis",
isActive: true,
createdAt: "2025-06-16T09:57:16.514Z",
updatedAt: "2025-06-16T09:57:16.514Z",
masterBidangBisnisId: "8",
},
{
id: "JAS-08",
name: "Jasa Hukum/Legal",
slug: "jasa_hukum_legal",
isActive: true,
createdAt: "2025-06-16T09:57:16.516Z",
updatedAt: "2025-06-16T09:57:16.516Z",
masterBidangBisnisId: "8",
},
{
id: "EDU-01",
name: "Bimbingan Belajar",
slug: "bimbingan_belajar",
isActive: true,
createdAt: "2025-06-16T09:57:16.518Z",
updatedAt: "2025-06-16T09:57:16.518Z",
masterBidangBisnisId: "9",
},
{
id: "EDU-02",
name: "Kursus Bahasa",
slug: "kursus_bahasa",
isActive: true,
createdAt: "2025-06-16T09:57:16.520Z",
updatedAt: "2025-06-16T09:57:16.520Z",
masterBidangBisnisId: "9",
},
{
id: "EDU-03",
name: "Pelatihan Digital/Skill",
slug: "pelatihan_digital_skill",
isActive: true,
createdAt: "2025-06-16T09:57:16.521Z",
updatedAt: "2025-06-16T09:57:16.521Z",
masterBidangBisnisId: "9",
},
{
id: "EDU-04",
name: "LPK (Lembaga Pelatihan Kerja)",
slug: "lpk_lembaga_pelatihan_kerja",
isActive: true,
createdAt: "2025-06-16T09:57:16.523Z",
updatedAt: "2025-06-16T09:57:16.523Z",
masterBidangBisnisId: "9",
},
{
id: "EDU-05",
name: "Homeschooling",
slug: "homeschooling",
isActive: true,
createdAt: "2025-06-16T09:57:16.525Z",
updatedAt: "2025-06-16T09:57:16.525Z",
masterBidangBisnisId: "9",
},
{
id: "KEU-01",
name: "Koperasi",
slug: "koperasi",
isActive: true,
createdAt: "2025-06-16T09:57:16.529Z",
updatedAt: "2025-06-16T09:57:16.529Z",
masterBidangBisnisId: "10",
},
{
id: "KEU-02",
name: "FinTEKh",
slug: "finTEKh",
isActive: true,
createdAt: "2025-06-16T09:57:16.530Z",
updatedAt: "2025-06-16T09:57:16.530Z",
masterBidangBisnisId: "10",
},
{
id: "KEU-03",
name: "Konsultan Keuangan",
slug: "konsultan_keuangan",
isActive: true,
createdAt: "2025-06-16T09:57:16.531Z",
updatedAt: "2025-06-16T09:57:16.531Z",
masterBidangBisnisId: "10",
},
{
id: "KEU-04",
name: "Investasi & Saham",
slug: "investasi_saham",
isActive: true,
createdAt: "2025-06-16T09:57:16.532Z",
updatedAt: "2025-06-16T09:57:16.532Z",
masterBidangBisnisId: "10",
},
{
id: "KEU-05",
name: "Asuransi",
slug: "asuransi",
isActive: true,
createdAt: "2025-06-16T09:57:16.534Z",
updatedAt: "2025-06-16T09:57:16.534Z",
masterBidangBisnisId: "10",
},
{
id: "KEU-06",
name: "Akuntan Publik",
slug: "akuntan_publik",
isActive: true,
createdAt: "2025-06-16T09:57:16.535Z",
updatedAt: "2025-06-16T09:57:16.535Z",
masterBidangBisnisId: "10",
},
{
id: "PER-01",
name: "Toko Kelontong",
slug: "toko_kelontong",
isActive: true,
createdAt: "2025-06-16T09:57:16.538Z",
updatedAt: "2025-06-16T09:57:16.538Z",
masterBidangBisnisId: "11",
},
{
id: "PER-02",
name: "Minimarket",
slug: "minimarket",
isActive: true,
createdAt: "2025-06-16T09:57:16.540Z",
updatedAt: "2025-06-16T09:57:16.540Z",
masterBidangBisnisId: "11",
},
{
id: "PER-03",
name: "Grosir & Distributor",
slug: "grosir_distributor",
isActive: true,
createdAt: "2025-06-16T09:57:16.541Z",
updatedAt: "2025-06-16T09:57:16.541Z",
masterBidangBisnisId: "11",
},
{
id: "PER-04",
name: "Dropshipper & Reseller",
slug: "dropshipper_reseller",
isActive: true,
createdAt: "2025-06-16T09:57:16.543Z",
updatedAt: "2025-06-16T09:57:16.543Z",
masterBidangBisnisId: "11",
},
{
id: "PER-05",
name: "Marketplace & E-commerce",
slug: "marketplace_e_commerce",
isActive: true,
createdAt: "2025-06-16T09:57:16.544Z",
updatedAt: "2025-06-16T09:57:16.544Z",
masterBidangBisnisId: "11",
},
{
id: "PER-06",
name: "Supplier Produk",
slug: "supplier_produk",
isActive: true,
createdAt: "2025-06-16T09:57:16.546Z",
updatedAt: "2025-06-16T09:57:16.546Z",
masterBidangBisnisId: "11",
},
{
id: "PAR-01",
name: "Agen Travel",
slug: "agen_travel",
isActive: true,
createdAt: "2025-06-16T09:57:16.549Z",
updatedAt: "2025-06-16T09:57:16.549Z",
masterBidangBisnisId: "12",
},
{
id: "PAR-02",
name: "Tour Guide",
slug: "tour_guide",
isActive: true,
createdAt: "2025-06-16T09:57:16.550Z",
updatedAt: "2025-06-16T09:57:16.550Z",
masterBidangBisnisId: "12",
},
{
id: "PAR-03",
name: "Villa & Penginapan",
slug: "villa_penginapan",
isActive: true,
createdAt: "2025-06-16T09:57:16.551Z",
updatedAt: "2025-06-16T09:57:16.551Z",
masterBidangBisnisId: "12",
},
{
id: "PAR-04",
name: "Homestay",
slug: "homestay",
isActive: true,
createdAt: "2025-06-16T09:57:16.552Z",
updatedAt: "2025-06-16T09:57:16.552Z",
masterBidangBisnisId: "12",
},
{
id: "PAR-05",
name: "Hotel",
slug: "hotel",
isActive: true,
createdAt: "2025-06-16T09:57:16.554Z",
updatedAt: "2025-06-16T09:57:16.554Z",
masterBidangBisnisId: "12",
},
{
id: "PAR-06",
name: "Sewa Motor/Travel",
slug: "sewa_motor_travel",
isActive: true,
createdAt: "2025-06-16T09:57:16.555Z",
updatedAt: "2025-06-16T09:57:16.555Z",
masterBidangBisnisId: "12",
},
{
id: "PAR-07",
name: "Sovenir & Oleh-Oleh",
slug: "sovenir_oleh_oleh",
isActive: true,
createdAt: "2025-06-16T09:57:16.557Z",
updatedAt: "2025-06-16T09:57:16.557Z",
masterBidangBisnisId: "12",
},
];
export default dummyMasterSubBidangBisnis;

0
lib/index.ts Normal file
View File

1242
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -18,6 +18,7 @@
"@react-navigation/native": "^7.1.6",
"@react-navigation/native-stack": "^7.3.21",
"@types/react-native-vector-icons": "^6.4.18",
"dayjs": "^1.11.13",
"expo": "53.0.17",
"expo-blur": "~14.1.5",
"expo-camera": "~16.1.10",
@@ -38,7 +39,9 @@
"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",
@@ -49,9 +52,9 @@
"devDependencies": {
"@babel/core": "^7.25.2",
"@types/react": "~19.0.10",
"typescript": "~5.8.3",
"eslint": "^9.25.0",
"eslint-config-expo": "~9.2.0"
"eslint-config-expo": "~9.2.0",
"typescript": "~5.8.3"
},
"private": true
}

View File

@@ -30,10 +30,12 @@ export default function LoginView() {
const id = randomAlfabet + randomNumber + fixNumber;
console.log("login user id :", id);
router.navigate("/verification");
// router.navigate(`/(application)/profile/${id}`);
// router.navigate("/(application)/home");
// router.navigate("/verification");
// router.navigate(`/(application)/(user)/profile/${id}`);
router.navigate("/(application)/(user)/home");
// router.navigate(`/(application)/profile/${id}/edit`);
// router.navigate(`/(application)/(user)/portofolio/${id}`)
// router.navigate(`/(application)/(image)/preview-image/${id}`);
}
return (
@@ -55,7 +57,7 @@ export default function LoginView() {
fontSize: 10,
fontWeight: "thin",
fontStyle: "italic",
color: MainColor.white,
color: MainColor.white_gray,
}}
>
powered by muku.id

View File

@@ -13,7 +13,7 @@ export default function RegisterView() {
const [username, setUsername] = useState("Bagas Banuna");
const handleRegister = () => {
console.log("Success register", username);
router.push("/(application)/home");
router.push("/(application)/(user)/home");
};
return (
<>

View File

@@ -0,0 +1,74 @@
import {
BaseBox,
Grid,
AvatarCustom,
TextCustom,
ClickableCustom,
Spacing,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { Entypo } from "@expo/vector-icons";
import { View } from "react-native";
export default function Forum_CommentarBoxSection({
data,
setOpenDrawer,
setStatus,
}: {
data: any;
setOpenDrawer: (value: boolean) => void;
setStatus: (value: string) => void;
}) {
return (
<>
<BaseBox>
<View>
<Grid>
<Grid.Col span={2}>
<AvatarCustom href={`/profile/${data.id}`} />
</Grid.Col>
<Grid.Col
span={8}
style={{
justifyContent: "center",
}}
>
<TextCustom>{data.name}</TextCustom>
</Grid.Col>
<Grid.Col
span={2}
style={{
justifyContent: "center",
}}
>
<ClickableCustom
onPress={() => {
setOpenDrawer(true);
setStatus(data.status);
}}
style={{
alignItems: "flex-end",
}}
>
<Entypo
name="dots-three-horizontal"
color={MainColor.white}
size={ICON_SIZE_SMALL}
/>
</ClickableCustom>
</Grid.Col>
</Grid>
<TextCustom>{data.deskripsi}</TextCustom>
<Spacing />
<View style={{ alignItems: "flex-end" }}>
<TextCustom>{data.date}</TextCustom>
</View>
</View>
</BaseBox>
</>
);
}

View File

@@ -0,0 +1,124 @@
import {
AvatarCustom,
BaseBox,
ClickableCustom,
Grid,
Spacing,
TextCustom,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { Entypo, Ionicons } from "@expo/vector-icons";
import { Href, router } from "expo-router";
import { View } from "react-native";
export default function Forum_BoxDetailSection({
data,
isTruncate,
setOpenDrawer,
setStatus,
href,
}: {
data: any;
isTruncate?: boolean;
setOpenDrawer: (value: boolean) => void;
setStatus: (value: string) => void;
href?: Href;
}) {
const deskripsiView = (
<View
style={{
backgroundColor: MainColor.soft_darkblue,
padding: 8,
borderRadius: 8,
}}
>
{isTruncate ? (
<TextCustom truncate={2}>{data.deskripsi}</TextCustom>
) : (
<TextCustom>{data.deskripsi}</TextCustom>
)}
</View>
);
return (
<>
<BaseBox>
<View>
<Grid>
<Grid.Col span={2}>
<AvatarCustom href={`/profile/${data.id}`} />
</Grid.Col>
<Grid.Col span={8}>
<TextCustom>{data.name}</TextCustom>
{data.status === "Open" ? (
<TextCustom bold size="small" color="green">
{data.status}
</TextCustom>
) : (
<TextCustom bold size="small" color="red">
{data.status}
</TextCustom>
)}
</Grid.Col>
<Grid.Col
span={2}
style={{
justifyContent: "center",
}}
>
<ClickableCustom
onPress={() => {
setOpenDrawer(true);
setStatus(data.status);
}}
style={{
alignItems: "flex-end",
}}
>
<Entypo
name="dots-three-horizontal"
color={MainColor.white}
size={ICON_SIZE_SMALL}
/>
</ClickableCustom>
</Grid.Col>
</Grid>
{href ? (
<ClickableCustom onPress={() => router.push(href as any)}>
{deskripsiView}
</ClickableCustom>
) : (
deskripsiView
)}
<Spacing height={10} />
<Grid>
<Grid.Col span={6}>
<View
style={{
flexDirection: "row",
alignItems: "center",
gap: 10,
}}
>
<Ionicons
name="chatbubble-outline"
size={ICON_SIZE_SMALL}
color={MainColor.white}
/>
<TextCustom>{data.jumlahBalas}</TextCustom>
</View>
</Grid.Col>
<Grid.Col span={6} style={{ alignItems: "flex-end" }}>
<TextCustom size="small"> {data.date}</TextCustom>
</Grid.Col>
</Grid>
</View>
</BaseBox>
</>
);
}

View File

@@ -0,0 +1,41 @@
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { Feather, Ionicons } from "@expo/vector-icons";
export { drawerItemsForumBeranda };
const drawerItemsForumBeranda = ({
id,
status,
}: {
id: string;
status: string;
}) => [
{
icon: (
<Feather name="edit" size={ICON_SIZE_SMALL} color={MainColor.white} />
),
label: "Edit posting",
path: `/forum/${id}/edit`,
},
{
icon:
status === "Open" ? (
<Ionicons name="open" size={ICON_SIZE_SMALL} color={MainColor.white} />
) : (
<Ionicons name="close" size={ICON_SIZE_SMALL} color={MainColor.white} />
),
label: status === "Open" ? "Buka forum" : "Tutup forum",
path: "",
color: status === "Open" ? MainColor.green : MainColor.orange,
},
{
icon: (
<Ionicons name="trash" size={ICON_SIZE_SMALL} color={MainColor.white} />
),
label: "Hapus",
path: "",
color: MainColor.red,
},
];

View File

@@ -0,0 +1,41 @@
import { IMenuDrawerItem } from "@/components/_Interface/types";
import MenuDrawerDynamicGrid from "@/components/Drawer/MenuDrawerDynamicGird";
import { router } from "expo-router";
import { drawerItemsForumBeranda } from "../ListPage";
export default function Forum_MenuDrawerBerandaSection({
id,
status,
setIsDrawerOpen,
setShowDeleteAlert,
setShowAlertStatus,
}: {
id: string;
status: string;
setIsDrawerOpen: (value: boolean) => void;
setShowDeleteAlert: (value: boolean) => void;
setShowAlertStatus: (value: boolean) => void;
}) {
const handlePress = (item: IMenuDrawerItem) => {
if (item.label === "Hapus") {
setShowDeleteAlert(true);
} else if (item.label === "Buka forum" || item.label === "Tutup forum") {
setShowAlertStatus(true);
} else {
router.push(item.path as any);
}
setIsDrawerOpen(false);
};
return (
<>
{/* Menu Items */}
<MenuDrawerDynamicGrid
data={drawerItemsForumBeranda({ id, status })}
columns={4} // Ubah ke 2 jika ingin 2 kolom per baris
onPressItem={handlePress}
/>
</>
);
}

View File

@@ -0,0 +1,104 @@
export {
listDummyDiscussionForum,
listDummyCommentarForum,
}
const listDummyDiscussionForum = [
{
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,
},
{
name: "Banuna",
status: "Close",
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: 8,
},
{
name: "Nusantara",
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: 5,
},
{
name: "Nabillah",
status: "Close",
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,
},
{
name: "Riyusa",
status: "Close",
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: 3,
},
{
name: "Nita",
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,
},
];
const listDummyCommentarForum = [
{
name: "Bagas",
status: "Delete",
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?",
},
{
name: "Banuna",
status: "Report",
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?",
},
{
name: "Nusantara",
status: "Delete",
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?",
},
{
name: "Nabillah",
status: "Report",
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?",
},
{
name: "Riyusa",
status: "Report",
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?",
},
{
name: "Nita",
status: "Delete",
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?",
},
];

View File

@@ -1,5 +1,5 @@
// import { ITabs } from "@/components/_Interface/types";
import Spacing from "@/components/_ShareComponent/Spacing";
import { StackCustom } from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { useNavigation } from "expo-router";
import React, { useEffect } from "react";
@@ -16,21 +16,16 @@ export default function UiHome() {
navigation.setOptions({});
}, [navigation]);
return (
<>
<ViewWrapper footerComponent={<TabSection tabs={tabsHome} />}>
{/* Content Image */}
<Home_ImageSection />
<Spacing height={10} />
<StackCustom>
<Home_ImageSection />
{/* Grid Section */}
<Home_FeatureSection />
<Spacing height={10} />
<Home_FeatureSection />
{/* Job Vacancy Section */}
<Home_BottomFeatureSection />
<Spacing height={20} />
<Home_BottomFeatureSection />
</StackCustom>
</ViewWrapper>
</>
);

View File

@@ -9,7 +9,7 @@ export default function Home_FeatureSection() {
<View style={stylesHome.gridContainer}>
<TouchableOpacity
style={stylesHome.gridItem}
onPress={() => router.push("/(application)/event/(tabs)")}
onPress={() => router.push("/(application)/(user)/event/(tabs)")}
>
<Ionicons name="analytics" size={48} color="white" />
<Text style={stylesHome.gridLabel}>Event</Text>

View File

@@ -0,0 +1,14 @@
import { BaseBox, MapCustom, StackCustom, TextCustom } from "@/components";
export default function Portofolio_BusinessLocation() {
return (
<>
<BaseBox>
<StackCustom>
<TextCustom bold>Lokasi Bisnis</TextCustom>
<MapCustom />
</StackCustom>
</BaseBox>
</>
);
}

View File

@@ -0,0 +1,14 @@
import { ButtonCustom } from "@/components";
import { MainColor } from "@/constants/color-palet";
import { Ionicons } from "@expo/vector-icons";
export default function Portofolio_ButtonDelete() {
const handleDelete = () => {
console.log("Delete");
};
return (
<ButtonCustom textColor={MainColor.white} iconLeft={<Ionicons name="trash-outline" size={20} color="white" />} onPress={handleDelete} backgroundColor={MainColor.red}>
Hapus
</ButtonCustom>
);
}

View File

@@ -0,0 +1,173 @@
import {
AvatarCustom,
BaseBox,
CenterCustom,
ClickableCustom,
Grid,
StackCustom,
TextCustom,
} from "@/components";
import DividerCustom from "@/components/Divider/DividerCustom";
import { AccentColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { FontAwesome, Ionicons } from "@expo/vector-icons";
import { router, useLocalSearchParams } from "expo-router";
import { View } from "react-native";
export default function Portofolio_Data() {
const { id } = useLocalSearchParams();
const listData = [
{
icon: (
<FontAwesome name="building-o" size={ICON_SIZE_SMALL} color="white" />
),
label: "PT.Bali Interakrtif Perkasa",
},
{
icon: (
<Ionicons name="call-outline" size={ICON_SIZE_SMALL} color="white" />
),
label: "+6282340374412",
},
{
icon: (
<Ionicons name="home-outline" size={ICON_SIZE_SMALL} color="white" />
),
label: "Jl. Raya Kuta No. 123, Bandung, Indonesia",
},
{
icon: (
<Ionicons name="list-outline" size={ICON_SIZE_SMALL} color="white" />
),
label: "Teknologia",
},
];
const listSubBidang = [
{
icon: (
<Ionicons
name="chevron-forward-outline"
size={ICON_SIZE_SMALL}
color="white"
/>
),
label: "Security System",
},
{
icon: (
<Ionicons
name="chevron-forward-outline"
size={ICON_SIZE_SMALL}
color="white"
/>
),
label: "Web Developers",
},
{
icon: (
<Ionicons
name="chevron-forward-outline"
size={ICON_SIZE_SMALL}
color="white"
/>
),
label: "Mobile Developers",
},
];
return (
<>
<BaseBox>
<StackCustom>
<Grid>
<Grid.Col span={6}>
<TextCustom bold>Data Bisnis</TextCustom>
</Grid.Col>
<Grid.Col span={6} style={{ alignItems: "flex-end" }}>
<TextCustom color="yellow">ID: {id}</TextCustom>
</Grid.Col>
</Grid>
{/* <ClickableCustom
style={{ backgroundColor: "blue" }}
onPress={() => {
router.navigate(`/(application)/(image)/preview-image/${id}`);
}}
>
<AvatarCustom size="xl" />
</ClickableCustom> */}
<ClickableCustom
style={{}}
onPress={() => {
router.navigate(`/(application)/(image)/preview-image/${id}`);
}}
>
<CenterCustom>
<AvatarCustom size="xl" />
</CenterCustom>
</ClickableCustom>
{/* <Spacing height={10}/> */}
<View>
{listData.map((item, index) => (
<Grid key={index}>
<Grid.Col span={2} style={{ alignItems: "center" }}>
{item.icon}
</Grid.Col>
<Grid.Col span={10}>
<TextCustom style={{ paddingLeft: 5 }}>
{item.label}
</TextCustom>
</Grid.Col>
</Grid>
))}
<View style={{ paddingLeft: 10 }}>
{listSubBidang.map((item, index) => (
<Grid key={index}>
<Grid.Col span={2} style={{ alignItems: "center" }}>
{item.icon}
</Grid.Col>
<Grid.Col span={10}>
<TextCustom style={{ paddingLeft: 5 }}>
{item.label}
</TextCustom>
</Grid.Col>
</Grid>
))}
</View>
</View>
<DividerCustom labelPosition="top" color={AccentColor.blue} />
<View>
<Grid>
<Grid.Col span={2} style={{ alignItems: "center" }}>
<Ionicons
name="pin-outline"
size={ICON_SIZE_SMALL}
color="white"
/>
</Grid.Col>
<Grid.Col span={10}>
<TextCustom bold style={{ paddingLeft: 5 }}>
Tentang Kami
</TextCustom>
</Grid.Col>
</Grid>
<TextCustom style={{ paddingInline: 10 }}>
Lorem ipsum, dolor sit amet consectetur adipisicing elit.
Doloremque, alias perspiciatis quis enim eos facilis sit est?
Doloremque, rerum. Cumque error asperiores harum temporibus
cupiditate ullam, id quibusdam! Harum, rerum!
</TextCustom>
</View>
</StackCustom>
</BaseBox>
</>
);
}

View File

@@ -0,0 +1,62 @@
import { IMenuDrawerItem } from "@/components/_Interface/types";
import { AccentColor } from "@/constants/color-palet";
import { ICON_SIZE_MEDIUM } from "@/constants/constans-value";
import { Ionicons, FontAwesome5, FontAwesome, Fontisto } from "@expo/vector-icons";
export const drawerItemsPortofolio = ({ id }: { id: string }): IMenuDrawerItem[] => [
{
icon: (
<Ionicons
name="create"
size={ICON_SIZE_MEDIUM}
color={AccentColor.white}
/>
),
label: "Edit portofolio",
path: `/(application)/portofolio/${id}/edit`,
},
{
icon: (
<Ionicons
name="camera"
size={ICON_SIZE_MEDIUM}
color={AccentColor.white}
/>
),
label: "Edit logo ",
path: `/(application)/portofolio/${id}/edit-logo`,
},
{
icon: (
<FontAwesome
name="id-card-o"
size={ICON_SIZE_MEDIUM}
color={AccentColor.white}
/>
),
label: "Edit social media ",
path: `/(application)/portofolio/${id}/edit-social-media`,
},
{
icon: (
<Fontisto
name="map-marker-alt"
size={ICON_SIZE_MEDIUM}
color={AccentColor.white}
/>
),
label: "Edit Map",
path: `/(application)/maps/${id}/edit`,
},
{
icon: (
<FontAwesome5
name="map-pin"
size={ICON_SIZE_MEDIUM}
color={AccentColor.white}
/>
),
label: "Custom Pin Map",
path: `/(application)/maps/${id}/custom-pin`,
},
];

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