Fix:
- profile/[id]/edit.tsx: api upload
- profile/[id]/update-photo.tsx: api upload
- service/api-client/api-profile.ts: api profile bisa memilih kategori

Component
Add:
- components/Image/AvatarComp.tsx

### No Issue
This commit is contained in:
2025-08-27 12:16:31 +08:00
parent 7cddc7abe3
commit 2227aaa99f
9 changed files with 106 additions and 38 deletions

View File

@@ -1,9 +1,13 @@
import { LandscapeFrameUploaded, ViewWrapper } from "@/components"; import { ViewWrapper } from "@/components";
import API_STRORAGE from "@/constants/base-url-api-strorage";
import { Image } from "expo-image";
import { useLocalSearchParams } from "expo-router";
export default function PreviewImage() { export default function PreviewImage() {
const { id } = useLocalSearchParams();
return ( return (
<ViewWrapper> <ViewWrapper>
<LandscapeFrameUploaded /> <Image source={API_STRORAGE.GET({ fileId: id as string })} contentFit="contain" style={{ width: "100%", height: "100%" }}/>
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -39,7 +39,11 @@ export default function ProfileEdit() {
const handleUpdate = async () => { const handleUpdate = async () => {
try { try {
setIsLoading(true); setIsLoading(true);
const response = await apiUpdateProfile({ id: id as string, data }); const response = await apiUpdateProfile({
id: id as string,
data,
category: "profile",
});
if (!response.success) { if (!response.success) {
Toast.show({ Toast.show({
type: "info", type: "info",

View File

@@ -1,15 +1,14 @@
import { import {
AvatarCustom,
BaseBox, BaseBox,
BoxButtonOnFooter, BoxButtonOnFooter,
ButtonCenteredOnly, ButtonCenteredOnly,
ButtonCustom, ButtonCustom
} from "@/components"; } from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper"; 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 { apiProfile } 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";
import pickImage from "@/utils/pickImage"; import pickImage from "@/utils/pickImage";
@@ -53,6 +52,12 @@ export default function UpdatePhotoProfile() {
console.log("Upload res >>", JSON.stringify(response, null, 2)); console.log("Upload res >>", JSON.stringify(response, null, 2));
if (response.success) { if (response.success) {
const imageId = response.data.id;
await apiUpdateProfile({
id: id as string,
data: { imageId },
category: "photo",
});
router.back(); router.back();
} }
} catch (error) { } catch (error) {
@@ -78,15 +83,15 @@ export default function UpdatePhotoProfile() {
); );
const image = imageUri ? ( const image = imageUri ? (
<Image source={{ uri: imageUri }} style={{ width: 200, height: 200 }} /> <Image source={{ uri: imageUri }} style={{ width: "100%", height: "100%" }} />
) : ( ) : (
<AvatarCustom <Image
size="xl"
source={ source={
data?.imageId data?.imageId
? API_STRORAGE.GET({ fileId: data.imageId }) ? { uri: API_STRORAGE.GET({ fileId: data.imageId }) }
: DUMMY_IMAGE.avatar : DUMMY_IMAGE.avatar
} }
style={{ width: "100%", height: "100%" }}
/> />
); );

Binary file not shown.

Before

Width:  |  Height:  |  Size: 395 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View File

@@ -0,0 +1,59 @@
import API_STRORAGE from "@/constants/base-url-api-strorage";
import DUMMY_IMAGE from "@/constants/dummy-image-value";
import { Href, router } from "expo-router";
import { TouchableOpacity } from "react-native";
import { Avatar } from "react-native-paper";
type Size = "base" | "sm" | "md" | "lg" | "xl";
const sizeMap = {
base: 40,
sm: 60,
md: 80,
lg: 100,
xl: 120,
};
interface AvatarCompProps {
fileId?: string;
size: Size;
onPress?: () => void | any;
href?: Href | undefined | any;
}
export default function AvatarComp({
fileId,
size,
onPress,
href = `/(application)/(image)/preview-image/${fileId}`,
}: AvatarCompProps) {
const dimension = sizeMap[size];
const avatarImage = () => {
return (
<Avatar.Image
size={dimension}
source={
fileId ? { uri: API_STRORAGE.GET({ fileId }) } : DUMMY_IMAGE.avatar
}
/>
);
};
return (
<>
{onPress || href ? (
<TouchableOpacity
activeOpacity={0.9}
onPress={
href && fileId ? () => router.navigate(href as any) : onPress
}
>
{avatarImage()}
</TouchableOpacity>
) : (
avatarImage()
)}
</>
);
}

View File

@@ -14,8 +14,8 @@ type Size = "base" | "sm" | "md" | "lg" | "xl";
interface AvatarCustomProps { interface AvatarCustomProps {
source?: ImageSourcePropType; source?: ImageSourcePropType;
size?: Size; size?: Size;
onPress?: () => void; onPress?: () => any;
href?: Href | undefined; href?: Href | undefined | any;
} }
const sizeMap = { const sizeMap = {

View File

@@ -38,6 +38,7 @@ import StackCustom from "./Stack/StackCustom";
import SelectCustom from "./Select/SelectCustom"; import SelectCustom from "./Select/SelectCustom";
// Image // Image
import AvatarCustom from "./Image/AvatarCustom"; import AvatarCustom from "./Image/AvatarCustom";
import AvatarComp from "./Image/AvatarComp";
import LandscapeFrameUploaded from "./Image/LandscapeFrameUploaded"; import LandscapeFrameUploaded from "./Image/LandscapeFrameUploaded";
// Divider // Divider
import Divider from "./Divider/Divider"; import Divider from "./Divider/Divider";
@@ -69,6 +70,7 @@ export {
AlertDefaultSystem, AlertDefaultSystem,
// Image // Image
AvatarCustom, AvatarCustom,
AvatarComp,
// ShareComponent // ShareComponent
AvatarUsernameAndOtherComponent, AvatarUsernameAndOtherComponent,
// Button // Button

View File

@@ -1,4 +1,4 @@
import { AvatarCustom, ClickableCustom } from "@/components"; import { AvatarComp, ClickableCustom } from "@/components";
import { AccentColor } from "@/constants/color-palet"; import { AccentColor } from "@/constants/color-palet";
import DUMMY_IMAGE from "@/constants/dummy-image-value"; import DUMMY_IMAGE from "@/constants/dummy-image-value";
import { router } from "expo-router"; import { router } from "expo-router";
@@ -12,38 +12,30 @@ const AvatarAndBackground = ({
imageId: string; imageId: string;
}) => { }) => {
return ( return (
// console.log("backgroundId", backgroundId), console.log("backgroundId", backgroundId),
// console.log("imageId", imageId), (
<View style={styles.container}> <View style={styles.container}>
{/* Background Image */} {/* 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}>
<ClickableCustom <ClickableCustom
onPress={() => { onPress={() => {
router.navigate( router.navigate(
`/(application)/(image)/preview-image/${imageId}` `/(application)/(image)/preview-image/${backgroundId}`
); );
}} }}
> >
<AvatarCustom source={DUMMY_IMAGE.avatar} size="lg" /> <ImageBackground
source={DUMMY_IMAGE.background}
style={styles.backgroundImage}
resizeMode="cover"
/>
</ClickableCustom> </ClickableCustom>
{/* Avatar yang sedikit keluar */}
<View style={styles.avatarOverlap}>
<AvatarComp size="lg" fileId={imageId} />
</View>
</View> </View>
</View> )
); );
}; };

View File

@@ -15,11 +15,13 @@ export async function apiProfile({ id }: { id: string }) {
export async function apiUpdateProfile({ export async function apiUpdateProfile({
id, id,
data, data,
category,
}: { }: {
id: string; id: string;
data: any; data: any;
category: "profile" | "photo" | "background";
}) { }) {
const response = await apiConfig.put(`/mobile/profile/${id}`, { const response = await apiConfig.put(`/mobile/profile/${id}?category=${category}`, {
data: data, data: data,
}); });
return response.data; return response.data;