Compare commits

...

5 Commits

Author SHA1 Message Date
ab5733f336 Fix redirect admin 2025-12-04 17:41:19 +08:00
f5e30087ed Fix QC Inno
Fix:
- app/(application)/admin/donation/category-create.tsx
- app/(application)/admin/donation/category-update.tsx
- app/(application)/admin/donation/category.tsx
- components/_ShareComponent/Admin/TableValue.tsx
- screens/Authentication/LoginView.tsx
- service/api-admin/api-master-admin.ts

### No Issue
2025-12-04 16:59:39 +08:00
a93f97ed6a Fix rejected Apple
Add:
-  utils/viersionBadge.ts

Fix:
- app.config.js
- context/AuthContext.tsx
- ios/HIPMIBadungConnect/Info.plist
- screens/Authentication/LoginView.tsx
- screens/Authentication/VerificationView.tsx
- service/api-config.ts

### No Issue
2025-12-03 17:23:12 +08:00
858b441a8c Clearing apple rejected
QC: Inno

Fix:
- app.config.js
- app/(application)/(user)/investment/[id]/index.tsx
- app/(application)/(user)/voting/(tabs)/index.tsx
- app/(application)/(user)/waiting-room.tsx
- app/(application)/terms-agreement.tsx
- context/AuthContext.tsx
- ios/HIPMIBadungConnect.xcodeproj/project.pbxproj
- ios/HIPMIBadungConnect/Info.plist
- screens/Authentication/LoginView.tsx
- screens/Authentication/VerificationView.tsx
- screens/Home/topFeatureSection.tsx
- screens/Invesment/BoxBerandaSection.tsx
- screens/Invesment/ButtonInvestasiSection.tsx
- screens/Invesment/DetailDataPublishSection.tsx
- service/api-client/api-voting.ts
- service/api-config.ts

### No Issue
2025-12-02 17:48:24 +08:00
98aaa126a1 QC: Inno dan Pak Jun
Fix:
- app/(application)/(user)/collaboration/create.tsx
- app/(application)/(user)/event/[id]/edit.tsx
- app/(application)/(user)/event/create.tsx
- app/(application)/(user)/profile/[id]/blocked-list.tsx
- app/(application)/(user)/profile/[id]/index.tsx
- app/(application)/(user)/voting/[id]/[status]/detail.tsx
- components/Button/FloatingButton.tsx
- components/TextArea/TextAreaCustom.tsx
- components/TextInput/TextInputCustom.tsx
- constants/color-palet.ts
- screens/Authentication/LoginView.tsx
- screens/Home/topFeatureSection.tsx
- screens/Portofolio/SocialMediaSection.tsx
- screens/Voting/BoxDetailHasilVotingSection.tsx
- styles/global-styles.ts

### No Issue
2025-12-01 17:43:20 +08:00
33 changed files with 619 additions and 190 deletions

View File

@@ -19,7 +19,7 @@ export default {
"NSLocationWhenInUseUsageDescription": "Aplikasi membutuhkan akses lokasi untuk menampilkan peta.", "NSLocationWhenInUseUsageDescription": "Aplikasi membutuhkan akses lokasi untuk menampilkan peta.",
}, },
associatedDomains: ["applinks:cld-dkr-staging-hipmi.wibudev.com"], associatedDomains: ["applinks:cld-dkr-staging-hipmi.wibudev.com"],
buildNumber: "9", buildNumber: "12",
}, },
android: { android: {

View File

@@ -155,7 +155,7 @@ export default function CollaborationCreate() {
<TextAreaCustom <TextAreaCustom
required required
label="Keuntungan Proyek" label="Keuntungan Proyek"
placeholder="Masukan keuntungan proyek" placeholder="Masukan keuntungan proyek, contoh: Meningkatkan relasi bisnis , menjamin kualitas produk, meningkatkan kinerja dan lain lain"
showCount showCount
maxLength={1000} maxLength={1000}
value={data?.benefit} value={data?.benefit}

View File

@@ -18,7 +18,7 @@ import {
import { apiMasterEventType } from "@/service/api-client/api-master"; import { apiMasterEventType } from "@/service/api-client/api-master";
import { DateTimePickerEvent } from "@react-native-community/datetimepicker"; import { DateTimePickerEvent } from "@react-native-community/datetimepicker";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router"; import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import React, { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
export default function EventEdit() { export default function EventEdit() {
@@ -55,6 +55,7 @@ export default function EventEdit() {
try { try {
setIsLoadData(true); setIsLoadData(true);
const response = await apiEventGetOne({ id: id as string }); const response = await apiEventGetOne({ id: id as string });
console.log("[DATA BY ID]", JSON.stringify(response, null, 2));
if (response.success) { if (response.success) {
setData(response.data); setData(response.data);
setSelectedDate(new Date(response.data.tanggal)); setSelectedDate(new Date(response.data.tanggal));
@@ -209,7 +210,7 @@ export default function EventEdit() {
minimumDate={new Date(Date.now())} minimumDate={new Date(Date.now())}
label="Tanggal & Waktu Mulai" label="Tanggal & Waktu Mulai"
required required
value={selectedDate as any} value={selectedDate}
onChange={(date: any) => { onChange={(date: any) => {
setSelectedDate(date as any); setSelectedDate(date as any);
}} }}
@@ -254,7 +255,6 @@ export default function EventEdit() {
placeholder="Masukkan deskripsi event" placeholder="Masukkan deskripsi event"
required required
showCount showCount
maxLength={100}
value={data?.deskripsi} value={data?.deskripsi}
onChangeText={(value) => setData({ ...data, deskripsi: value })} onChangeText={(value) => setData({ ...data, deskripsi: value })}
/> />

View File

@@ -110,13 +110,14 @@ export default function EventCreate() {
const response = await apiEventCreate(newData); const response = await apiEventCreate(newData);
console.log("Response", JSON.stringify(response, null, 2)); console.log("Response", JSON.stringify(response, null, 2));
router.navigate("/event/status"); router.replace("/event/status");
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
}; };
const buttonSubmit = ( const buttonSubmit = (
<ButtonCustom <ButtonCustom
@@ -191,7 +192,7 @@ export default function EventCreate() {
placeholder="Masukkan deskripsi event" placeholder="Masukkan deskripsi event"
required required
showCount showCount
maxLength={1000} value={data?.deskripsi || ""}
onChangeText={(value: any) => onChangeText={(value: any) =>
setData({ ...data, deskripsi: value }) setData({ ...data, deskripsi: value })
} }

View File

@@ -15,6 +15,7 @@ import Investment_ButtonInvestasiSection from "@/screens/Invesment/ButtonInvesta
import Invesment_ComponentBoxOnBottomDetail from "@/screens/Invesment/ComponentBoxOnBottomDetail"; import Invesment_ComponentBoxOnBottomDetail from "@/screens/Invesment/ComponentBoxOnBottomDetail";
import Invesment_DetailDataPublishSection from "@/screens/Invesment/DetailDataPublishSection"; import Invesment_DetailDataPublishSection from "@/screens/Invesment/DetailDataPublishSection";
import { apiInvestmentGetOne } from "@/service/api-client/api-investment"; import { apiInvestmentGetOne } from "@/service/api-client/api-investment";
import { countDownAndCondition } from "@/utils/countDownAndCondition";
import { AntDesign, MaterialIcons } from "@expo/vector-icons"; import { AntDesign, MaterialIcons } from "@expo/vector-icons";
import { import {
router, router,
@@ -23,7 +24,7 @@ import {
useLocalSearchParams, useLocalSearchParams,
} from "expo-router"; } from "expo-router";
import _ from "lodash"; import _ from "lodash";
import { useCallback, useState } from "react"; import { useCallback, useEffect, useState } from "react";
export default function InvestmentDetail() { export default function InvestmentDetail() {
const { user } = useAuth(); const { user } = useAuth();
@@ -62,6 +63,31 @@ export default function InvestmentDetail() {
setOpenDrawerPublish(false); setOpenDrawerPublish(false);
}; };
const [value, setValue] = useState({
sisa: 0,
reminder: false,
});
useEffect(() => {
updateCountDown();
}, [data]);
console.log("[DATA DETAIL]", JSON.stringify(data, null, 2));
const updateCountDown = () => {
const countDown = countDownAndCondition({
duration: data?.MasterPencarianInvestor.name,
publishTime: data?.countDown,
});
setValue({
sisa: countDown.durationDay,
reminder: countDown.reminder,
});
};
const bottomSection = ( const bottomSection = (
<Invesment_ComponentBoxOnBottomDetail <Invesment_ComponentBoxOnBottomDetail
id={id as string} id={id as string}
@@ -71,7 +97,7 @@ export default function InvestmentDetail() {
); );
const buttonSection = ( const buttonSection = (
<Investment_ButtonInvestasiSection id={id as string} isMine={user?.id === data?.author?.id} /> <Investment_ButtonInvestasiSection id={id as string} isMine={user?.id === data?.author?.id} reminder={value.reminder} />
); );
return ( return (

View File

@@ -121,7 +121,7 @@ export default function ProfileBlockedList() {
return ( return (
<> <>
<NewWrapper <NewWrapper
headerComponent={renderHeader()} // headerComponent={renderHeader()}
listData={listData} listData={listData}
renderItem={renderItem} renderItem={renderItem}
onEndReached={loadMore} onEndReached={loadMore}

View File

@@ -64,14 +64,18 @@ export default function Profile() {
}; };
const onLoadPortofolio = async (id: string) => { const onLoadPortofolio = async (id: string) => {
const response = await apiGetPortofolio({ id: id }); try {
const lastTwoByDate = response.data const response = await apiGetPortofolio({ id: id });
.sort( const lastTwoByDate = response.data
(a: any, b: any) => .sort(
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() (a: any, b: any) =>
) // urut desc new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
.slice(0, 2); ) // urut desc
setListPortofolio(lastTwoByDate); .slice(0, 2);
setListPortofolio(lastTwoByDate);
} catch (error) {
console.log("[ERROR]", error);
}
}; };
return ( return (

View File

@@ -6,6 +6,7 @@ import {
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { useAuth } from "@/hooks/use-auth";
import Voting_BoxPublishSection from "@/screens/Voting/BoxPublishSection"; import Voting_BoxPublishSection from "@/screens/Voting/BoxPublishSection";
import { apiVotingGetAll } from "@/service/api-client/api-voting"; import { apiVotingGetAll } from "@/service/api-client/api-voting";
import { router, useFocusEffect } from "expo-router"; import { router, useFocusEffect } from "expo-router";
@@ -13,6 +14,7 @@ import _ from "lodash";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
export default function VotingBeranda() { export default function VotingBeranda() {
const { user } = useAuth();
const [listData, setListData] = useState<any>([]); const [listData, setListData] = useState<any>([]);
const [loadingGetData, setLoadingGetData] = useState(false); const [loadingGetData, setLoadingGetData] = useState(false);
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
@@ -29,6 +31,7 @@ export default function VotingBeranda() {
const response = await apiVotingGetAll({ const response = await apiVotingGetAll({
search, search,
category: "beranda", category: "beranda",
userLoginId: user?.id,
}); });
if (response.success) { if (response.success) {
setListData(response.data); setListData(response.data);

View File

@@ -134,7 +134,7 @@ export default function VotingDetailStatus() {
{data && {data &&
data?.catatan && data?.catatan &&
(status === "draft" || status === "rejected") && ( (status === "draft" || status === "reject") && (
<ReportBox text={data?.catatan} /> <ReportBox text={data?.catatan} />
)} )}

View File

@@ -4,6 +4,7 @@ import {
ButtonCenteredOnly, ButtonCenteredOnly,
ButtonCustom, ButtonCustom,
InformationBox, InformationBox,
NewWrapper,
StackCustom, StackCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
@@ -12,6 +13,7 @@ import { useAuth } from "@/hooks/use-auth";
import { apiUser } from "@/service/api-client/api-user"; import { apiUser } from "@/service/api-client/api-user";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { router } from "expo-router"; import { router } from "expo-router";
import { RefreshControl } from "react-native";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
export default function WaitingRoom() { export default function WaitingRoom() {
@@ -33,7 +35,7 @@ export default function WaitingRoom() {
} else { } else {
Toast.show({ Toast.show({
type: "success", type: "success",
text1: "Akun anda telah aktif", // text2: "Anda berhasil login", text1: "Selamat ! Akun anda telah aktif", // text2: "Anda berhasil login",
}); });
router.replace(`/(application)/(user)/profile/create`); router.replace(`/(application)/(user)/profile/create`);
} }
@@ -82,10 +84,18 @@ export default function WaitingRoom() {
return ( return (
<> <>
<ViewWrapper footerComponent={logoutButton()}> <NewWrapper
footerComponent={logoutButton()}
refreshControl={
<RefreshControl refreshing={isLoading} onRefresh={handleCheck} />
}
>
<StackCustom> <StackCustom>
<InformationBox text="Permohonan akses Anda sedang dalam proses verifikasi oleh admin. Harap tunggu, Anda akan menerima pemberitahuan melalui Whatsapp setelah disetujui." /> <InformationBox
<ButtonCenteredOnly text="Akun Anda sedang menunggu aktivasi.
Silakan tunggu beberapa saat. Untuk memperbarui status, tarik layar ke bawah."
/>
{/* <ButtonCenteredOnly
isLoading={isLoading} isLoading={isLoading}
onPress={() => { onPress={() => {
handleCheck(); handleCheck();
@@ -93,9 +103,9 @@ export default function WaitingRoom() {
icon="refresh-ccw" icon="refresh-ccw"
> >
Check Check
</ButtonCenteredOnly> </ButtonCenteredOnly> */}
</StackCustom> </StackCustom>
</ViewWrapper> </NewWrapper>
</> </>
); );
} }

View File

@@ -1,17 +1,56 @@
import { import {
BoxButtonOnFooter, BoxButtonOnFooter,
ButtonCustom, ButtonCustom,
StackCustom,
TextCustom,
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { MainColor } from "@/constants/color-palet";
import { apiAdminMasterDonationCategoryCreate } from "@/service/api-admin/api-master-admin";
import { useRouter } from "expo-router"; import { useRouter } from "expo-router";
import { useState } from "react";
import { Switch } from "react-native-paper";
import Toast from "react-native-toast-message";
export default function AdminDonationCategoryCreate() { export default function AdminDonationCategoryCreate() {
const router = useRouter(); const router = useRouter();
const [loading, setLoading] = useState(false);
const [data, setData] = useState({
name: "",
active: false,
});
const onSubmit = async () => {
try {
setLoading(true);
const response = await apiAdminMasterDonationCategoryCreate({ data });
if (response.success) {
Toast.show({
type: "success",
text2: "Data berhasil disimpan",
});
router.back();
return;
}
Toast.show({
type: "error",
text1: "Gagal menyimpan data",
});
} catch (error) {
console.log("[Error]", error);
} finally {
setLoading(false);
}
};
const buttonSubmit = ( const buttonSubmit = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom onPress={() => router.back()}>Simpan</ButtonCustom> <ButtonCustom isLoading={loading} onPress={onSubmit}>
Simpan
</ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
); );
return ( return (
@@ -20,7 +59,23 @@ export default function AdminDonationCategoryCreate() {
headerComponent={<AdminBackButtonAntTitle title="Tambah Kategori" />} headerComponent={<AdminBackButtonAntTitle title="Tambah Kategori" />}
footerComponent={buttonSubmit} footerComponent={buttonSubmit}
> >
<TextInputCustom placeholder="Masukkan Kategori" /> <TextInputCustom
label=""
placeholder="Masukkan Kategori"
value={data.name}
onChangeText={(text) => setData({ ...data, name: text })}
/>
<StackCustom gap={"sm"}>
<TextCustom>Status</TextCustom>
<Switch
style={{
alignSelf: "flex-start",
}}
color={MainColor.yellow}
value={data.active}
onValueChange={(value) => setData({ ...data, active: value })}
/>
</StackCustom>
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -1,21 +1,76 @@
import { import {
AlertDefaultSystem,
BoxButtonOnFooter, BoxButtonOnFooter,
ButtonCustom, ButtonCustom,
StackCustom,
TextCustom,
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { MainColor } from "@/constants/color-palet";
import {
apiAdminMasterDonationCategoryById,
apiAdminMasterDonationCategoryUpdate,
} from "@/service/api-admin/api-master-admin";
import { useLocalSearchParams, useRouter } from "expo-router"; import { useLocalSearchParams, useRouter } from "expo-router";
import { useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { Switch } from "react-native-paper";
export default function AdminDonationCategoryUpdate() { export default function AdminDonationCategoryUpdate() {
const router = useRouter();
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [value, setValue] = useState(id); const [value, setValue] = useState(id);
const router = useRouter(); const [data, setData] = useState<any>(null);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const fetchData = async () => {
const response = await apiAdminMasterDonationCategoryById({
id: id as any,
});
console.log(JSON.stringify(response.data, null, 2));
setData(response.data);
};
fetchData();
}, [id]);
const handlerSubmit = async () => {
try {
setIsLoading(true);
const response = await apiAdminMasterDonationCategoryUpdate({
id: id as any,
data: data,
});
console.log(JSON.stringify(response.data, null, 2));
router.back();
} catch (error) {
console.log(error);
} finally {
setIsLoading(false);
}
};
const buttonSubmit = ( const buttonSubmit = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom onPress={() => router.back()}>Update</ButtonCustom> <ButtonCustom
disabled={isLoading || data?.name === ""}
isLoading={isLoading}
onPress={() => {
AlertDefaultSystem({
title: "Update Data",
message: "Apakah anda yakin ingin mengupdate data ini?",
textLeft: "Batal",
textRight: "Ya",
onPressLeft: () => {},
onPressRight: () => handlerSubmit(),
});
}}
>
Update
</ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
); );
return ( return (
@@ -25,10 +80,28 @@ export default function AdminDonationCategoryUpdate() {
footerComponent={buttonSubmit} footerComponent={buttonSubmit}
> >
<TextInputCustom <TextInputCustom
label="Nama Kategori"
placeholder="Masukkan Kategori" placeholder="Masukkan Kategori"
value={value as any} value={data?.name}
onChangeText={setValue} onChangeText={(value) => setData({ ...data, name: value })}
/> />
<StackCustom
gap={"sm"}
style={{
alignContent: "flex-start",
}}
>
<TextCustom>Status</TextCustom>
<Switch
style={{
alignSelf: "flex-start",
}}
color={MainColor.yellow}
value={data?.active}
onValueChange={(value) => setData({ ...data, active: value })}
/>
</StackCustom>
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -1,11 +1,15 @@
import { import {
ActionIcon, ActionIcon,
BaseBox, BadgeCustom,
CenterCustom, BaseBox,
Spacing, CenterCustom,
StackCustom, ClickableCustom,
TextCustom, DividerCustom,
ViewWrapper, Grid,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components"; } from "@/components";
import { IconEdit } from "@/components/_Icon"; import { IconEdit } from "@/components/_Icon";
import AdminActionIconPlus from "@/components/_ShareComponent/Admin/ActionIconPlus"; import AdminActionIconPlus from "@/components/_ShareComponent/Admin/ActionIconPlus";
@@ -14,14 +18,56 @@ import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
import { GridView_3_3_6 } from "@/components/_ShareComponent/GridView_3_3_6"; import { GridView_3_3_6 } from "@/components/_ShareComponent/GridView_3_3_6";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value"; import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { View } from "react-native"; import { RefreshControl, View } from "react-native";
import { Divider, Switch } from "react-native-paper"; import { Divider, Switch } from "react-native-paper";
import { router } from "expo-router"; import { router, useFocusEffect } from "expo-router";
import { useCallback, useEffect, useState } from "react";
import { apiAdminMasterDonationCategory } from "@/service/api-admin/api-master-admin";
import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8";
import GridTwoView from "@/components/_ShareComponent/GridTwoView";
export default function AdminDonationCategory() { export default function AdminDonationCategory() {
const [listData, setListData] = useState<any[]>([]);
const [refreshing, setRefreshing] = useState(false);
const [loading, setLoading] = useState(false);
useFocusEffect(
useCallback(() => {
fetchMaster();
}, [])
);
const fetchMaster = async () => {
try {
setLoading(true);
const response = await apiAdminMasterDonationCategory();
if (response.success) {
console.log(JSON.stringify(response.data, null, 2));
setListData(response.data);
} else {
setListData([]);
}
} catch (error) {
console.log("[Error]", error);
} finally {
setLoading(false);
}
};
const onRefresh = async () => {
setRefreshing(true);
await fetchMaster();
setRefreshing(false);
};
return ( return (
<> <>
<ViewWrapper headerComponent={<AdminTitlePage title="Donasi" />}> <ViewWrapper
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
headerComponent={<AdminTitlePage title="Donasi" />}
>
<AdminComp_BoxTitle <AdminComp_BoxTitle
title="Kategori" title="Kategori"
rightComponent={ rightComponent={
@@ -33,81 +79,50 @@ export default function AdminDonationCategory() {
} }
/> />
<BaseBox> <View>
<GridView_3_3_6 <Grid>
component1={ <Grid.Col style={{paddingLeft: 10}} span={4}>
<TextCustom bold align="center"> <TextCustom bold>Status</TextCustom>
Aksi </Grid.Col>
</TextCustom> <Grid.Col span={8}>
} <TextCustom bold>Kategori</TextCustom>
component2={<TextCustom bold>Status</TextCustom>} </Grid.Col>
component3={<TextCustom bold>Kategori</TextCustom>} </Grid>
/>
<Divider /> <Divider />
<Spacing /> <Spacing />
<StackCustom> <StackCustom>
{listData.map((item, index) => ( {listData.map((item, index) => (
<View key={index}> <ClickableCustom
<GridView_3_3_6 onPress={() => {
component1={ router.push(`/admin/donation/category-update?id=${item.id}`);
}}
key={index}
>
<Grid containerStyle={{ paddingBottom: 10 }}>
<Grid.Col
span={4}
style={{paddingLeft: 10}}
>
<CenterCustom> <CenterCustom>
<ActionIcon <BadgeCustom
icon={ color={item.active ? MainColor.green : MainColor.red}
<IconEdit size={ICON_SIZE_BUTTON} color="black" /> >
} {item.active ? "Aktif" : "Tidak Aktif"}
onPress={() => { </BadgeCustom>
router.push(`/admin/donation/category-update?id=${index}`);
}}
/>
</CenterCustom> </CenterCustom>
} </Grid.Col>
component2={ <Grid.Col span={8}>
<Switch <TextCustom bold>{item.name}</TextCustom>
value={true} </Grid.Col>
onValueChange={(item) => { </Grid>
console.log(item);
}}
color={MainColor.yellow}
/>
}
component3={<TextCustom bold>{item.label}</TextCustom>}
/>
<Spacing height={10} />
<Divider /> <Divider />
</View> </ClickableCustom>
))} ))}
</StackCustom> </StackCustom>
</BaseBox> </View>
</ViewWrapper> </ViewWrapper>
</> </>
); );
} }
const listData = [
{
label: "Kegiatan Sosial",
value: "kegiatan_sosial",
},
{
label: "Pendidikan",
value: "pendidikan",
},
{
label: "Kesehatan",
value: "kesehatan",
},
{
label: "Kebudayaan",
value: "kebudayaan",
},
{
label: "Bencana Alami",
value: "bencana_alami",
},
{
label: "Lainnya",
value: "lainnya",
},
];

View File

@@ -74,7 +74,7 @@ export default function TermsAgreement() {
<> <>
<Stack.Screen <Stack.Screen
options={{ options={{
title: "Terms Agreement", title: "Terms & Conditions",
}} }}
/> />
<ViewWrapper footerComponent={footerComponent}> <ViewWrapper footerComponent={footerComponent}>
@@ -87,6 +87,7 @@ export default function TermsAgreement() {
alignItems: "center", alignItems: "center",
marginTop: 16, marginTop: 16,
marginBottom: 16, marginBottom: 16,
paddingInline: 10,
}} }}
> >
<CheckboxCustom value={term} onChange={() => setTerm(!term)} /> <CheckboxCustom value={term} onChange={() => setTerm(!term)} />

View File

@@ -32,9 +32,10 @@ const FloatingButton: React.FC<FloatingButtonProps> = ({
const styles = StyleSheet.create({ const styles = StyleSheet.create({
fab: { fab: {
position: "absolute", position: "absolute",
margin: 16, margin: "auto",
right: 0, right: 0,
bottom: 0, // bottom: 10,
top: -20,
backgroundColor: AccentColor.softblue, // Warna Twitter biru backgroundColor: AccentColor.softblue, // Warna Twitter biru
borderRadius: 50, borderRadius: 50,
borderColor: AccentColor.blue, borderColor: AccentColor.blue,

View File

@@ -1,3 +1,4 @@
import { AccentColor, MainColor } from "@/constants/color-palet";
import { GStyles } from "@/styles/global-styles"; import { GStyles } from "@/styles/global-styles";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { import {
@@ -6,7 +7,9 @@ import {
Text, Text,
View, View,
ViewStyle, ViewStyle,
useColorScheme,
} from "react-native"; } from "react-native";
import { PlaceholderColor } from "@/constants/color-palet";
type IconType = React.ReactNode | string; type IconType = React.ReactNode | string;
@@ -48,7 +51,7 @@ const TextAreaCustom: React.FC<TextAreaCustomProps> = ({
minRows = 4, minRows = 4,
maxRows = 6, maxRows = 6,
showCount = false, showCount = false,
maxLength, maxLength = 1000,
value, value,
onChangeText, onChangeText,
height = 100, height = 100,
@@ -78,6 +81,9 @@ const TextAreaCustom: React.FC<TextAreaCustomProps> = ({
); );
}; };
const colorScheme = useColorScheme();
const theme = PlaceholderColor[colorScheme || "light"];
return ( return (
<View style={[GStyles.inputContainerArea]}> <View style={[GStyles.inputContainerArea]}>
{label && ( {label && (
@@ -109,6 +115,7 @@ const TextAreaCustom: React.FC<TextAreaCustomProps> = ({
GStyles.textAreaInput, GStyles.textAreaInput,
{ color: fontColor }, { color: fontColor },
]} ]}
placeholderTextColor={theme.placeholder}
editable={!disabled} editable={!disabled}
value={value as string} value={value as string}
onChangeText={onChangeText} onChangeText={onChangeText}

View File

@@ -1,3 +1,4 @@
import { PlaceholderColor } from "@/constants/color-palet";
import { GStyles } from "@/styles/global-styles"; import { GStyles } from "@/styles/global-styles";
import Ionicons from "@expo/vector-icons/Ionicons"; import Ionicons from "@expo/vector-icons/Ionicons";
import React, { useState } from "react"; import React, { useState } from "react";
@@ -8,8 +9,10 @@ import {
TouchableOpacity, TouchableOpacity,
View, View,
ViewStyle, ViewStyle,
useColorScheme
} from "react-native"; } from "react-native";
type IconType = React.ReactNode | string; type IconType = React.ReactNode | string;
type Props = { type Props = {
@@ -74,6 +77,9 @@ const TextInputCustom = ({
} }
}; };
const colorScheme = useColorScheme();
const theme = PlaceholderColor[colorScheme || "light"];
return ( return (
<View style={[GStyles.inputContainerArea, containerStyle]}> <View style={[GStyles.inputContainerArea, containerStyle]}>
{label && ( {label && (
@@ -100,12 +106,14 @@ const TextInputCustom = ({
{ color: fontColor }, { color: fontColor },
disabled && GStyles.inputPlaceholderDisabled, // <-- placeholder saat disabled disabled && GStyles.inputPlaceholderDisabled, // <-- placeholder saat disabled
]} ]}
placeholderTextColor={theme.placeholder}
editable={!disabled} editable={!disabled}
secureTextEntry={secureTextEntry && !isPasswordVisible} secureTextEntry={secureTextEntry && !isPasswordVisible}
keyboardType={keyboardType} keyboardType={keyboardType}
onChangeText={handleTextChange} onChangeText={handleTextChange}
maxLength={maxLength} maxLength={maxLength}
{...rest} {...rest}
/> />
{secureTextEntry && ( {secureTextEntry && (
<TouchableOpacity <TouchableOpacity

View File

@@ -1,17 +1,23 @@
import Grid from "@/components/Grid/GridCustom"; import Grid from "@/components/Grid/GridCustom";
import React from "react"; import React from "react";
import { View } from "react-native"; import { StyleProp, View, ViewStyle } from "react-native";
import { Divider } from "react-native-paper"; import { Divider } from "react-native-paper";
export default function AdminTableValue({ export default function AdminTableValue({
value1, value1,
value2, value2,
value3, value3,
style1,
style2,
style3,
bottomLine = false, bottomLine = false,
}: { }: {
value1: React.ReactNode; value1: React.ReactNode;
value2: React.ReactNode; value2: React.ReactNode;
value3: React.ReactNode; value3: React.ReactNode;
style1?: ViewStyle;
style2?: ViewStyle;
style3?: ViewStyle;
bottomLine?: boolean; bottomLine?: boolean;
}) { }) {
return ( return (
@@ -25,6 +31,7 @@ export default function AdminTableValue({
justifyContent: "center", justifyContent: "center",
paddingLeft: 10, paddingLeft: 10,
paddingRight: 10, paddingRight: 10,
...style1,
}} }}
> >
{value1} {value1}
@@ -36,6 +43,7 @@ export default function AdminTableValue({
justifyContent: "center", justifyContent: "center",
paddingLeft: 10, paddingLeft: 10,
paddingRight: 10, paddingRight: 10,
...style2,
}} }}
> >
{value2} {value2}
@@ -44,9 +52,10 @@ export default function AdminTableValue({
span={6} span={6}
style={{ style={{
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "flex-start",
paddingLeft: 10, paddingLeft: 10,
paddingRight: 10, paddingRight: 10,
...style3,
}} }}
> >
{value3} {value3}

View File

@@ -45,3 +45,23 @@ export const AdminColor = {
// Warna Asli: #002e59 // Warna Asli: #002e59
// Warna Lebih Gelap: #001f3b // Warna Lebih Gelap: #001f3b
// Warna Tergelap: #001323 // Warna Tergelap: #001323
export const PlaceholderColor = {
light: {
text: "#000",
placeholder: "#666",
border: "#ccc",
background: "#fff",
error: "#d00",
icon: "#555",
},
dark: {
text: "#fff",
placeholder: "#aaa",
border: "#444",
background: "#1a1a1a",
error: "#ff4d4d",
icon: "#ccc",
},
};

View File

@@ -72,8 +72,31 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
const loginWithNomor = async (nomor: string) => { const loginWithNomor = async (nomor: string) => {
setIsLoading(true); setIsLoading(true);
try { try {
console.log("[Masuk provider]", nomor);
const response = await apiLogin({ nomor: nomor }); const response = await apiLogin({ nomor: nomor });
await AsyncStorage.setItem("kode_otp", response.kodeId); console.log("[RESPONSE AUTH]", JSON.stringify(response));
if (response.success) {
console.log("[Keluar provider]", nomor);
Toast.show({
type: "success",
text1: "Sukses",
text2: "Kode OTP berhasil dikirim",
});
await AsyncStorage.setItem("kode_otp", response.kodeId);
router.push(`/verification?nomor=${nomor}`);
return;
} else {
router.push(`/register?nomor=${nomor}`);
Toast.show({
type: "info",
text1: "Info",
text2: "Silahkan mendaftar",
});
return;
}
} catch (error: any) { } catch (error: any) {
throw new Error(error.response?.data?.message || "Gagal kirim OTP"); throw new Error(error.response?.data?.message || "Gagal kirim OTP");
} finally { } finally {
@@ -81,13 +104,26 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
} }
}; };
// const loginWithNomor = async (nomor: string) => {
// setIsLoading(true);
// try {
// const response = await apiLogin({ nomor: nomor });
// await AsyncStorage.setItem("kode_otp", response.kodeId);
// } catch (error: any) {
// throw new Error(error.response?.data?.message || "Gagal kirim OTP");
// } finally {
// setIsLoading(false);
// }
// };
// --- 2. Validasi OTP & cek user --- // --- 2. Validasi OTP & cek user ---
const validateOtp = async (nomor: string) => { const validateOtp = async (nomor: string) => {
try { try {
setIsLoading(true); setIsLoading(true);
const response = await apiValidationCode({ nomor: nomor }); const response = await apiValidationCode({ nomor: nomor });
const { token } = response; const { token } = response;
console.log("[RESPONSE VALIDASI OTP]", JSON.stringify(response, null, 2));
if (response.success) { if (response.success) {
setToken(token); setToken(token);
await AsyncStorage.setItem("authToken", token); await AsyncStorage.setItem("authToken", token);
@@ -104,20 +140,23 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
if (response.active) { if (response.active) {
if (response.roleId === "1") { if (response.roleId === "1") {
return "/(application)/(user)/home"; router.replace("/(application)/(user)/home");
return;
} else { } else {
return "/(application)/admin/dashboard"; router.replace("/(application)/admin/dashboard");
return;
} }
} else { } else {
return "/(application)/(user)/waiting-room"; router.replace("/(application)/(user)/waiting-room");
return;
} }
} else { } else {
Toast.show({ Toast.show({
type: "info", type: "info",
text1: "Anda belum terdaftar", text1: "Terjadi kesalahan",
text2: "Silahkan daftar terlebih dahulu", text2: "Silahkan coba lagi",
}); });
return `/register?nomor=${nomor}`; return;
} }
} catch (error: any) { } catch (error: any) {
console.log("Error validasi otp >>", (error as Error).message || error); console.log("Error validasi otp >>", (error as Error).message || error);
@@ -132,6 +171,10 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
// --- 3. Ambil data user --- // --- 3. Ambil data user ---
const userData = async (token: string) => { const userData = async (token: string) => {
try { try {
if (!token) {
throw new Error("Token tidak ditemukan");
}
setIsLoading(true); setIsLoading(true);
const response = await apiConfig.get(`/mobile?token=${token}`, { const response = await apiConfig.get(`/mobile?token=${token}`, {
headers: { headers: {
@@ -145,7 +188,10 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
await AsyncStorage.setItem("userData", JSON.stringify(dataUser)); await AsyncStorage.setItem("userData", JSON.stringify(dataUser));
return dataUser; return dataUser;
} catch (error: any) { } catch (error: any) {
console.log("[LOAD USER DATA]",error.response?.data?.message + "user" || "Gagal mengambil data user"); console.log(
"[LOAD USER DATA]",
error.response?.data?.message + "user" || "Gagal mengambil data user"
);
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
@@ -160,9 +206,8 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
setIsLoading(true); setIsLoading(true);
try { try {
const response = await apiRegister({ data: userData }); const response = await apiRegister({ data: userData });
console.log("response", response); console.log("[REGISTER FETCH]", JSON.stringify(response, null, 2));
const { token } = response;
if (!response.success) { if (!response.success) {
Toast.show({ Toast.show({
type: "info", type: "info",
@@ -173,23 +218,63 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
return; return;
} }
setToken(token);
await AsyncStorage.setItem("authToken", token);
Toast.show({ Toast.show({
type: "success", type: "success",
text1: "Sukses", text1: "Sukses",
text2: "Anda berhasil terdaftar", text2: "Anda berhasil terdaftar",
}); });
router.replace("/(application)/(user)/waiting-room"); router.replace(`/verification?nomor=${userData.nomor}`);
return; return;
} catch (error: any) { } catch (error: any) {
Toast.show({
type: "error",
text1: "Error",
text2: error.response?.data?.message || "Gagal mendaftar",
});
console.log("Error register", error); console.log("Error register", error);
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
}; };
// const registerUser = async (userData: {
// username: string;
// nomor: string;
// termsOfServiceAccepted: boolean;
// }) => {
// setIsLoading(true);
// try {
// const response = await apiRegister({ data: userData });
// console.log("response", response);
// const { token } = response;
// if (!response.success) {
// Toast.show({
// type: "info",
// text1: "Info",
// text2: response.message,
// });
// return;
// }
// setToken(token);
// await AsyncStorage.setItem("authToken", token);
// Toast.show({
// type: "success",
// text1: "Sukses",
// text2: "Anda berhasil terdaftar",
// });
// router.replace("/(application)/(user)/waiting-room");
// return;
// } catch (error: any) {
// console.log("Error register", error);
// } finally {
// setIsLoading(false);
// }
// };
// --- 5. Logout --- // --- 5. Logout ---
const logout = async () => { const logout = async () => {
try { try {
setIsLoading(true); setIsLoading(true);

View File

@@ -422,7 +422,7 @@
); );
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = "com.anonymous.hipmi-mobile"; PRODUCT_BUNDLE_IDENTIFIER = "com.anonymous.hipmi-mobile";
PRODUCT_NAME = HIPMIBadungConnect; PRODUCT_NAME = "HIPMIBadungConnect";
SWIFT_OBJC_BRIDGING_HEADER = "HIPMIBadungConnect/HIPMIBadungConnect-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "HIPMIBadungConnect/HIPMIBadungConnect-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@@ -454,7 +454,7 @@
); );
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = "com.anonymous.hipmi-mobile"; PRODUCT_BUNDLE_IDENTIFIER = "com.anonymous.hipmi-mobile";
PRODUCT_NAME = HIPMIBadungConnect; PRODUCT_NAME = "HIPMIBadungConnect";
SWIFT_OBJC_BRIDGING_HEADER = "HIPMIBadungConnect/HIPMIBadungConnect-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "HIPMIBadungConnect/HIPMIBadungConnect-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";

View File

@@ -39,7 +39,7 @@
</dict> </dict>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>9</string> <string>12</string>
<key>ITSAppUsesNonExemptEncryption</key> <key>ITSAppUsesNonExemptEncryption</key>
<false/> <false/>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>

View File

@@ -1,3 +1,4 @@
import { NewWrapper } from "@/components";
import ButtonCustom from "@/components/Button/ButtonCustom"; import ButtonCustom from "@/components/Button/ButtonCustom";
import Spacing from "@/components/_ShareComponent/Spacing"; import Spacing from "@/components/_ShareComponent/Spacing";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper"; import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
@@ -5,9 +6,11 @@ import { MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth"; import { useAuth } from "@/hooks/use-auth";
import { apiVersion } from "@/service/api-config"; import { apiVersion } from "@/service/api-config";
import { GStyles } from "@/styles/global-styles"; import { GStyles } from "@/styles/global-styles";
import { Redirect, router } from "expo-router"; import versionBadge from "@/utils/viersionBadge";
import VersionBadge from "@/utils/viersionBadge";
import { Redirect } from "expo-router";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Text, View } from "react-native"; import { RefreshControl, Text, View } from "react-native";
import PhoneInput, { ICountry } from "react-native-international-phone-number"; import PhoneInput, { ICountry } from "react-native-international-phone-number";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
@@ -16,6 +19,7 @@ export default function LoginView() {
const [selectedCountry, setSelectedCountry] = useState<null | ICountry>(null); const [selectedCountry, setSelectedCountry] = useState<null | ICountry>(null);
const [inputValue, setInputValue] = useState<string>(""); const [inputValue, setInputValue] = useState<string>("");
const [loading, setLoading] = useState<boolean>(false); const [loading, setLoading] = useState<boolean>(false);
const [refreshing, setRefreshing] = useState<boolean>(false);
const { loginWithNomor, token, isAdmin, isUserActive } = useAuth(); const { loginWithNomor, token, isAdmin, isUserActive } = useAuth();
@@ -25,7 +29,18 @@ export default function LoginView() {
async function onLoadVersion() { async function onLoadVersion() {
const res = await apiVersion(); const res = await apiVersion();
setVersion(res.data);
if (res.success) {
setVersion(versionBadge());
}
}
async function handleRefresh() {
setRefreshing(true);
await onLoadVersion();
setInputValue("");
setLoading(false);
setRefreshing(false);
} }
function handleInputValue(phoneNumber: string) { function handleInputValue(phoneNumber: string) {
@@ -66,21 +81,13 @@ export default function LoginView() {
if (!isValid) return; if (!isValid) return;
const callingCode = selectedCountry?.callingCode.replace(/^\+/, "") || ""; const callingCode = selectedCountry?.callingCode.replace(/^\+/, "") || "";
const fixNumber = inputValue.replace(/\s+/g, ""); let fixNumber = inputValue.replace(/\s+/g, "").replace(/^0+/, "");
const realNumber = callingCode + fixNumber; const realNumber = callingCode + fixNumber;
try { try {
setLoading(true); setLoading(true);
// const response = await apiLogin({ nomor: realNumber });
await loginWithNomor(realNumber); await loginWithNomor(realNumber);
Toast.show({
type: "success",
text1: "Sukses",
text2: "Kode OTP berhasil dikirim",
});
router.navigate(`/verification?nomor=${realNumber}`);
} catch (error) { } catch (error) {
console.log("Error login", error); console.log("Error login", error);
Toast.show({ Toast.show({
@@ -91,6 +98,30 @@ export default function LoginView() {
} finally { } finally {
setLoading(false); setLoading(false);
} }
// try {
// setLoading(true);
// // const response = await apiLogin({ nomor: realNumber });
// const response = await loginWithNomor(realNumber);
// console.log("[RESPONSE]", response);
// Toast.show({
// type: "success",
// text1: "Sukses",
// text2: "Kode OTP berhasil dikirim",
// });
// // router.navigate(`/verification?nomor=${realNumber}`);
// } catch (error) {
// console.log("Error login", error);
// Toast.show({
// type: "error",
// text1: "Error",
// text2: error as string,
// });
// } finally {
// setLoading(false);
// }
} }
if (token && token !== "" && !isUserActive) { if (token && token !== "" && !isUserActive) {
@@ -110,7 +141,12 @@ export default function LoginView() {
} }
return ( return (
<ViewWrapper withBackground> <NewWrapper
withBackground
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />
}
>
<View style={GStyles.authContainer}> <View style={GStyles.authContainer}>
<View> <View>
<View style={GStyles.authContainerTitle}> <View style={GStyles.authContainerTitle}>
@@ -154,6 +190,6 @@ export default function LoginView() {
Coba Coba
</ButtonCustom> */} </ButtonCustom> */}
</View> </View>
</ViewWrapper> </NewWrapper>
); );
} }

View File

@@ -17,6 +17,8 @@ import Toast from "react-native-toast-message";
export default function VerificationView() { export default function VerificationView() {
const { nomor } = useLocalSearchParams<{ nomor: string }>(); const { nomor } = useLocalSearchParams<{ nomor: string }>();
console.log("[NOMOR]", nomor);
const [inputOtp, setInputOtp] = useState<string>(""); const [inputOtp, setInputOtp] = useState<string>("");
const [userNumber, setUserNumber] = useState<string>(""); const [userNumber, setUserNumber] = useState<string>("");
const [loading, setLoading] = useState<boolean>(false); const [loading, setLoading] = useState<boolean>(false);
@@ -52,7 +54,7 @@ export default function VerificationView() {
try { try {
const response = await apiCheckCodeOtp({ kodeId }); const response = await apiCheckCodeOtp({ kodeId });
console.log( console.log(
"Response check code otp >>", "[OTP] >>",
JSON.stringify(response.otp, null, 2) JSON.stringify(response.otp, null, 2)
); );
// Kita tidak perlu simpan codeOtp di state karena verifikasi dilakukan di backend // Kita tidak perlu simpan codeOtp di state karena verifikasi dilakukan di backend
@@ -89,8 +91,9 @@ export default function VerificationView() {
// ✅ VERIFIKASI OTOMATIS UNTUK APPLE REVIEW // ✅ VERIFIKASI OTOMATIS UNTUK APPLE REVIEW
if (inputOtp === "1234") { if (inputOtp === "1234") {
try { try {
const response = await validateOtp(nomor as string); await validateOtp(nomor as string);
router.replace(response);
return;
} catch (error) { } catch (error) {
console.log("Error verification", error); console.log("Error verification", error);
Toast.show({ type: "error", text1: "Gagal verifikasi" }); Toast.show({ type: "error", text1: "Gagal verifikasi" });
@@ -103,16 +106,8 @@ export default function VerificationView() {
// 🔁 VERIFIKASI NORMAL (untuk pengguna sungguhan) // 🔁 VERIFIKASI NORMAL (untuk pengguna sungguhan)
try { try {
const response = await validateOtp(nomor as string); await validateOtp(nomor as string);
// registerForPushNotificationsAsync().then((token) => { return
// if (token) {
// console.log("Expo Push Token:", token);
// // TODO: Kirim token ke backend kamu
// } else {
// console.log("Failed to get Expo Push Token");
// }
// });
router.replace(response);
} catch (error) { } catch (error) {
console.log("Error verification", error); console.log("Error verification", error);
Toast.show({ type: "error", text1: "Gagal verifikasi" }); Toast.show({ type: "error", text1: "Gagal verifikasi" });

View File

@@ -1,12 +1,13 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox, BaseBox,
Grid, Grid,
ProgressCustom, ProgressCustom,
StackCustom, StackCustom,
TextCustom, TextCustom,
} from "@/components"; } from "@/components";
import API_STRORAGE from "@/constants/base-url-api-strorage"; import API_STRORAGE from "@/constants/base-url-api-strorage";
import { MainColor } from "@/constants/color-palet";
import DUMMY_IMAGE from "@/constants/dummy-image-value"; import DUMMY_IMAGE from "@/constants/dummy-image-value";
import { countDownAndCondition } from "@/utils/countDownAndCondition"; import { countDownAndCondition } from "@/utils/countDownAndCondition";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
@@ -21,7 +22,7 @@ export default function Investment_BoxBerandaSection({
id: string; id: string;
data: any; data: any;
}) { }) {
// console.log("[DATA By one]", JSON.stringify(data, null, 2)); // console.log("[DATA By one]", JSON.stringify(data, null, 2));
const [value, setValue] = useState({ const [value, setValue] = useState({
sisa: 0, sisa: 0,
@@ -32,6 +33,8 @@ export default function Investment_BoxBerandaSection({
updateCountDown(); updateCountDown();
}, [data]); }, [data]);
console.log("[DATA BERANDA]", JSON.stringify(data, null, 2));
const updateCountDown = () => { const updateCountDown = () => {
const countDown = countDownAndCondition({ const countDown = countDownAndCondition({
duration: data?.pencarianInvestor, duration: data?.pencarianInvestor,
@@ -66,8 +69,10 @@ export default function Investment_BoxBerandaSection({
<TextCustom truncate={2}>{data.title}</TextCustom> <TextCustom truncate={2}>{data.title}</TextCustom>
<ProgressCustom <ProgressCustom
label={`${data.progress}%`} label={`${data.progress}%`}
value={data.progress} value={Number(data.progress)}
size="lg" size="lg"
animated
color="primary"
/> />
{value.reminder ? ( {value.reminder ? (
<View <View
@@ -79,13 +84,11 @@ export default function Investment_BoxBerandaSection({
> >
<Ionicons name="alert-circle-outline" size={16} color="red" /> <Ionicons name="alert-circle-outline" size={16} color="red" />
<TextCustom truncate color="red" size="small"> <TextCustom truncate color="red" size="small">
Periode Investasi Berakhir Periode Berakhir
</TextCustom> </TextCustom>
</View> </View>
) : ( ) : (
<TextCustom> <TextCustom>Sisa waktu: {value.sisa} hari</TextCustom>
Sisa waktu: {value.sisa} hari
</TextCustom>
)} )}
</StackCustom> </StackCustom>
</Grid.Col> </Grid.Col>

View File

@@ -4,9 +4,11 @@ import { router } from "expo-router";
export default function Investment_ButtonInvestasiSection({ export default function Investment_ButtonInvestasiSection({
id, id,
isMine, isMine,
reminder,
}: { }: {
id: string; id: string;
isMine: boolean; isMine: boolean;
reminder: boolean;
}) { }) {
return ( return (
<> <>
@@ -14,11 +16,12 @@ export default function Investment_ButtonInvestasiSection({
<ButtonCustom disabled>Investasi ini milik Anda</ButtonCustom> <ButtonCustom disabled>Investasi ini milik Anda</ButtonCustom>
) : ( ) : (
<ButtonCustom <ButtonCustom
disabled={reminder}
onPress={() => { onPress={() => {
router.navigate(`/investment/${id}/(transaction-flow)`); router.navigate(`/investment/${id}/(transaction-flow)`);
}} }}
> >
Beli Saham {reminder ? "Periode Investasi Berakhir" : "Beli Saham"}
</ButtonCustom> </ButtonCustom>
)} )}
</> </>

View File

@@ -52,7 +52,7 @@ export default function Invesment_DetailDataPublishSection({
<ReportBox text={data?.catatan} /> <ReportBox text={data?.catatan} />
)} )}
<Invesment_BoxProgressSection <Invesment_BoxProgressSection
progress={data?.progress} progress={Number(data?.progress)}
status={status as string} status={status as string}
/> />
<Invesment_BoxDetailDataSection <Invesment_BoxDetailDataSection

View File

@@ -5,16 +5,6 @@ import { Ionicons } from "@expo/vector-icons";
export default function Portofolio_SocialMediaSection({ data }: { data: any }) { export default function Portofolio_SocialMediaSection({ data }: { data: any }) {
const listData = [ const listData = [
{
label: data && data?.facebook ? data.facebook : "-",
icon: (
<Ionicons
name="logo-facebook"
size={ICON_SIZE_SMALL}
color={MainColor.white}
/>
),
},
{ {
label: data && data?.tiktok ? data.tiktok : "-", label: data && data?.tiktok ? data.tiktok : "-",
icon: ( icon: (
@@ -35,6 +25,16 @@ export default function Portofolio_SocialMediaSection({ data }: { data: any }) {
/> />
), ),
}, },
{
label: data && data?.facebook ? data.facebook : "-",
icon: (
<Ionicons
name="logo-facebook"
size={ICON_SIZE_SMALL}
color={MainColor.white}
/>
),
},
{ {
label: data && data?.twitter ? data.twitter : "-", label: data && data?.twitter ? data.twitter : "-",
icon: ( icon: (

View File

@@ -22,9 +22,11 @@ export default function Voting_BoxDetailHasilVotingSection({
<Grid> <Grid>
{listData?.map((item: any, i: number) => ( {listData?.map((item: any, i: number) => (
<Grid.Col span={12 / listData?.length} style={{ alignItems: "center" }} key={i}> <Grid.Col span={12 / listData?.length} style={{ alignItems: "center" }} key={i}>
<StackCustom> <StackCustom style={{
alignItems: "center",
}}>
<CircleContainer value={item?.jumlah} /> <CircleContainer value={item?.jumlah} />
<TextCustom align="center" size="small">{item?.value}</TextCustom> <TextCustom truncate={2} align="center" size="small">{item?.value}</TextCustom>
</StackCustom> </StackCustom>
</Grid.Col> </Grid.Col>
))} ))}

View File

@@ -167,3 +167,56 @@ export async function apiAdminMasterTypeOfEventUpdate({
} }
// ================== END EVENT ================== // // ================== END EVENT ================== //
// ================== START DONATION ================== //
export async function apiAdminMasterDonationCategory() {
try {
const response = await apiConfig.get(`/mobile/admin/master/donation`);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiAdminMasterDonationCategoryById({ id }: { id: string }) {
try {
const response = await apiConfig.get(`/mobile/admin/master/donation/${id}`);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiAdminMasterDonationCategoryUpdate({
id,
data,
}: {
id: string;
data: any;
}) {
try {
const response = await apiConfig.put(
`/mobile/admin/master/donation/${id}`,
{
data: data,
}
);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiAdminMasterDonationCategoryCreate({ data }: { data: any }) {
try {
const response = await apiConfig.post(`/mobile/admin/master/donation`, {
data: data,
});
return response.data;
} catch (error) {
throw error;
}
}
// ================== END DONATION ================== //

View File

@@ -79,12 +79,14 @@ export async function apiVotingUpdateData({
} }
} }
export async function apiVotingGetAll({ search, category, authorId }: { search?: string, category: "beranda" | "contribution" | "all-history" | "my-history", authorId?: string }) { export async function apiVotingGetAll({ search, category, authorId, userLoginId }: { search?: string, category: "beranda" | "contribution" | "all-history" | "my-history", authorId?: string, userLoginId?: string }) {
try { try {
console.log("userLoginId", userLoginId);
const categoryQuery = category ? `?category=${category}` : ""; const categoryQuery = category ? `?category=${category}` : "";
const searchQuery = search ? `&search=${search}` : ""; const searchQuery = search ? `&search=${search}` : "";
const authorIdQuery = authorId ? `&authorId=${authorId}` : ""; const authorIdQuery = authorId ? `&authorId=${authorId}` : "";
const response = await apiConfig.get(`/mobile/voting${categoryQuery}${searchQuery}${authorIdQuery}`); const userLoginIdQuery = userLoginId ? `&userLoginId=${userLoginId}` : "";
const response = await apiConfig.get(`/mobile/voting${categoryQuery}${searchQuery}${authorIdQuery}${userLoginIdQuery}`);
return response.data; return response.data;
} catch (error) { } catch (error) {
throw error; throw error;

View File

@@ -14,13 +14,13 @@ apiConfig.interceptors.request.use(
async (config) => { async (config) => {
console.log("API_BASE_URL >>", API_BASE_URL); console.log("API_BASE_URL >>", API_BASE_URL);
const token = await AsyncStorage.getItem("authToken"); const token = await AsyncStorage.getItem("authToken");
// console.log("[TOKEN] >>", token);
if (token) { if (token) {
// config.timeout = 10000; // config.timeout = 10000;
config.headers["Content-Type"] = "application/json"; config.headers["Content-Type"] = "application/json";
config.headers.Authorization = `Bearer ${token}`; config.headers.Authorization = `Bearer ${token}`;
} }
// console.log("config", JSON.stringify(config, null, 2));
return config; return config;
}, },
(error) => { (error) => {
@@ -29,16 +29,15 @@ apiConfig.interceptors.request.use(
); );
export async function apiVersion() { export async function apiVersion() {
// console.log("API_BASE_URL", API_BASE_URL);
const response = await apiConfig.get("/version"); const response = await apiConfig.get("/version");
return response.data; return response.data;
} }
export async function apiLogin({ nomor }: { nomor: string }) { export async function apiLogin({ nomor }: { nomor: string }) {
const response = await apiConfig.post("/auth/login", { const response = await apiConfig.post("/auth/mobile-login", {
nomor: nomor, nomor: nomor,
}); });
return response.data; return response.data;;
} }
export async function apiCheckCodeOtp({ kodeId }: { kodeId: string }) { export async function apiCheckCodeOtp({ kodeId }: { kodeId: string }) {
@@ -47,7 +46,7 @@ export async function apiCheckCodeOtp({ kodeId }: { kodeId: string }) {
} }
export async function apiValidationCode({ nomor }: { nomor: string }) { export async function apiValidationCode({ nomor }: { nomor: string }) {
const response = await apiConfig.post(`/auth/validasi`, { const response = await apiConfig.post(`/auth/mobile-validasi`, {
nomor: nomor, nomor: nomor,
}); });
return response.data; return response.data;
@@ -58,7 +57,7 @@ export async function apiRegister({
}: { }: {
data: { nomor: string; username: string; termsOfServiceAccepted: boolean }; data: { nomor: string; username: string; termsOfServiceAccepted: boolean };
}) { }) {
const response = await apiConfig.post(`/auth/register`, { const response = await apiConfig.post(`/auth/mobile-register`, {
data: data, data: data,
}); });
return response.data; return response.data;

18
utils/viersionBadge.ts Normal file
View File

@@ -0,0 +1,18 @@
// VersionBadge.tsx
import Constants from "expo-constants";
import { Platform } from "react-native";
export default function versionBadge() {
const expoConfig = Constants.expoConfig;
const version = expoConfig?.version; // "1.0.1"
const iosBuild = expoConfig?.ios?.buildNumber; // "10"
const androidBuild = expoConfig?.android?.versionCode; // 2
const build =
Platform.OS === "ios" ? iosBuild : androidBuild;
const result = `${version} ( ${build} )`;
return result
}