Add:
- service/api-client/api-file.ts: upload & delete

Portofolio
Fix:
- user)/portofolio/[id]/create.tsx: Loading submit
- (user)/portofolio/[id]/index.tsx: Delete button recode

Profile
Fix:
- (user)/profile/[id]/update-photo && upload-backgroud: delete image yang kama

### No Issue
This commit is contained in:
2025-09-01 12:11:21 +08:00
parent 41a4a94255
commit bb95e8ccbd
12 changed files with 195 additions and 86 deletions

View File

@@ -22,7 +22,9 @@ export default function Application() {
async function onLoadData() { async function onLoadData() {
const response = await apiUser(user?.id as string); const response = await apiUser(user?.id as string);
console.log("User >>", JSON.stringify(response.data, null, 2)); console.log("User >>", JSON.stringify(response.data.username, null, 2));
console.log("Profile Check >>", JSON.stringify(response.data.Profile.id, null, 2));
setData(response.data); setData(response.data);
} }

View File

@@ -39,7 +39,6 @@ export default function PortofolioCreate() {
const [data, setData] = useState({ const [data, setData] = useState({
namaBisnis: "", namaBisnis: "",
masterBidangBisnisId: "", masterBidangBisnisId: "",
// sub_bidang_usaha: "",
alamatKantor: "", alamatKantor: "",
tlpn: "", tlpn: "",
deskripsi: "", deskripsi: "",
@@ -67,6 +66,8 @@ export default function PortofolioCreate() {
tiktok: "", tiktok: "",
}); });
const [isLoadingCreate, setIsLoadingCreate] = useState(false);
function handleInputValue(phoneNumber: string) { function handleInputValue(phoneNumber: string) {
setInputValue(phoneNumber); setInputValue(phoneNumber);
const callingCode = selectedCountry?.callingCode.replace(/^\+/, "") || ""; const callingCode = selectedCountry?.callingCode.replace(/^\+/, "") || "";
@@ -111,6 +112,8 @@ export default function PortofolioCreate() {
dataMedsos={dataMedsos} dataMedsos={dataMedsos}
imageUri={imageUri} imageUri={imageUri}
subBidangSelected={listSubBidangSelected} subBidangSelected={listSubBidangSelected}
isLoadingCreate={isLoadingCreate}
setIsLoadingCreate={setIsLoadingCreate}
/> />
} }
> >
@@ -220,7 +223,7 @@ export default function PortofolioCreate() {
maxRows={5} maxRows={5}
required required
showCount showCount
maxLength={100} maxLength={1000}
/> />
<Spacing /> <Spacing />

View File

@@ -1,4 +1,4 @@
import { AlertCustom, DrawerCustom, Spacing, StackCustom } from "@/components"; import { DrawerCustom, Spacing, StackCustom } from "@/components";
import LeftButtonCustom from "@/components/Button/BackButton"; import LeftButtonCustom from "@/components/Button/BackButton";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper"; import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
@@ -12,12 +12,7 @@ import Portofolio_SocialMediaSection from "@/screens/Portofolio/SocialMediaSecti
import { apiGetOnePortofolio } from "@/service/api-client/api-portofolio"; import { apiGetOnePortofolio } from "@/service/api-client/api-portofolio";
import { GStyles } from "@/styles/global-styles"; import { GStyles } from "@/styles/global-styles";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { import { Stack, useFocusEffect, useLocalSearchParams } from "expo-router";
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { TouchableOpacity } from "react-native"; import { TouchableOpacity } from "react-native";
@@ -25,7 +20,7 @@ export default function Portofolio() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const { user } = useAuth(); const { user } = useAuth();
const [isDrawerOpen, setIsDrawerOpen] = useState(false); const [isDrawerOpen, setIsDrawerOpen] = useState(false);
const [deleteAlert, setDeleteAlert] = useState(false); const [isLoadingDelete, setIsLoadingDelete] = useState(false);
const [data, setData] = useState<any>(); const [data, setData] = useState<any>();
const openDrawer = () => { const openDrawer = () => {
@@ -43,6 +38,10 @@ export default function Portofolio() {
async function onLoadData(id: string) { async function onLoadData(id: string) {
const response = await apiGetOnePortofolio({ id: id }); const response = await apiGetOnePortofolio({ id: id });
console.log(
"Response portofolio >>",
JSON.stringify(response.data.namaBisnis, null, 2)
);
setData(response.data); setData(response.data);
} }
@@ -79,7 +78,11 @@ export default function Portofolio() {
<Portofolio_Data data={data} /> <Portofolio_Data data={data} />
<Portofolio_BusinessLocation /> <Portofolio_BusinessLocation />
<Portofolio_SocialMediaSection data={data?.Portofolio_MediaSosial} /> <Portofolio_SocialMediaSection data={data?.Portofolio_MediaSosial} />
<Portofolio_ButtonDelete setShowDeleteAlert={setDeleteAlert} /> <Portofolio_ButtonDelete
id={id as string}
isLoadingDelete={isLoadingDelete}
setIsLoadingDelete={setIsLoadingDelete}
/>
<Spacing /> <Spacing />
</StackCustom> </StackCustom>
</ViewWrapper> </ViewWrapper>
@@ -95,22 +98,6 @@ export default function Portofolio() {
setIsDrawerOpen={setIsDrawerOpen} setIsDrawerOpen={setIsDrawerOpen}
/> />
</DrawerCustom> </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

@@ -8,6 +8,8 @@ import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import API_STRORAGE from "@/constants/base-url-api-strorage"; import API_STRORAGE from "@/constants/base-url-api-strorage";
import DIRECTORY_ID from "@/constants/directory-id"; import DIRECTORY_ID from "@/constants/directory-id";
import DUMMY_IMAGE from "@/constants/dummy-image-value"; import DUMMY_IMAGE from "@/constants/dummy-image-value";
import { useAuth } from "@/hooks/use-auth";
import { apiFileDelete } from "@/service/api-client/api-file";
import { apiProfile, apiUpdateProfile } from "@/service/api-client/api-profile"; import { apiProfile, apiUpdateProfile } from "@/service/api-client/api-profile";
import { uploadImageService } from "@/service/upload-service"; import { uploadImageService } from "@/service/upload-service";
import { IProfile } from "@/types/Type-Profile"; import { IProfile } from "@/types/Type-Profile";
@@ -22,6 +24,7 @@ export default function UpdateBackgroundProfile() {
const [data, setData] = useState<IProfile>(); const [data, setData] = useState<IProfile>();
const [imageUri, setImageUri] = useState<string | null>(null); const [imageUri, setImageUri] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const { token } = useAuth();
useFocusEffect( useFocusEffect(
useCallback(() => { useCallback(() => {
@@ -32,10 +35,6 @@ export default function UpdateBackgroundProfile() {
async function onLoadData(id: string) { async function onLoadData(id: string) {
try { try {
const response = await apiProfile({ id }); const response = await apiProfile({ id });
console.log(
"response image id >>",
JSON.stringify(response.data.backgroundId, null, 2)
);
setData(response.data); setData(response.data);
} catch (error) { } catch (error) {
console.log("error get profile >>", error); console.log("error get profile >>", error);
@@ -68,12 +67,23 @@ export default function UpdateBackgroundProfile() {
return; return;
} }
if (data?.imageBackgroundId) {
const deletePrevFile = await apiFileDelete({
token: token as string,
id: data?.imageBackgroundId as string,
});
if (!deletePrevFile.success) {
console.log("error delete prev file >>", deletePrevFile.message);
}
}
Toast.show({ Toast.show({
type: "success", type: "success",
text1: "Sukses", text1: "Sukses",
text2: "Background berhasil diupdate", text2: "Background berhasil diupdate",
}); });
router.back(); router.back();
} }
} catch (error) { } catch (error) {

View File

@@ -16,12 +16,15 @@ import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { Image } from "react-native"; import { Image } from "react-native";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
import { useAuth } from "@/hooks/use-auth";
import { apiFileDelete } from "@/service/api-client/api-file";
export default function UpdatePhotoProfile() { export default function UpdatePhotoProfile() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [data, setData] = useState<IProfile>(); const [data, setData] = useState<IProfile>();
const [imageUri, setImageUri] = useState<string | null>(null); const [imageUri, setImageUri] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const { token } = useAuth();
useFocusEffect( useFocusEffect(
useCallback(() => { useCallback(() => {
@@ -32,6 +35,7 @@ export default function UpdatePhotoProfile() {
async function onLoadData(id: string) { async function onLoadData(id: string) {
try { try {
const response = await apiProfile({ id }); const response = await apiProfile({ id });
setData(response.data); setData(response.data);
} catch (error) { } catch (error) {
console.log("error get profile >>", error); console.log("error get profile >>", error);
@@ -47,8 +51,6 @@ export default function UpdatePhotoProfile() {
dirId: DIRECTORY_ID.profile_foto, dirId: DIRECTORY_ID.profile_foto,
}); });
console.log("response upload photo>>", JSON.stringify(response, null, 2));
if (response.success) { if (response.success) {
const fileId = response.data.id; const fileId = response.data.id;
const responseUpdate = await apiUpdateProfile({ const responseUpdate = await apiUpdateProfile({
@@ -66,12 +68,23 @@ export default function UpdatePhotoProfile() {
return; return;
} }
if (data?.imageId) {
const deletePrevFile = await apiFileDelete({
token: token as string,
id: data?.imageId as string,
});
if (!deletePrevFile.success) {
console.log("error delete prev file >>", deletePrevFile.message);
}
}
Toast.show({ Toast.show({
type: "success", type: "success",
text1: "Sukses", text1: "Sukses",
text2: "Photo berhasil diupdate", text2: "Photo berhasil diupdate",
}); });
router.back(); router.back();
} }
} catch (error) { } catch (error) {

View File

@@ -2,7 +2,7 @@
import { apiConfig } from "@/service/api-config"; import { apiConfig } from "@/service/api-config";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
export async function tryUploadService({ export default async function tryUploadService({
dirId, dirId,
imageUri, imageUri,
}: { }: {

View File

@@ -61,7 +61,7 @@ const ButtonCustom: React.FC<ButtonProps> = ({
activeOpacity={0.8} activeOpacity={0.8}
> >
{/* Render icon jika tersedia */} {/* Render icon jika tersedia */}
{iconLeft && iconLeft} {isLoading ? "" : iconLeft && iconLeft}
{isLoading ? ( {isLoading ? (
<ActivityIndicator size={18} color={MainColor.darkblue} /> <ActivityIndicator size={18} color={MainColor.darkblue} />
) : ( ) : (

View File

@@ -11,6 +11,8 @@ interface Props {
dataMedsos: any; dataMedsos: any;
imageUri: string | null; imageUri: string | null;
subBidangSelected: any[]; subBidangSelected: any[];
isLoadingCreate: boolean;
setIsLoadingCreate: (value: boolean) => void;
} }
interface ICreatePortofolio { interface ICreatePortofolio {
@@ -34,9 +36,34 @@ export default function Portofolio_ButtonCreate({
dataMedsos, dataMedsos,
imageUri, imageUri,
subBidangSelected, subBidangSelected,
isLoadingCreate,
setIsLoadingCreate,
}: Props) { }: Props) {
const validaasiData = () => {
if (
!data.namaBisnis ||
!data.masterBidangBisnisId ||
!data.alamatKantor ||
!data.tlpn ||
!data.deskripsi
) {
return false;
}
return true;
};
const handleCreatePortofolio = async () => { const handleCreatePortofolio = async () => {
if (!validaasiData()) {
Toast.show({
type: "info",
text1: "Info",
text2: "Harap lengkapi data",
});
return;
}
try { try {
setIsLoadingCreate(true);
let fileId = ""; let fileId = "";
if (imageUri) { if (imageUri) {
const response = await uploadImageService({ const response = await uploadImageService({
@@ -81,41 +108,19 @@ export default function Portofolio_ButtonCreate({
text1: "Error", text1: "Error",
text2: error as string, text2: error as string,
}); });
} finally {
setIsLoadingCreate(false);
} }
}; };
// const onCreatePortofolio = async ({ fileId }: { fileId: string }) => {
// const newData: ICreatePortofolio = {
// namaBisnis: data.namaBisnis,
// masterBidangBisnisId: data.masterBidangBisnisId,
// alamatKantor: data.alamatKantor,
// tlpn: data.tlpn,
// deskripsi: data.deskripsi,
// fileId: fileId,
// facebook: dataMedsos.facebook,
// twitter: dataMedsos.twitter,
// instagram: dataMedsos.instagram,
// tiktok: dataMedsos.tiktok,
// youtube: dataMedsos.youtube,
// };
// try {
// const response = await apiPortofolioCreate({
// id: id,
// data: newData,
// });
// console.log("Response >>", JSON.stringify(response, null, 2));
// return response;
// } catch (error) {
// throw error;
// }
// // router.replace(`/maps/create`);
// };
return ( return (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom onPress={handleCreatePortofolio}>Selanjutnya</ButtonCustom> <ButtonCustom
isLoading={isLoadingCreate}
onPress={handleCreatePortofolio}
>
Selanjutnya
</ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
); );
} }

View File

@@ -1,17 +1,53 @@
import { ButtonCustom } from "@/components"; import { AlertDefaultSystem, ButtonCustom } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { apiDeletePortofolio } from "@/service/api-client/api-portofolio";
import { router } from "expo-router";
export default function Portofolio_ButtonDelete({ export default function Portofolio_ButtonDelete({
setShowDeleteAlert, id,
isLoadingDelete,
setIsLoadingDelete,
}: { }: {
setShowDeleteAlert: (value: boolean) => void; id: string;
isLoadingDelete: boolean;
setIsLoadingDelete: (value: boolean) => void;
}) { }) {
const handleDelete = () => { const handleDelete = async () => {
setShowDeleteAlert(true); AlertDefaultSystem({
title: "Hapus Portofolio",
message: "Yakin ingin menghapus portofolio ini?",
textLeft: "Batal",
textRight: "Hapus",
onPressRight: async () => {
try {
setIsLoadingDelete(true);
const response = await apiDeletePortofolio({ id: id });
console.log("Response portofolio >>", response);
if (response.success) {
console.log("Portofolio berhasil dihapus");
router.back();
}
console.log("Gagal dihapus >>", response);
} catch (error) {
console.log("Error delete portofolio >>", error);
} finally {
setIsLoadingDelete(false);
}
},
})
}; };
return ( return (
<ButtonCustom textColor={MainColor.white} iconLeft={<Ionicons name="trash-outline" size={20} color="white" />} onPress={handleDelete} backgroundColor={MainColor.red}> <ButtonCustom
isLoading={isLoadingDelete}
textColor={MainColor.white}
iconLeft={<Ionicons name="trash-outline" size={20} color="white" />}
onPress={handleDelete}
backgroundColor={MainColor.red}
>
Hapus Hapus
</ButtonCustom> </ButtonCustom>
); );

View File

@@ -0,0 +1,45 @@
import axios from "axios";
import { API_BASE_URL } from "../api-config";
const url = `${API_BASE_URL}/mobile/file`;
export async function apiFileUpload({
token,
formData,
}: {
token: string;
formData: FormData;
}) {
try {
const response = await axios.post(url, formData, {
headers: {
"Content-Type": "multipart/form-data",
Authorization: `Bearer ${token}`,
},
// timeout: 30000,
});
return response.data;
} catch (error) {
throw error;
}
}
export async function apiFileDelete({
token,
id,
}: {
token: string;
id: string;
}) {
try {
const response = await axios.delete(`${url}/${id}`, {
headers: {
Authorization: `Bearer ${token}`,
},
// timeout: 30000,
});
return response.data;
} catch (error) {
throw error;
}
}

View File

@@ -31,3 +31,14 @@ export async function apiGetOnePortofolio({ id }: { id: string }) {
throw error; throw error;
} }
} }
export async function apiDeletePortofolio({ id }: { id: string }) {
try {
const response = await apiConfig.delete(`/mobile/portofolio/${id}`);
return response.data;
} catch (error) {
throw error;
}
}

View File

@@ -1,6 +1,6 @@
import { API_BASE_URL } from "@/service/api-config"; import { API_BASE_URL } from "@/service/api-config";
import AsyncStorage from "@react-native-async-storage/async-storage"; import AsyncStorage from "@react-native-async-storage/async-storage";
import axios from "axios"; import { apiFileUpload } from "./api-client/api-file";
export async function uploadImageService({ export async function uploadImageService({
dirId, dirId,
@@ -10,7 +10,7 @@ export async function uploadImageService({
imageUri: string | null; imageUri: string | null;
}) { }) {
const token = await AsyncStorage.getItem("authToken"); const token = await AsyncStorage.getItem("authToken");
const url = `${API_BASE_URL}/mobile/upload`; const url = `${API_BASE_URL}/mobile/file`;
console.log("url >>", url); console.log("url >>", url);
@@ -33,21 +33,18 @@ export async function uploadImageService({
}); });
formData.append("dirId", dirId); formData.append("dirId", dirId);
const response = await axios.post(url, formData, { const response = await apiFileUpload({
headers: { token: token as string,
"Content-Type": "multipart/form-data", formData,
Authorization: `Bearer ${token}`,
},
// timeout: 30000,
}); });
const { data } = response; console.log("Response upload file >>", JSON.stringify(response, null, 2));
if (!data.success) { if (!response.success) {
throw new Error(data.message); throw new Error(response.message);
} }
return data; return response;
} catch (error) { } catch (error) {
throw error; throw error;
} }