Compare commits

..

24 Commits

Author SHA1 Message Date
08dfd62bfd Deskripsi:
Event:
Add menu drawer draft
Fix event layoutk, index (beranda) , kontribusi

Comp
Add share avatar-username

# No Issue
2025-07-22 10:35:47 +08:00
c8cc0f0232 deskripsi
Fix: page event/id/deteil menjadi event/id/status
Feature:
Add  screens/Event/AlertButtonStatusSection
Add app/(application)/(user)/event/[id]/[status]/

# No Issue"
2025-07-21 17:39:37 +08:00
b844a8151d deskripsi:
Fix event: layout dan event deatil
Feature: Button dot, edit screen

# No Issue
2025-07-21 15:23:18 +08:00
f9e96aa077 Deskripsi:
Fix: event beranda dan status > tampilan
Feature: create event dan halaman detail status
Fix: Basebox: Hide safearea kalau ada tabs
Fix: TextCustom: Tambah size xlarge
Fix: ScrollCustom penambhan props value

# No Issue
2025-07-18 16:30:56 +08:00
b8b1efc71e Deskripsi:
- New component: Datetime custom, datetime android, datetime ios
- Fix: event create input datetime

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

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

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

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

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

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

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

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

# No Issue
2025-07-11 17:34:04 +08:00
3301cf3d7a fix
deskripsi:
- fix page: user search
- fix global-style
- fix component Text Input dan ViewWrapper ( sekarang bisa menambahkan header )
# No Issue
2025-07-10 17:37:54 +08:00
6913e9e4b5 fix
deskripsi:
- fix page maps
- fix ViewWrapper : props styles di tambah
# No Issue
2025-07-10 16:57:49 +08:00
c5798b3127 feature & fix
deskripsi:
- new component Clickable
- new folder (image) untuk take picture dan imaga preview
- fix klik gambar
# No Issue
2025-07-10 16:47:02 +08:00
2c94638e27 fix
deskripsi:
- fix component : Menu drawer
- fix feature : list menui drawer pada portofolio & profile
# No Issue
2025-07-10 15:21:40 +08:00
b8ed577fea feature & fix
deskripsi:
- new component : Center
- fix component : Button > disable props
- feture : fix portofolio > edit, edit logo, edit sosmed
- fix feature : maps > edit map, custom-pin
- fix featue : profile > update photo
# No Issue
2025-07-10 15:03:52 +08:00
e68a18bb89 feature
deskripsi:
- komponen Map
# No Issue
2025-07-10 14:03:01 +08:00
862af44c03 feature
deskripsi:
- portofolio: detail bisnis, maps, media social
- new component divide
# No Issue
2025-07-10 11:58:23 +08:00
88 changed files with 5080 additions and 778 deletions

View File

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

View File

@@ -76,6 +76,13 @@ export default function UserLayout() {
),
}}
/>
<Stack.Screen
name="event/create"
options={{
title: "Tambah Event",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="event/detail/[id]"
@@ -85,11 +92,68 @@ export default function UserLayout() {
}}
/>
<Stack.Screen
name="event/[id]/edit"
options={{
title: "Edit Event",
headerLeft: () => <BackButton />,
}}
/>
{/* ========== Forum Section ========= */}
<Stack.Screen
name="forum/index"
name="forum/create"
options={{
title: "Forum",
title: "Tambah Diskusi",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="forum/[id]/edit"
options={{
title: "Edit Diskusi",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="forum/[id]/forumku"
options={{
title: "Forumku",
headerLeft: () => <BackButton icon={'close'} />,
}}
/>
<Stack.Screen
name="forum/[id]/index"
options={{
title: "Detail",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="forum/[id]/report-commentar"
options={{
title: "Laporkan Komentar",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="forum/[id]/other-report-commentar"
options={{
title: "Laporkan Komentar",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="forum/[id]/report-posting"
options={{
title: "Laporkan Diskusi",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="forum/[id]/other-report-posting"
options={{
title: "Laporkan Diskusi",
headerLeft: () => <BackButton />,
}}
/>

View File

@@ -1,6 +1,8 @@
import { MainColor } from "@/constants/color-palet";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { OS_IOS_HEIGHT, OS_ANDROID_HEIGHT } from "@/constants/constans-value";
import { FontAwesome5, Ionicons } from "@expo/vector-icons";
import { Tabs } from "expo-router";
import { Platform, View } from "react-native";
export default function EventLayout() {
return (
@@ -9,24 +11,26 @@ export default function EventLayout() {
headerShown: false,
tabBarActiveTintColor: MainColor.yellow,
tabBarInactiveTintColor: MainColor.white_gray,
tabBarStyle: {
backgroundColor: MainColor.darkblue,
},
// tabBarButton: HapticTab,
// tabBarBackground: BlurTabBarBackground,
// tabBarStyle: Platform.select({
// ios: {
// // Use a transparent background on iOS to show the blur effect
// position: "absolute",
// },
// default: {},
// }),
tabBarBackground: CustomTabBarBackground,
tabBarStyle: Platform.select({
ios: {
borderTopWidth: 0,
paddingTop: 5,
height: OS_IOS_HEIGHT,
},
android: {
borderTopWidth: 0,
paddingTop: 5,
height: OS_ANDROID_HEIGHT,
},
default: {},
}),
}}
>
<Tabs.Screen
name="index"
options={{
title: "Home",
title: "Beranda",
tabBarIcon: ({ color }) => (
<Ionicons size={20} name="home" color={color} />
),
@@ -62,3 +66,16 @@ export default function EventLayout() {
</Tabs>
);
}
function CustomTabBarBackground() {
return (
<View
style={{
flex: 1,
backgroundColor: MainColor.darkblue,
borderTopWidth: 1,
borderTopColor: AccentColor.blue,
}}
/>
);
}

View File

@@ -1,25 +1,43 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import {
AvatarUsernameAndOtherComponent,
BaseBox,
StackCustom,
TextCustom,
} from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { GStyles } from "@/styles/global-styles";
import FloatingButton from "@/components/Button/FloatingButton";
import { router } from "expo-router";
import { Text, TouchableHighlight, View } from "react-native";
export default function Event() {
const index = "test-id-event";
const status = "publish";
return (
<ViewWrapper>
<TouchableHighlight onPress={() => router.push("/event/detail/1")}>
<View
style={{
padding: 20,
backgroundColor: MainColor.darkblue,
borderRadius: 10,
borderColor: AccentColor.blue,
borderWidth: 1,
}}
>
<Text style={GStyles.textLabel}>Event</Text>
</View>
</TouchableHighlight>
<ViewWrapper
hideFooter
floatingButton={
<FloatingButton onPress={() => router.push("/event/create")} />
}
>
{Array.from({ length: 10 }).map((_, index) => (
<BaseBox key={index} href={`/event/${index}/${status}/detail-event`}>
<StackCustom gap={"xs"}>
<AvatarUsernameAndOtherComponent
avatarHref={`/profile/${index}`}
name="Lorem ipsum dolor sit"
/>
<TextCustom truncate bold>
Lorem ipsum dolor sit
</TextCustom>
<TextCustom truncate={2}>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Porro sed
doloremque tempora soluta. Dolorem ex quidem ipsum tempora, ipsa,
obcaecati quia suscipit numquam, voluptates commodi porro impedit
natus quos doloremque!
</TextCustom>
</StackCustom>
</BaseBox>
))}
</ViewWrapper>
);
}

View File

@@ -1,11 +1,46 @@
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { GStyles } from "@/styles/global-styles";
import { Text } from "react-native";
import {
AvatarCustom,
AvatarUsernameAndOtherComponent,
BaseBox,
Grid,
StackCustom,
TextCustom,
ViewWrapper
} from "@/components";
export default function Kontribusi() {
return (
<ViewWrapper>
<Text style={GStyles.textLabel}>Kontribusi</Text>
{Array.from({ length: 10 }).map((_, index) => (
<BaseBox key={index} href={`/event/${index}/publish/detail-event`}>
<StackCustom>
<AvatarUsernameAndOtherComponent
avatarHref={`/profile/${index}`}
rightComponent={
<TextCustom truncate>
{new Date().toDateString().split(" ")[2] +
", " +
new Date().toDateString().split(" ")[1] +
" " +
new Date().toDateString().split(" ")[3]}
</TextCustom>
}
/>
<TextCustom bold align="center" size="xlarge">
Judul Event Disini
</TextCustom>
<Grid>
{Array.from({ length: 4 }).map((_, index2) => (
<Grid.Col span={3} key={index2}>
<AvatarCustom size="sm" href={`/profile/${index2}`} />
</Grid.Col>
))}
</Grid>
</StackCustom>
</BaseBox>
))}
</ViewWrapper>
);
}

View File

@@ -1,11 +1,63 @@
import {
BaseBox,
Grid,
ScrollableCustom,
StackCustom,
TextCustom,
} from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { GStyles } from "@/styles/global-styles";
import { Text } from "react-native";
import { masterStatus } from "@/lib/dummy-data/_master/status";
import { useState } from "react";
export default function Status() {
const id = "test-id-event";
const [activeCategory, setActiveCategory] = useState<string | null>(
"publish"
);
const handlePress = (item: any) => {
setActiveCategory(item.value);
// tambahkan logika lain seperti filter dsb.
};
const scrollComponent = (
<ScrollableCustom
data={masterStatus.map((e, i) => ({
id: i,
label: e.label,
value: e.value,
}))}
onButtonPress={handlePress}
activeId={activeCategory as any}
/>
);
return (
<ViewWrapper>
<Text style={GStyles.textLabel}>Status</Text>
<ViewWrapper headerComponent={scrollComponent}>
<BaseBox href={`/event/${id}/${activeCategory}/detail-event`}>
<StackCustom gap={"xs"}>
<Grid>
<Grid.Col span={8}>
<TextCustom truncate bold>
Lorem ipsum,{" "}
<TextCustom color="green">{activeCategory}</TextCustom> dolor
sit amet consectetur adipisicing elit.
</TextCustom>
</Grid.Col>
<Grid.Col span={4} style={{ alignItems: "flex-end" }}>
<TextCustom>{new Date().toLocaleDateString()}</TextCustom>
</Grid.Col>
</Grid>
<TextCustom truncate={2}>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consectetur
eveniet ab eum ducimus tempore a quia deserunt quisquam. Tempora,
atque. Aperiam minima asperiores dicta perferendis quis adipisci,
dolore optio porro!
</TextCustom>
</StackCustom>
</BaseBox>
</ViewWrapper>
);
}

View File

@@ -0,0 +1,117 @@
import {
BaseBox,
DotButton,
DrawerCustom,
Grid,
MenuDrawerDynamicGrid,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import LeftButtonCustom from "@/components/Button/BackButton";
import Event_AlertButtonStatusSection from "@/screens/Event/AlertButtonStatusSection";
import Event_ButtonStatusSection from "@/screens/Event/ButtonStatusSection";
import { menuDrawerDraftEvent } from "@/screens/Event/menuDrawerDraft";
import { router, Stack, useLocalSearchParams } from "expo-router";
import { useState } from "react";
export default function EventDetail() {
const { id, status } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false);
const [openAlert, setOpenAlert] = useState(false);
const [openDeleteAlert, setOpenDeleteAlert] = useState(false);
const handlePress = (item: IMenuDrawerItem) => {
console.log("PATH >> ", item.path);
router.navigate(item.path as any);
setOpenDrawer(false);
};
return (
<>
<Stack.Screen
options={{
title: `Detail ${status === "publish" ? "" : status}`,
headerLeft: () => <LeftButtonCustom />,
headerRight: () =>
status === "draft" ? (
<DotButton onPress={() => setOpenDrawer(true)} />
) : null,
}}
/>
<ViewWrapper>
<BaseBox>
<StackCustom>
<TextCustom bold align="center" size="xlarge">
Judul event {status}
</TextCustom>
{listData.map((item, index) => (
<Grid key={index}>
<Grid.Col span={4}>
<TextCustom bold>{item.title}</TextCustom>
</Grid.Col>
<Grid.Col span={8}>
<TextCustom>{item.value}</TextCustom>
</Grid.Col>
</Grid>
))}
</StackCustom>
</BaseBox>
<Event_ButtonStatusSection
status={status as string}
onOpenAlert={setOpenAlert}
onOpenDeleteAlert={setOpenDeleteAlert}
/>
<Spacing />
</ViewWrapper>
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
height={250}
>
<MenuDrawerDynamicGrid
data={menuDrawerDraftEvent({ id: id as string })}
columns={4}
onPressItem={handlePress}
/>
</DrawerCustom>
<Event_AlertButtonStatusSection
id={id as string}
status={status as string}
openAlert={openAlert}
setOpenAlert={setOpenAlert}
openDeleteAlert={openDeleteAlert}
setOpenDeleteAlert={setOpenDeleteAlert}
/>
</>
);
}
const listData = [
{
title: "Lokasi",
value:
"Lorem ipsum dolor sit amet consectetur adipisicing elit. Consectetur eveniet ab eum ducimus tempore a quia deserunt quisquam. Tempora, atque. Aperiam minima asperiores dicta perferendis quis adipisci, dolore optio porro!",
},
{
title: "Tipe Acara",
value: "Workshop",
},
{
title: "Tanggal Mulai",
value: "Senin, 18 Juli 2025, 10:00 WIB",
},
{
title: "Tanggal Berakhir",
value: "Selasa, 19 Juli 2025, 12:00 WIB",
},
{
title: "Deskripsi",
value:
"Lorem ipsum dolor sit amet consectetur adipisicing elit. Consectetur eveniet ab eum ducimus tempore a quia deserunt quisquam. Tempora, atque. Aperiam minima asperiores dicta perferendis quis adipisci, dolore optio porro!",
},
];

View File

@@ -0,0 +1,13 @@
import { StackCustom, TextCustom, ViewWrapper } from "@/components";
export default function EventEdit() {
return (
<>
<ViewWrapper>
<StackCustom>
<TextCustom>Edit Event</TextCustom>
</StackCustom>
</ViewWrapper>
</>
);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,11 +1,128 @@
import { TextCustom, ViewWrapper } from "@/components";
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 (
<>
<ViewWrapper>
<TextCustom>Forum</TextCustom>
<Stack.Screen
options={{
title: "Forum",
headerLeft: () => <BackButton />,
headerRight: () => <AvatarCustom href={`/forum/${id}/forumku`} />,
}}
/>
<ViewWrapper
headerComponent={
<TextInputCustom
iconLeft={
<Ionicons
name="search-outline"
size={ICON_SIZE_SMALL}
color={MainColor.placeholder}
/>
}
placeholder="Cari topik forum..."
borderRadius={50}
containerStyle={{ marginBottom: 0 }}
/>
}
floatingButton={
<FloatingButton
onPress={() =>
router.navigate("/(application)/(user)/forum/create")
}
/>
}
>
{listDummyDiscussionForum.map((e, i) => (
<Forum_BoxDetailSection
key={i}
data={e}
setOpenDrawer={setOpenDrawer}
setStatus={setStatus}
isTruncate={true}
href={`/forum/${id}`}
/>
))}
</ViewWrapper>
<DrawerCustom
height={350}
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
>
<Forum_MenuDrawerBerandaSection
id={id}
status={status}
setIsDrawerOpen={() => {
setOpenDrawer(false);
}}
setShowDeleteAlert={setDeleteAlert}
setShowAlertStatus={setAlertStatus}
/>
</DrawerCustom>
{/* Alert Status */}
<AlertCustom
isVisible={alertStatus}
title="Ubah Status Forum"
message="Apakah Anda yakin ingin mengubah status forum ini?"
onLeftPress={() => {
setOpenDrawer(false);
setAlertStatus(false);
console.log("Batal");
}}
onRightPress={() => {
setOpenDrawer(false);
setAlertStatus(false);
console.log("Ubah status forum");
}}
textLeft="Batal"
textRight="Ubah"
colorRight={MainColor.green}
/>
{/* Alert Delete */}
<AlertCustom
isVisible={deleteAlert}
title="Hapus Forum"
message="Apakah Anda yakin ingin menghapus forum ini?"
onLeftPress={() => {
setOpenDrawer(false);
setDeleteAlert(false);
console.log("Batal");
}}
onRightPress={() => {
setOpenDrawer(false);
setDeleteAlert(false);
console.log("Hapus forum");
}}
textLeft="Batal"
textRight="Hapus"
colorRight={MainColor.red}
/>
</>
);
}

View File

@@ -1,10 +1,54 @@
import { TextCustom, ViewWrapper } from "@/components";
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>
<TextCustom>Maps Custom Pin</TextCustom>
<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

@@ -1,11 +1,59 @@
import { TextCustom, ViewWrapper } from "@/components";
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>
<TextCustom>Maps Edit</TextCustom>
</ViewWrapper>
</>
<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

@@ -18,7 +18,7 @@ export default function MapsCreate() {
<BoxButtonOnFooter>
<ButtonCustom
onPress={() => {
console.log("Simpan");
console.log(`Simpan maps ${id}`);
router.replace(`/portofolio/${id}`);
}}
>

View File

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

View File

@@ -1,9 +1,111 @@
import { TextCustom, ViewWrapper } from "@/components";
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 = [
{ value: "all", label: "Semua" },
{ value: "event", label: "Event" },
{ value: "job", label: "Job" },
{ value: "voting", label: "Voting" },
{ value: "donasi", label: "Donasi" },
{ value: "investasi", label: "Investasi" },
{ value: "forum", label: "Forum" },
{ value: "collaboration", label: "Collaboration" },
];
const selectedCategory = (value: string) => {
const category = categories.find((c) => c.value === value);
return category?.label;
};
const BoxNotification = ({
index,
activeCategory,
}: {
index: number;
activeCategory: string | null;
}) => {
return (
<>
<BaseBox
onPress={() =>
console.log(
"Notification >",
selectedCategory(activeCategory as string)
)
}
>
<StackCustom>
<TextCustom bold>
# {selectedCategory(activeCategory as string)}
</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<string | null>("all");
const handlePress = (item: any) => {
setActiveCategory(item.value);
// tambahkan logika lain seperti filter dsb.
};
return (
<ViewWrapper>
<TextCustom>Notifications</TextCustom>
<ViewWrapper
headerComponent={
<ScrollableCustom
data={categories.map((e, i) => ({
id: i,
label: e.label,
value: e.value,
}))}
onButtonPress={handlePress}
activeId={activeCategory as string}
/>
}
>
{Array.from({ length: 20 }).map((e, i) => (
<View key={i}>
<BoxNotification index={i} activeCategory={activeCategory as any} />
</View>
))}
</ViewWrapper>
);
}

View File

@@ -155,7 +155,7 @@ export default function PortofolioCreate() {
icon="upload"
onPress={() => {
console.log("Upload logo >>", id);
router.navigate(`/(application)/take-picture/${id}`);
router.navigate(`/(application)/(image)/take-picture/${id}`);
}}
>
Upload

View File

@@ -1,10 +1,47 @@
import { TextCustom, ViewWrapper } from "@/components";
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>
<TextCustom>Portofolio Edit Logo</TextCustom>
<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

@@ -1,10 +1,35 @@
import { TextCustom, ViewWrapper } from "@/components";
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>
<TextCustom>Portofolio Edit Social Media</TextCustom>
<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

@@ -1,10 +1,150 @@
import { TextCustom, ViewWrapper } from "@/components";
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>
<TextCustom>Portofolio Edit</TextCustom>
<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

@@ -1,21 +1,20 @@
import { DrawerCustom } from "@/components";
import { AlertCustom, DrawerCustom } from "@/components";
import LeftButtonCustom from "@/components/Button/BackButton";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { MainColor } from "@/constants/color-palet";
import { drawerItemsPortofolio } from "@/screens/Portofolio/ListPage";
import Portofolio_MenuDrawerSection from "@/screens/Portofolio/MenuDrawer";
import PorfofolioSection from "@/screens/Portofolio/PorfofolioSection";
import { GStyles } from "@/styles/global-styles";
import { Ionicons } from "@expo/vector-icons";
import { Stack, useLocalSearchParams } from "expo-router";
import { Stack, useLocalSearchParams, router } from "expo-router";
import { useState } from "react";
import {
Text,
TouchableOpacity
} from "react-native";
import { TouchableOpacity } from "react-native";
export default function Portofolio() {
const { id } = useLocalSearchParams();
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
const [deleteAlert, setDeleteAlert] = useState(false);
const openDrawer = () => {
setIsDrawerOpen(true);
@@ -44,7 +43,7 @@ export default function Portofolio() {
headerTitleStyle: GStyles.headerTitleStyle,
}}
/>
<Text style={GStyles.textLabel}>Portofolio {id}</Text>
<PorfofolioSection setShowDeleteAlert={setDeleteAlert} />
</ViewWrapper>
{/* Drawer Komponen Eksternal */}
@@ -58,6 +57,22 @@ export default function Portofolio() {
setIsDrawerOpen={setIsDrawerOpen}
/>
</DrawerCustom>
{/* Alert Delete */}
<AlertCustom
isVisible={deleteAlert}
onLeftPress={() => setDeleteAlert(false)}
onRightPress={() => {
setDeleteAlert(false);
console.log("Hapus portofolio");
router.back();
}}
title="Hapus Portofolio"
message="Apakah Anda yakin ingin menghapus portofolio ini?"
textLeft="Batal"
textRight="Hapus"
colorRight={MainColor.red}
/>
</>
);
}

View File

@@ -103,14 +103,7 @@ export default function CreateProfile() {
required
onChange={(value) => setData({ ...(data as any), gender: value })}
/>
<TextInputCustom
required
label="Alamat"
placeholder="Masukkan alamat"
value={data.address}
onChangeText={(text) => setData({ ...data, address: text })}
/>
{/* <Spacing /> */}
<Spacing />
</StackCustom>
</ViewWrapper>
);

View File

@@ -22,15 +22,8 @@ export default function ProfileEdit() {
});
const options = [
{ label: "React", value: "react" },
{ label: "Vue", value: "vue" },
{ label: "Angular", value: "angular" },
{ label: "Svelte", value: "svelte" },
{ label: "Next.js", value: "nextjs" },
{ label: "Nuxt.js", value: "nuxtjs" },
{ label: "Remix", value: "remix" },
{ label: "Sapper", value: "sapper" },
{ label: "SvelteKit", value: "sveltekit" },
{ label: "Laki-laki", value: "laki-laki" },
{ label: "Perempuan", value: "perempuan" },
];
const handleSave = () => {
@@ -59,16 +52,6 @@ export default function ProfileEdit() {
}
>
<StackCustom gap={"xs"}>
<SelectCustom
label="Framework"
placeholder="Pilih framework favoritmu"
data={options}
value={data.selectedValue}
onChange={(value) => {
setData({ ...(data as any), selectedValue: value });
}}
/>
<TextInputCustom
label="Nama"
placeholder="Nama"
@@ -96,6 +79,16 @@ export default function ProfileEdit() {
}}
required
/>
<SelectCustom
required
label="Jenis Kelamin"
placeholder="Pilih jenis kelamin"
data={options}
value={data.selectedValue}
onChange={(value) => {
setData({ ...(data as any), selectedValue: value });
}}
/>
</StackCustom>
</ViewWrapper>
);

View File

@@ -1,13 +1,12 @@
import {
AvatarCustom,
BaseBox,
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
} from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import DUMMY_IMAGE from "@/constants/dummy-image-value";
import { router, useLocalSearchParams } from "expo-router";
import { Image } from "react-native";
export default function UpdatePhotoProfile() {
const { id } = useLocalSearchParams();
@@ -28,11 +27,7 @@ export default function UpdatePhotoProfile() {
<BaseBox
style={{ alignItems: "center", justifyContent: "center", height: 250 }}
>
<Image
source={DUMMY_IMAGE.avatar}
resizeMode="cover"
style={{ width: 200, height: 200 }}
/>
<AvatarCustom size="xl" />
</BaseBox>
<ButtonCenteredOnly

View File

@@ -1,9 +1,100 @@
import { TextCustom, ViewWrapper } from "@/components";
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>
<TextCustom>User Search</TextCustom>
</ViewWrapper>
<>
<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,7 +1,6 @@
import { MainColor } from "@/constants/color-palet";
import { BackButton } from "@/components";
import { HeaderStyles } from "@/styles/header-styles";
import { Ionicons } from "@expo/vector-icons";
import { router, Stack } from "expo-router";
import { Stack } from "expo-router";
export default function ApplicationLayout() {
return (
@@ -11,17 +10,19 @@ export default function ApplicationLayout() {
{/* 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

@@ -5,12 +5,14 @@
"name": "hipmi-mobile",
"dependencies": {
"@expo/vector-icons": "^14.1.0",
"@react-native-community/datetimepicker": "8.4.1",
"@react-navigation/bottom-tabs": "^7.4.2",
"@react-navigation/drawer": "^7.5.2",
"@react-navigation/elements": "^2.3.8",
"@react-navigation/native": "^7.1.6",
"@react-navigation/native-stack": "^7.3.21",
"@types/react-native-vector-icons": "^6.4.18",
"dayjs": "^1.11.13",
"expo": "53.0.17",
"expo-blur": "~14.1.5",
"expo-camera": "~16.1.10",
@@ -31,7 +33,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 +241,8 @@
"@babel/types": ["@babel/types@7.27.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q=="],
"@callstack/react-theme-provider": ["@callstack/react-theme-provider@3.0.9", "", { "dependencies": { "deepmerge": "^3.2.0", "hoist-non-react-statics": "^3.3.0" }, "peerDependencies": { "react": ">=16.3.0" } }, "sha512-tTQ0uDSCL0ypeMa8T/E9wAZRGKWj8kXP7+6RYgPTfOPs9N07C9xM8P02GJ3feETap4Ux5S69D9nteq9mEj86NA=="],
"@egjs/hammerjs": ["@egjs/hammerjs@2.0.17", "", { "dependencies": { "@types/hammerjs": "^2.0.36" } }, "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A=="],
"@emnapi/core": ["@emnapi/core@1.4.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" } }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="],
@@ -367,6 +373,8 @@
"@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w=="],
"@react-native-community/datetimepicker": ["@react-native-community/datetimepicker@8.4.1", "", { "dependencies": { "invariant": "^2.2.4" }, "peerDependencies": { "expo": ">=52.0.0", "react": "*", "react-native": "*", "react-native-windows": "*" }, "optionalPeers": ["expo", "react-native-windows"] }, "sha512-DrK+CUS5fZnz8dhzBezirkzQTcNDdaXer3oDLh0z4nc2tbdIdnzwvXCvi8IEOIvleoc9L95xS5tKUl0/Xv71Mg=="],
"@react-native/assets-registry": ["@react-native/assets-registry@0.79.5", "", {}, "sha512-N4Kt1cKxO5zgM/BLiyzuuDNquZPiIgfktEQ6TqJ/4nKA8zr4e8KJgU6Tb2eleihDO4E24HmkvGc73naybKRz/w=="],
"@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.79.5", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@react-native/codegen": "0.79.5" } }, "sha512-Rt/imdfqXihD/sn0xnV4flxxb1aLLjPtMF1QleQjEhJsTUPpH4TFlfOpoCvsrXoDl4OIcB1k4FVM24Ez92zf5w=="],
@@ -423,6 +431,8 @@
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
"@types/geojson": ["@types/geojson@7946.0.16", "", {}, "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="],
"@types/graceful-fs": ["@types/graceful-fs@4.1.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ=="],
"@types/hammerjs": ["@types/hammerjs@2.0.46", "", {}, "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw=="],
@@ -697,6 +707,8 @@
"data-view-byte-offset": ["data-view-byte-offset@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" } }, "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ=="],
"dayjs": ["dayjs@1.11.13", "", {}, "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="],
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
"decode-uri-component": ["decode-uri-component@0.2.2", "", {}, "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ=="],
@@ -1373,8 +1385,12 @@
"react-native-is-edge-to-edge": ["react-native-is-edge-to-edge@1.1.7", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-EH6i7E8epJGIcu7KpfXYXiV2JFIYITtq+rVS8uEb+92naMRBdxhTuS8Wn2Q7j9sqyO0B+Xbaaf9VdipIAmGW4w=="],
"react-native-maps": ["react-native-maps@1.20.1", "", { "dependencies": { "@types/geojson": "^7946.0.13" }, "peerDependencies": { "react": ">= 17.0.1", "react-native": ">= 0.64.3", "react-native-web": ">= 0.11" }, "optionalPeers": ["react-native-web"] }, "sha512-NZI3B5Z6kxAb8gzb2Wxzu/+P2SlFIg1waHGIpQmazDSCRkNoHNY4g96g+xS0QPSaG/9xRBbDNnd2f2/OW6t6LQ=="],
"react-native-otp-entry": ["react-native-otp-entry@1.8.5", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-TZNkIuUzZKAAWrC8X/A22ZHJdycLysxUNysrGf0yTmDLRUyf4zLXwVFcDYUcRNe763Hjaf5qvtKGILb6lDGzoA=="],
"react-native-paper": ["react-native-paper@5.14.5", "", { "dependencies": { "@callstack/react-theme-provider": "^3.0.9", "color": "^3.1.2", "use-latest-callback": "^0.2.3" }, "peerDependencies": { "react": "*", "react-native": "*", "react-native-safe-area-context": "*" } }, "sha512-eaIH5bUQjJ/mYm4AkI6caaiyc7BcHDwX6CqNDi6RIxfxfWxROsHpll1oBuwn/cFvknvA8uEAkqLk/vzVihI3AQ=="],
"react-native-reanimated": ["react-native-reanimated@3.17.5", "", { "dependencies": { "@babel/plugin-transform-arrow-functions": "^7.0.0-0", "@babel/plugin-transform-class-properties": "^7.0.0-0", "@babel/plugin-transform-classes": "^7.0.0-0", "@babel/plugin-transform-nullish-coalescing-operator": "^7.0.0-0", "@babel/plugin-transform-optional-chaining": "^7.0.0-0", "@babel/plugin-transform-shorthand-properties": "^7.0.0-0", "@babel/plugin-transform-template-literals": "^7.0.0-0", "@babel/plugin-transform-unicode-regex": "^7.0.0-0", "@babel/preset-typescript": "^7.16.7", "convert-source-map": "^2.0.0", "invariant": "^2.2.4", "react-native-is-edge-to-edge": "1.1.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0", "react": "*", "react-native": "*" } }, "sha512-SxBK7wQfJ4UoWoJqQnmIC7ZjuNgVb9rcY5Xc67upXAFKftWg0rnkknTw6vgwnjRcvYThrjzUVti66XoZdDJGtw=="],
"react-native-safe-area-context": ["react-native-safe-area-context@5.4.0", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-JaEThVyJcLhA+vU0NU8bZ0a1ih6GiF4faZ+ArZLqpYbL6j7R3caRqj+mE3lEtKCuHgwjLg3bCxLL1GPUJZVqUA=="],
@@ -1707,6 +1723,8 @@
"@babel/traverse--for-generate-function-map/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
"@callstack/react-theme-provider/deepmerge": ["deepmerge@3.3.0", "", {}, "sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA=="],
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
@@ -1887,6 +1905,8 @@
"react-native/semver": ["semver@7.7.2", "", { "bin": "bin/semver.js" }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"react-native-paper/color": ["color@3.2.1", "", { "dependencies": { "color-convert": "^1.9.3", "color-string": "^1.6.0" } }, "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA=="],
"react-native-vector-icons/yargs": ["yargs@16.2.0", "", { "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw=="],
"react-native-web/@react-native/normalize-colors": ["@react-native/normalize-colors@0.74.89", "", {}, "sha512-qoMMXddVKVhZ8PA1AbUCk83trpd6N+1nF2A6k1i6LsQObyS92fELuk8kU/lQs6M7BsMHwqyLCpQJ1uFgNvIQXg=="],
@@ -2007,6 +2027,8 @@
"ora/chalk/supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="],
"react-native-paper/color/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
"react-native-vector-icons/yargs/cliui": ["cliui@7.0.4", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" } }, "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ=="],
"react-native-vector-icons/yargs/yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="],
@@ -2043,6 +2065,8 @@
"ora/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
"react-native-paper/color/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
"react-native-vector-icons/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"serve-static/send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],

View File

@@ -1,27 +1,45 @@
import { AccentColor } from "@/constants/color-palet";
import { StyleProp, TouchableHighlight, View, ViewStyle } from "react-native";
import {
PADDING_EXTRA_SMALL,
PADDING_MEDIUM,
PADDING_SMALL,
} from "@/constants/constans-value";
import { Href, router } from "expo-router";
import {
StyleProp,
TouchableHighlight,
TouchableOpacity,
View,
ViewStyle,
} from "react-native";
interface BaseBoxProps {
children: React.ReactNode;
style?: StyleProp<ViewStyle>;
href?: Href;
onPress?: () => void;
marginBottom?: number;
padding?: number;
paddingInline?: number;
paddingBlock?: number;
}
export default function BaseBox({
children,
style,
href,
onPress,
marginBottom = 16,
padding = 12,
marginBottom = PADDING_MEDIUM,
paddingBlock = PADDING_MEDIUM,
paddingInline = PADDING_SMALL,
}: BaseBoxProps) {
return (
<>
{onPress ? (
<TouchableHighlight
onPress={onPress}
{onPress || href ? (
<TouchableOpacity
activeOpacity={0.7}
onPress={href ? () => router.navigate(href) : onPress}
style={[
{
backgroundColor: AccentColor.darkblue,
@@ -29,14 +47,14 @@ export default function BaseBox({
borderWidth: 1,
borderRadius: 10,
marginBottom,
padding,
paddingBlock,
paddingInline,
},
style,
]}
// activeOpacity={0.7}
>
<View>{children}</View>
</TouchableHighlight>
</TouchableOpacity>
) : (
<View
style={[
@@ -46,7 +64,8 @@ export default function BaseBox({
borderWidth: 1,
borderRadius: 10,
marginBottom,
padding,
paddingBlock,
paddingInline,
},
style,
]}

View File

@@ -3,19 +3,33 @@ import { Ionicons } from "@expo/vector-icons";
import { Href, router } from "expo-router";
/**
*
*
* @param path - path to navigate to ?
* @default router.back()
* @returns if path : router.replace(path) else router.back()
*/
const LeftButtonCustom = ({path}: {path?: Href}) => {
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={() => path ? router.replace(path) : router.back()}
/>
<>
{iconCustom ? (
iconCustom
) : (
<Ionicons
name={icon}
size={20}
color={MainColor.yellow}
onPress={() => (path ? router.replace(path) : router.back())}
/>
)}
</>
);
};

View File

@@ -5,6 +5,7 @@ import { StyleProp, Text, TouchableOpacity, ViewStyle } from "react-native";
import { radiusMap } from "@/constants/radius-value";
import { MainColor } from "@/constants/color-palet";
import { stylesButton } from "./buttonCustomStyles";
import { Href, router } from "expo-router";
// Import radiusMap
@@ -13,6 +14,7 @@ type RadiusType = keyof typeof radiusMap | number;
interface ButtonProps {
children?: React.ReactNode;
href?: Href;
onPress?: () => void;
title?: string;
backgroundColor?: string;
@@ -25,6 +27,7 @@ interface ButtonProps {
const ButtonCustom: React.FC<ButtonProps> = ({
children,
href,
onPress,
title = "Button",
backgroundColor = MainColor.yellow,
@@ -38,12 +41,19 @@ const ButtonCustom: React.FC<ButtonProps> = ({
<TouchableOpacity
style={[
stylesButton.button,
disabled && stylesButton.disabled,
style,
{ borderRadius: radius },
{ backgroundColor },
disabled
? [stylesButton.disabled, { backgroundColor: MainColor.disabled }]
: { backgroundColor },
style,
]}
onPress={onPress}
onPress={() => {
if (href) {
router.push(href);
} else {
onPress?.();
}
}}
disabled={disabled}
activeOpacity={0.8}
>

View File

@@ -0,0 +1,13 @@
import { Ionicons } from "@expo/vector-icons";
import { MainColor } from "@/constants/color-palet";
export default function DotButton({ onPress }: { onPress: () => void }) {
return (
<Ionicons
onPress={onPress}
name="ellipsis-vertical"
size={20}
color={MainColor.yellow}
/>
);
}

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

@@ -7,7 +7,7 @@ import { StyleSheet } from "react-native";
export const stylesButton = StyleSheet.create({
button: {
backgroundColor: MainColor.yellow,
paddingVertical: 12,
paddingVertical: 10,
paddingHorizontal: 20,
flexDirection: "row", // 👈 Tambahkan baris ini
alignItems: "center",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 { 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_gray}
/>
/> */}
</View>
<Text style={styles.label}>{item.label}</Text>
<Text
style={[styles.label, { color: item.color || AccentColor.white }]}
>
{item.label}
</Text>
</TouchableOpacity>
))}
</View>
@@ -54,4 +64,4 @@ const styles = StyleSheet.create({
textAlign: "center",
color: MainColor.white_gray,
},
});
});

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: {

View File

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

View File

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

View File

@@ -0,0 +1,61 @@
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;
value: 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.value;
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

@@ -3,9 +3,16 @@ import {
TEXT_SIZE_LARGE,
TEXT_SIZE_MEDIUM,
TEXT_SIZE_SMALL,
TEXT_SIZE_XLARGE,
} from "@/constants/constans-value";
import React from "react";
import { Text as RNText, StyleProp, StyleSheet, TextStyle, TouchableOpacity } 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";
@@ -15,8 +22,8 @@ interface TextCustomProps {
style?: StyleProp<TextStyle>;
bold?: boolean;
semiBold?: boolean;
size?: "default" | "large" | "small";
color?: "default" | "yellow" | "red";
size?: "default" | "large" | "small" | "xlarge";
color?: "default" | "yellow" | "red" | "gray" | "green" | "black"
align?: TextAlign; // Prop untuk alignment
truncate?: boolean | number;
onPress?: () => void;
@@ -45,11 +52,15 @@ const TextCustom: React.FC<TextCustomProps> = ({
// Size
if (size === "large") selectedStyles.push(styles.large);
else if (size === "xlarge") selectedStyles.push(styles.xlarge);
else if (size === "small") selectedStyles.push(styles.small);
// Color
if (color === "yellow") selectedStyles.push(styles.yellow);
else if (color === "red") selectedStyles.push(styles.red);
else if (color === "gray") selectedStyles.push(styles.gray);
else if (color === "green") selectedStyles.push(styles.green);
else if (color === "black") selectedStyles.push(styles.black);
// Alignment
if (align) {
@@ -62,20 +73,8 @@ const TextCustom: React.FC<TextCustomProps> = ({
return selectedStyles;
};
return (
onPress ? (
<TouchableOpacity onPress={onPress}>
<RNText
numberOfLines={
typeof truncate === "number" ? truncate : truncate ? 1 : undefined
}
ellipsizeMode="tail"
style={getStyle()}
>
{children}
</RNText>
</TouchableOpacity>
) : (
return onPress ? (
<TouchableOpacity onPress={onPress}>
<RNText
numberOfLines={
typeof truncate === "number" ? truncate : truncate ? 1 : undefined
@@ -85,7 +84,17 @@ const TextCustom: React.FC<TextCustomProps> = ({
>
{children}
</RNText>
)
</TouchableOpacity>
) : (
<RNText
numberOfLines={
typeof truncate === "number" ? truncate : truncate ? 1 : undefined
}
ellipsizeMode="tail"
style={getStyle()}
>
{children}
</RNText>
);
};
@@ -96,6 +105,7 @@ export const styles = StyleSheet.create({
fontSize: TEXT_SIZE_MEDIUM,
color: MainColor.white,
fontFamily: "Poppins-Regular",
lineHeight: 20,
},
bold: {
fontFamily: "Poppins-Bold",
@@ -105,11 +115,14 @@ export const styles = StyleSheet.create({
fontFamily: "Poppins-SemiBold",
fontWeight: "500",
},
small: {
fontSize: TEXT_SIZE_SMALL,
},
large: {
fontSize: TEXT_SIZE_LARGE,
},
small: {
fontSize: TEXT_SIZE_SMALL,
xlarge: {
fontSize: TEXT_SIZE_XLARGE,
},
yellow: {
color: MainColor.yellow,
@@ -117,4 +130,13 @@ export const styles = StyleSheet.create({
red: {
color: MainColor.red,
},
gray: {
color: MainColor.placeholder,
},
green: {
color: MainColor.green,
},
black: {
color: MainColor.black,
},
});

View File

@@ -1,11 +1,11 @@
import { GStyles } from "@/styles/global-styles";
import React, { useEffect, useState } from "react";
import {
TextInput as RNTextInput,
StyleProp,
Text,
View,
ViewStyle,
TextInput as RNTextInput,
StyleProp,
Text,
View,
ViewStyle,
} from "react-native";
type IconType = React.ReactNode | string;
@@ -24,6 +24,7 @@ type BaseProps = {
maxRows?: number;
showCount?: boolean;
maxLength?: number;
height?: number;
style?: StyleProp<ViewStyle>;
};
@@ -44,12 +45,13 @@ const TextAreaCustom: React.FC<TextAreaCustomProps> = ({
disabled = false,
borderRadius = 8,
autosize = false,
minRows = 3,
minRows = 4,
maxRows = 6,
showCount = false,
maxLength,
value,
onChangeText,
height = 100,
style,
...rest
}) => {
@@ -77,7 +79,7 @@ const TextAreaCustom: React.FC<TextAreaCustomProps> = ({
};
return (
<View style={GStyles.inputContainerArea}>
<View style={[GStyles.inputContainerArea]}>
{label && (
<Text style={GStyles.inputLabel}>
{label}
@@ -90,6 +92,7 @@ const TextAreaCustom: React.FC<TextAreaCustomProps> = ({
disabled && GStyles.disabledBox,
hasError ? GStyles.inputErrorBorder : {},
{ borderRadius },
{ height },
style,
]}
>
@@ -102,7 +105,7 @@ const TextAreaCustom: React.FC<TextAreaCustomProps> = ({
multiline
numberOfLines={numberOfLines}
style={[
GStyles.inputText,
// GStyles.inputText,
GStyles.textAreaInput,
{ color: fontColor },
]}

View File

@@ -24,6 +24,7 @@ type Props = {
borderRadius?: number;
style?: StyleProp<ViewStyle>;
maxLength?: number;
containerStyle?: StyleProp<ViewStyle>;
} & Omit<React.ComponentProps<typeof RNTextInput>, "style">;
const TextInputCustom = ({
@@ -40,6 +41,7 @@ const TextInputCustom = ({
keyboardType,
onChangeText,
maxLength,
containerStyle,
...rest
}: Props) => {
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
@@ -73,7 +75,7 @@ const TextInputCustom = ({
};
return (
<View style={GStyles.inputContainerArea}>
<View style={[GStyles.inputContainerArea, containerStyle]}>
{label && (
<Text style={GStyles.inputLabel}>
{label}
@@ -89,9 +91,9 @@ const TextInputCustom = ({
disabled && GStyles.disabledBox,
]}
>
{/* {iconLeft && (
{iconLeft && (
<View style={GStyles.inputIcon}>{renderIcon(iconLeft)}</View>
)} */}
)}
<RNTextInput
style={[
GStyles.inputText,

View File

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

View File

@@ -0,0 +1,46 @@
import { ImageSourcePropType, View } from "react-native";
import Grid from "../Grid/GridCustom";
import AvatarCustom from "../Image/AvatarCustom";
import TextCustom from "../Text/TextCustom";
const AvatarUsernameAndOtherComponent = ({
avatarHref,
avatar,
name,
rightComponent,
}: {
avatarHref?: string;
avatar?: ImageSourcePropType;
name?: string;
rightComponent?: React.ReactNode;
}) => {
return (
<>
<View>
<Grid containerStyle={{ zIndex: 10 }}>
<Grid.Col span={2}>
<AvatarCustom source={avatar} href={avatarHref as any} />
</Grid.Col>
<Grid.Col
span={rightComponent ? 6 : 10}
style={{ justifyContent: "center" }}
>
<TextCustom truncate bold>
{name || "Username"}
</TextCustom>
</Grid.Col>
{rightComponent && (
<Grid.Col
span={4}
style={{ alignItems: "flex-end", justifyContent: "center" }}
>
{rightComponent}
</Grid.Col>
)}
</Grid>
</View>
</>
);
};
export default AvatarUsernameAndOtherComponent;

View File

@@ -1,4 +1,5 @@
import { MainColor } from "@/constants/color-palet";
import { OS_HEIGHT } from "@/constants/constans-value";
import { GStyles } from "@/styles/global-styles";
import {
ImageBackground,
@@ -8,19 +9,35 @@ 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;
hideFooter?: boolean;
style?: StyleProp<ViewStyle>;
}
/**
*
* @param hideFooter
* @returns meneyembunyikan footer ketika menggunakan tabs (misal: bottom tab)
*/
const ViewWrapper = ({
children,
withBackground = false,
headerComponent,
footerComponent,
floatingButton,
hideFooter = false,
style,
}: ViewWrapperProps) => {
const assetBackground = require("../../assets/images/main-background.png");
@@ -30,6 +47,11 @@ const ViewWrapper = ({
behavior={Platform.OS === "ios" ? "padding" : "height"}
style={{ flex: 1, backgroundColor: MainColor.darkblue }}
>
{/* Header Sticky */}
{headerComponent && (
<View style={GStyles.stickyHeader}>{headerComponent}</View>
)}
<ScrollView
contentContainerStyle={{ flexGrow: 1 }}
keyboardShouldPersistTaps="handled"
@@ -40,14 +62,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>
@@ -58,53 +80,25 @@ const ViewWrapper = ({
edges={["bottom"]}
style={{
backgroundColor: MainColor.darkblue,
height: OS_HEIGHT
}}
>
{footerComponent}
</SafeAreaView>
) : (
<SafeAreaView
edges={["bottom"]}
style={{ backgroundColor: MainColor.darkblue }}
/>
)}
</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 ? (
hideFooter ? null : (
<SafeAreaView
edges={["bottom"]}
style={{
// flex: 1,
backgroundColor: MainColor.darkblue,
}}
>
{footerComponent}
</SafeAreaView>
) : null}
</KeyboardAvoidingView>
</SafeAreaView> */}
style={{ backgroundColor: MainColor.darkblue }}
/>
)
)}
{/* Floating Component (misal: FAB) */}
{floatingButton && (
<View style={GStyles.floatingContainer}>{floatingButton}</View>
)}
</KeyboardAvoidingView>
</>
);
};

View File

@@ -4,12 +4,10 @@ import AlertCustom from "./Alert/AlertCustom";
import LeftButtonCustom from "./Button/BackButton";
import ButtonCenteredOnly from "./Button/ButtonCenteredOnly";
import ButtonCustom from "./Button/ButtonCustom";
import DotButton from "./Button/DotButton";
// Drawer
import DrawerCustom from "./Drawer/DrawerCustom";
import MenuDrawerDynamicGrid from "./Drawer/MenuDrawerDynamicGird";
// ShareComponent
import Spacing from "./_ShareComponent/Spacing";
import ViewWrapper from "./_ShareComponent/ViewWrapper";
// Text
import TextCustom from "./Text/TextCustom";
// TextInput
@@ -22,7 +20,6 @@ import Grid from "./Grid/GridCustom";
import BaseBox from "./Box/BaseBox";
import BoxButtonOnFooter from "./Box/BoxButtonOnFooter";
import InformationBox from "./Box/InformationBox";
// Stack
import StackCustom from "./Stack/StackCustom";
// Select
@@ -30,25 +27,41 @@ 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";
// ShareComponent
import Spacing from "./_ShareComponent/Spacing";
import ViewWrapper from "./_ShareComponent/ViewWrapper";
import AvatarUsernameAndOtherComponent from "./_ShareComponent/AvataraAndOtherHeaderComponent";
export {
AlertCustom,
// Image
AvatarCustom,
LandscapeFrameUploaded,
// Button
ButtonCustom,
LeftButtonCustom as BackButton,
ButtonCenteredOnly,
// Box
BaseBox,
BoxButtonOnFooter,
InformationBox,
// Button
ButtonCenteredOnly,
ButtonCustom,
LeftButtonCustom as BackButton,
DotButton,
// Drawer
DrawerCustom,
MenuDrawerDynamicGrid,
// Grid
Grid,
MenuDrawerDynamicGrid,
// Map
MapCustom,
// Select
SelectCustom,
// ShareComponent
@@ -63,4 +76,14 @@ export {
TextInputCustom,
// ViewWrapper
ViewWrapper,
// Divider
DividerCustom,
// Center
CenterCustom,
// Clickable
ClickableCustom,
// Scroll
ScrollableCustom,
// ShareComponent
AvatarUsernameAndOtherComponent,
};

View File

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

View File

@@ -0,0 +1,6 @@
export const masterStatus = [
{ value: "publish", label: "Publish" },
{ value: "review", label: "Review" },
{ value: "draft", label: "Draft" },
{ value: "reject", label: "Reject" },
];

View File

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

View File

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

1242
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -12,12 +12,14 @@
},
"dependencies": {
"@expo/vector-icons": "^14.1.0",
"@react-native-community/datetimepicker": "8.4.1",
"@react-navigation/bottom-tabs": "^7.4.2",
"@react-navigation/drawer": "^7.5.2",
"@react-navigation/elements": "^2.3.8",
"@react-navigation/native": "^7.1.6",
"@react-navigation/native-stack": "^7.3.21",
"@types/react-native-vector-icons": "^6.4.18",
"dayjs": "^1.11.13",
"expo": "53.0.17",
"expo-blur": "~14.1.5",
"expo-camera": "~16.1.10",
@@ -38,7 +40,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 +53,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

@@ -34,6 +34,9 @@ export default function LoginView() {
// 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}`);
// router.replace("/(application)/(user)/event/(tabs)");
}
return (

View File

@@ -1,7 +1,7 @@
import Spacing from "@/components/_ShareComponent/Spacing";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import ButtonCustom from "@/components/Button/ButtonCustom";
import { TextInputCustom } from "@/components/TextInput/TextInputCustom";
import TextInputCustom from "@/components/TextInput/TextInputCustom";
import { MainColor } from "@/constants/color-palet";
import { GStyles } from "@/styles/global-styles";
import { MaterialCommunityIcons } from "@expo/vector-icons";

View File

@@ -0,0 +1,115 @@
import AlertCustom from "@/components/Alert/AlertCustom";
import { router } from "expo-router";
export default function Event_AlertButtonStatusSection({
id,
status,
openAlert,
setOpenAlert,
openDeleteAlert,
setOpenDeleteAlert,
}: {
id: string;
status: string;
openAlert: boolean;
setOpenAlert: (value: boolean) => void;
openDeleteAlert: boolean;
setOpenDeleteAlert: (value: boolean) => void;
}) {
// --- Alert untuk aksi berdasarkan status ---
const renderStatusAlert = () => {
switch (status) {
case "publish":
return <></>;
case "review":
return (
<AlertCustom
isVisible={openAlert}
title="Batalkan Review"
message="Apakah Anda yakin ingin membatalkan review?"
textLeft="Batal"
textRight="Ya"
colorRight="green"
onLeftPress={() => {
setOpenAlert(false);
}}
onRightPress={() => {
setOpenAlert(false);
router.back();
}}
/>
);
case "draft":
return (
<AlertCustom
isVisible={openAlert}
title="Ajukan Review ?"
message="Apakah Anda yakin ingin mengajukan review kembali?"
textLeft="Batal"
textRight="Ya"
colorRight="green"
onLeftPress={() => {
setOpenAlert(false);
router.back();
}}
onRightPress={() => {
setOpenAlert(false);
router.back();
}}
/>
);
case "reject":
return (
<AlertCustom
isVisible={openAlert}
title="Edit Kembali"
message="Apakah Anda yakin ingin mengedit kembali event ini?"
textLeft="Batal"
textRight="Ya"
colorRight="green"
onLeftPress={() => {
setOpenAlert(false);
router.back();
}}
onRightPress={() => {
setOpenAlert(false);
router.back();
}}
/>
);
default:
return null;
}
};
return (
<>
{/* Alert berdasarkan status */}
{renderStatusAlert()}
{/* Alert untuk hapus - selalu muncul jika openDeleteAlert true */}
<AlertCustom
isVisible={openDeleteAlert}
title="Hapus Event"
message="Apakah Anda yakin ingin menghapus event ini?"
textLeft="Batal"
textRight="Ya, Hapus"
colorRight="red"
onLeftPress={() => {
setOpenDeleteAlert(false);
router.back();
}}
onRightPress={() => {
// Aksi hapus event
console.log("Menghapus event dengan ID:", id);
setOpenDeleteAlert(false);
router.back();
}}
/>
</>
);
}

View File

@@ -0,0 +1,76 @@
import { ButtonCustom, Grid } from "@/components";
import { View } from "react-native";
export default function Event_ButtonStatusSection({
status,
onOpenAlert,
onOpenDeleteAlert,
}: {
status: string;
onOpenAlert: (value: boolean) => void;
onOpenDeleteAlert: (value: boolean) => void;
}) {
const handleOpenAlert = () => {
onOpenAlert(true);
};
const handleOpenDeleteAlert = () => {
onOpenDeleteAlert(true);
};
const DeleteButton = () => {
return (
<>
<ButtonCustom backgroundColor="red" textColor="white" onPress={handleOpenDeleteAlert}>
Hapus
</ButtonCustom>
</>
);
};
switch (status) {
case "publish":
return <></>;
case "review":
return (
<ButtonCustom onPress={handleOpenAlert}>
Batalkan Review
</ButtonCustom>
);
case "draft":
return (
<>
<Grid>
<Grid.Col span={5}>
<ButtonCustom onPress={handleOpenAlert}>Ajukan Review</ButtonCustom>
</Grid.Col>
<Grid.Col span={2}>
<View />
</Grid.Col>
<Grid.Col span={5}>{DeleteButton()}</Grid.Col>
</Grid>
</>
);
case "reject":
return (
<>
<Grid>
<Grid.Col span={5}>
<ButtonCustom onPress={handleOpenAlert}>Edit Kembali</ButtonCustom>
</Grid.Col>
<Grid.Col span={2}>
<View />
</Grid.Col>
<Grid.Col span={5}>{DeleteButton()}</Grid.Col>
</Grid>
</>
);
default:
return <ButtonCustom disabled>Status Undifined</ButtonCustom>;
}
}

View File

@@ -0,0 +1,17 @@
import { AccentColor } from "@/constants/color-palet";
import { ICON_SIZE_MEDIUM } from "@/constants/constans-value";
import { Ionicons } from "@expo/vector-icons";
export const menuDrawerDraftEvent = ({ id }: { id: string }) => [
{
icon: (
<Ionicons
name="create"
size={ICON_SIZE_MEDIUM}
color={AccentColor.white}
/>
),
label: "Edit event",
path: `/(application)/(user)/event/${id}/edit`,
},
];

View File

@@ -0,0 +1,71 @@
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 } from "@expo/vector-icons";
import { View } from "react-native";
export default function Forum_CommentarBoxSection({
data,
setOpenDrawer,
}: {
data: any;
setOpenDrawer: (value: boolean) => 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);
}}
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,69 @@
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { Feather, Ionicons } from "@expo/vector-icons";
export { drawerItemsForumBeranda, drawerItemsForumComentar };
const drawerItemsForumBeranda = ({
id,
status,
}: {
id: string;
status: string;
}) => [
{
icon: (
<Ionicons name="flag" size={ICON_SIZE_SMALL} color={MainColor.white} />
),
label: "Laporkan diskusi",
// color: MainColor.white,
path: `/forum/${id}/report-posting`,
},
{
icon: (
<Feather name="edit" size={ICON_SIZE_SMALL} color={MainColor.white} />
),
label: "Edit posting",
path: `/forum/${id}/edit`,
},
{
icon:
status === "Open" ? (
<Ionicons name="close" size={ICON_SIZE_SMALL} color={MainColor.white} />
) : (
<Ionicons name="open" size={ICON_SIZE_SMALL} color={MainColor.white} />
),
label: status === "Open" ? "Tutup forum" : "Buka forum",
path: "",
color: status === "Open" ? MainColor.orange : MainColor.green,
},
{
icon: (
<Ionicons name="trash" size={ICON_SIZE_SMALL} color={MainColor.white} />
),
label: "Hapus",
path: "",
color: MainColor.red,
},
];
const drawerItemsForumComentar = ({ id }: { id: string }) => [
{
icon: (
<Ionicons name="flag" size={ICON_SIZE_SMALL} color={MainColor.white} />
),
label: "Laporkan",
// color: MainColor.white,
path: `/forum/${id}/report-commentar`,
},
{
icon: (
<Ionicons name="trash" size={ICON_SIZE_SMALL} color={MainColor.white} />
),
label: "Hapus",
color: MainColor.red,
path: "",
},
];

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,33 @@
import { MenuDrawerDynamicGrid } from "@/components";
import { drawerItemsForumComentar } from "../ListPage";
import { router } from "expo-router";
export default function Forum_MenuDrawerCommentar({
id,
setShowDeleteAlert,
setIsDrawerOpen,
}: {
id: string;
setShowDeleteAlert: (value: boolean) => void;
setIsDrawerOpen: (value: boolean) => void;
}) {
const handlePress = (item: any) => {
if (item.label === "Hapus") {
setShowDeleteAlert(true);
} else {
router.push(item.path as any);
}
setIsDrawerOpen(false);
};
return (
<>
<MenuDrawerDynamicGrid
data={drawerItemsForumComentar({ id })}
columns={4}
onPressItem={handlePress}
/>
</>
);
}

View File

@@ -0,0 +1,41 @@
import { BaseBox, ButtonCustom, StackCustom, TextCustom } from "@/components";
import { RadioCustom, RadioGroup } from "@/components/Radio/RadioCustom";
import { MainColor } from "@/constants/color-palet";
import { listDummyReportForum } from "@/lib/dummy-data/forum/report-list";
import { router } from "expo-router";
import { useState } from "react";
import { View } from "react-native";
export default function Forum_ReportListSection() {
const [value, setValue] = useState<any | number>("");
return (
<>
<BaseBox>
<StackCustom>
<RadioGroup value={value} onChange={setValue}>
{listDummyReportForum.map((e, i) => (
<View key={i}>
<RadioCustom
label={e.title}
// value={i}
value={e.title}
/>
<TextCustom>{e.desc}</TextCustom>
</View>
))}
</RadioGroup>
{/* <ButtonCustom
backgroundColor={MainColor.red}
textColor={MainColor.white}
onPress={() => {
console.log("Report", value);
}}
>
Report
</ButtonCustom> */}
</StackCustom>
</BaseBox>
</>
);
}

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

@@ -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,18 @@
import { ButtonCustom } from "@/components";
import { MainColor } from "@/constants/color-palet";
import { Ionicons } from "@expo/vector-icons";
export default function Portofolio_ButtonDelete({
setShowDeleteAlert,
}: {
setShowDeleteAlert: (value: boolean) => void;
}) {
const handleDelete = () => {
setShowDeleteAlert(true);
};
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

@@ -1,32 +1,61 @@
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[] => [
export const drawerItemsPortofolio = ({ id }: { id: string }): IMenuDrawerItem[] => [
{
icon: "create",
icon: (
<Ionicons
name="create"
size={ICON_SIZE_MEDIUM}
color={AccentColor.white}
/>
),
label: "Edit portofolio",
path: `/(application)/portofolio/${id}/edit`,
},
{
icon: "camera",
icon: (
<Ionicons
name="camera"
size={ICON_SIZE_MEDIUM}
color={AccentColor.white}
/>
),
label: "Edit logo ",
path: `/(application)/portofolio/${id}/edit-logo`,
},
{
icon: "image",
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: "add-circle",
icon: (
<Fontisto
name="map-marker-alt"
size={ICON_SIZE_MEDIUM}
color={AccentColor.white}
/>
),
label: "Edit Map",
path: `/(application)/maps/${id}/edit`,
},
{
icon: "create-outline",
icon: (
<FontAwesome5
name="map-pin"
size={ICON_SIZE_MEDIUM}
color={AccentColor.white}
/>
),
label: "Custom Pin Map",
path: `/(application)/maps/${id}/custom-pin`,
},

View File

@@ -0,0 +1,21 @@
import { Spacing, StackCustom } from "@/components";
import Portofolio_BusinessLocation from "./BusinessLocationSection";
import Portofolio_Data from "./DataPortofolio";
import Portofolio_SocialMediaSection from "./SocialMediaSection";
import Portofolio_ButtonDelete from "./ButtonDelete";
export default function PorfofolioSection({
setShowDeleteAlert,
}: {
setShowDeleteAlert: (value: boolean) => void;
}) {
return (
<StackCustom>
<Portofolio_Data />
<Portofolio_BusinessLocation />
<Portofolio_SocialMediaSection />
<Portofolio_ButtonDelete setShowDeleteAlert={setShowDeleteAlert}/>
<Spacing/>
</StackCustom>
);
}

View File

@@ -0,0 +1,78 @@
import { BaseBox, Grid, StackCustom, TextCustom } from "@/components";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { Ionicons } from "@expo/vector-icons";
export default function Portofolio_SocialMediaSection() {
const listData = [
{
label: "Facebook ku bagas",
icon: (
<Ionicons
name="logo-facebook"
size={ICON_SIZE_SMALL}
color={MainColor.white}
/>
),
},
{
label: "Tiktok ku bagas",
icon: (
<Ionicons
name="logo-tiktok"
size={ICON_SIZE_SMALL}
color={MainColor.white}
/>
),
},
{
label: "Instagram ku bagas",
icon: (
<Ionicons
name="logo-instagram"
size={ICON_SIZE_SMALL}
color={MainColor.white}
/>
),
},
{
label: "Twitter ku bagas",
icon: (
<Ionicons
name="logo-twitter"
size={ICON_SIZE_SMALL}
color={MainColor.white}
/>
),
},
{
label: "Youtube ku bagas",
icon: (
<Ionicons
name="logo-youtube"
size={ICON_SIZE_SMALL}
color={MainColor.white}
/>
),
},
];
return (
<>
<BaseBox>
<StackCustom>
<TextCustom bold>Media Sosial Bisnis</TextCustom>
{listData.map((item, index) => (
<Grid key={index}>
<Grid.Col span={2} style={{ alignItems: "center" }}>
{item.icon}
</Grid.Col>
<Grid.Col span={10} style={{ paddingLeft: 5 }}>
<TextCustom>{item.label}</TextCustom>
</Grid.Col>
</Grid>
))}
</StackCustom>
</BaseBox>
</>
);
}

View File

@@ -1,24 +1,45 @@
import { AvatarCustom } from "@/components";
import { AvatarCustom, ClickableCustom } from "@/components";
import { AccentColor } from "@/constants/color-palet";
import DUMMY_IMAGE from "@/constants/dummy-image-value";
import { View, ImageBackground, StyleSheet } from "react-native";
import { router } from "expo-router";
import { ImageBackground, StyleSheet, View } from "react-native";
const AvatarAndBackground = () => {
const AvatarAndBackground = ({
backgroundId,
imageId,
}: {
backgroundId: string;
imageId: string;
}) => {
return (
<View style={styles.container}>
<ImageBackground
source={DUMMY_IMAGE.background}
style={styles.backgroundImage}
resizeMode="contain"
/>
{/* Background Image */}
<ClickableCustom
onPress={() => {
router.navigate(
`/(application)/(image)/preview-image/${backgroundId}`
);
}}
>
<ImageBackground
source={DUMMY_IMAGE.background}
style={styles.backgroundImage}
resizeMode="cover"
/>
</ClickableCustom>
{/* Avatar yang sedikit keluar */}
<View style={styles.avatarOverlap}>
<AvatarCustom
source={DUMMY_IMAGE.avatar}
size="lg"
/>
<ClickableCustom
onPress={() => {
router.navigate(
`/(application)/(image)/preview-image/${imageId}`
);
}}
>
<AvatarCustom source={DUMMY_IMAGE.avatar} size="lg" />
</ClickableCustom>
</View>
</View>
);
@@ -49,4 +70,4 @@ const styles = StyleSheet.create({
left: "50%", // Sentralisasi horizontal
transform: [{ translateX: -50 }], // Menggeser ke kiri 50% lebarnya
},
});
});

View File

@@ -1,23 +1,54 @@
import { IMenuDrawerItem } from "@/components/_Interface/types";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { ICON_SIZE_MEDIUM } from "@/constants/constans-value";
import { Ionicons } from "@expo/vector-icons";
export const drawerItemsProfile = ({ id }: { id: string }): IMenuDrawerItem[] => [
export const drawerItemsProfile = ({
id,
}: {
id: string;
}): IMenuDrawerItem[] => [
{
icon: "create",
icon: (
<Ionicons
name="create"
size={ICON_SIZE_MEDIUM}
color={AccentColor.white}
/>
),
label: "Edit profile",
path: `/(application)/profile/${id}/edit`,
},
{
icon: "camera",
icon: (
<Ionicons
name="camera"
size={ICON_SIZE_MEDIUM}
color={AccentColor.white}
/>
),
label: "Ubah foto profile",
path: `/(application)/profile/${id}/update-photo`,
},
{
icon: "image",
icon: (
<Ionicons
name="image"
size={ICON_SIZE_MEDIUM}
color={AccentColor.white}
/>
),
label: "Ubah latar belakang",
path: `/(application)/profile/${id}/update-background`,
},
{
icon: "add-circle",
icon: (
<Ionicons
name="add-circle"
size={ICON_SIZE_MEDIUM}
color={AccentColor.white}
/>
),
label: "Tambah portofolio",
path: `/(application)/portofolio/${id}/create`,
},
@@ -26,10 +57,27 @@ export const drawerItemsProfile = ({ id }: { id: string }): IMenuDrawerItem[] =>
// label: "Dashboard Admin",
// path: `/(application)/profile/dashboard-admin`,
// },
{ icon: "log-out", label: "Keluar", color: "red", path: "" },
{
icon: "create-outline",
icon: (
<Ionicons
name="log-out"
size={ICON_SIZE_MEDIUM}
color={AccentColor.white}
/>
),
label: "Keluar",
color: MainColor.red,
path: "",
},
{
icon: (
<Ionicons
name="create-outline"
size={ICON_SIZE_MEDIUM}
color={AccentColor.white}
/>
),
label: "Create profile",
path: `/(application)/profile/${id}/create`,
},
];
];

View File

@@ -43,7 +43,7 @@ export default function ProfilSection() {
return (
<>
<BaseBox>
<AvatarAndBackground />
<AvatarAndBackground backgroundId="test-background-id" imageId="test-image-id" />
<Spacing height={50} />
<View style={{ alignItems: "center" }}>

View File

@@ -1,4 +1,8 @@
import {
PADDING_EXTRA_SMALL,
PADDING_LARGE,
PADDING_MEDIUM,
PADDING_SMALL,
TEXT_SIZE_LARGE,
TEXT_SIZE_MEDIUM,
TEXT_SIZE_SMALL,
@@ -12,25 +16,44 @@ export const GStyles = StyleSheet.create({
// =============== Main Styles =============== //
container: {
flex: 1,
paddingInline: 20,
paddingBlock: 10,
paddingInline: PADDING_LARGE,
paddingBlock: PADDING_EXTRA_SMALL,
backgroundColor: MainColor.darkblue,
},
containerWithBackground: {
flex: 1,
paddingInline: 20,
paddingBlock: 10,
paddingInline: PADDING_LARGE,
paddingBlock: PADDING_EXTRA_SMALL,
},
imageBackground: {
height: "100%",
width: "100%",
},
// Style saat disabled
stickyHeader: {
position: "relative",
top: 0,
left: 0,
right: 0,
zIndex: 10,
backgroundColor: "transparent",
paddingBlock: PADDING_SMALL,
paddingInline: PADDING_MEDIUM,
// padding: 16,
// paddingTop: 8,
// paddingBottom: 8,
},
floatingContainer: {
position: "absolute",
bottom: 100,
right: 20,
zIndex: 8,
},
// =============== Disabled Styles =============== //
disabledBox: {
backgroundColor: MainColor.disabled,
borderColor: AccentColor.disabledBorder,
},
inputDisabled: {
backgroundColor: "#f0f0f0",
borderColor: "#ddd",
@@ -41,7 +64,7 @@ export const GStyles = StyleSheet.create({
inputPlaceholderDisabled: {
color: "#444",
},
// =============== Main Styles =============== //
// =============== Disabled Styles =============== //
// =============== AUTHENTICATION =============== //
authContainer: {
@@ -70,6 +93,11 @@ export const GStyles = StyleSheet.create({
color: MainColor.white_gray,
fontWeight: "normal",
},
textLabelBold: {
fontSize: TEXT_SIZE_MEDIUM,
color: MainColor.white_gray,
fontWeight: "bold",
},
// =============== TEXT & LABEL =============== //
// =============== STACK HEADER =============== //
@@ -260,8 +288,10 @@ export const GStyles = StyleSheet.create({
// TextArea untuk tambahan
textAreaInput: {
textAlignVertical: "top",
padding: 5,
height: undefined, // biar multiline bebas tinggi
paddingTop: 10,
// height: undefined, // biar multiline bebas tinggi
height: 100,
width: "100%",
},
// Select