Skip to content

Commit f81f535

Browse files
committed
[WRFE-70](feat): 이미지 업로드 API 연결
1 parent f3995d2 commit f81f535

File tree

4 files changed

+96
-78
lines changed

4 files changed

+96
-78
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import apiClient from '@/shared/api/apiClient';
2+
3+
interface PresignedUrlResponse {
4+
presignedUrls: string[];
5+
}
6+
7+
interface UploadRequest {
8+
fileKeys: string[];
9+
}
10+
11+
export const uploadImage = async (file: File): Promise<string> => {
12+
const fileName = encodeURIComponent(file.name);
13+
const response = await apiClient.post<PresignedUrlResponse, UploadRequest>(
14+
'/files/upload',
15+
{body: {fileKeys: [fileName]}, withAuth: true},
16+
);
17+
18+
const presignedUrl = response.data.presignedUrls[0];
19+
const contentType = file.type || 'image/jpeg';
20+
21+
await fetch(presignedUrl, {
22+
method: 'PUT',
23+
body: file,
24+
headers: {
25+
'Content-Type': contentType,
26+
'Cache-Control': 'public, max-age=31536000',
27+
},
28+
});
29+
30+
return presignedUrl;
31+
};

apps/front/wraffle-webview/src/features/image-handle/api/useImageUpload.ts

Lines changed: 0 additions & 42 deletions
This file was deleted.

apps/front/wraffle-webview/src/widgets/product-list/create/ui/ImageStepList.tsx

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,23 @@
1-
import {useState} from 'react';
1+
import {useState, useRef} from 'react';
2+
import type {ChangeEvent} from 'react';
23
import {AddItemCard, ImageCardWithDelete} from '@/features/image-handle';
3-
import {useImageUpload} from '@/features/image-handle/api/useImageUpload';
4+
import {uploadImage} from '@/features/image-handle/api/imageUpload';
45
import {Button, Label, Typography} from '@wraffle/ui';
56

67
export const ImageStep = ({onNext}: {onNext: (images: string[]) => void}) => {
78
const [images, setImages] = useState<string[]>([]);
8-
const {getImagePresignedUrl, uploadImageToS3} = useImageUpload();
9+
const fileInputRef = useRef<HTMLInputElement>(null);
910

10-
const handleImageUpload = async (e: Event) => {
11-
const input = e.target as HTMLInputElement;
12-
const files = input.files;
11+
const handleImageUpload = async (e: ChangeEvent<HTMLInputElement>) => {
12+
const file = e.target.files?.[0];
1313

14-
if (!files) return;
14+
if (!file) return;
1515

1616
try {
17-
const file = files[0];
18-
const fileName = encodeURIComponent(file.name);
19-
20-
const presignedUrl = await getImagePresignedUrl(fileName);
21-
22-
await uploadImageToS3(presignedUrl, file);
23-
24-
const fileUrl = presignedUrl;
25-
setImages(prev => [...prev, fileUrl].slice(0, 4));
17+
const uploadedImageUrl = await uploadImage(file);
18+
setImages(prev => [...prev, uploadedImageUrl].slice(0, 4));
2619
} catch (error) {
27-
console.error('이미지 업로드 실패:', error);
20+
console.error('이미지 업로드 중 오류가 발생했습니다:', error);
2821
}
2922
};
3023

@@ -52,6 +45,14 @@ export const ImageStep = ({onNext}: {onNext: (images: string[]) => void}) => {
5245
</Label>
5346
</div>
5447

48+
<input
49+
type='file'
50+
ref={fileInputRef}
51+
className='hidden'
52+
accept='image/*'
53+
onChange={handleImageUpload}
54+
/>
55+
5556
<div className='flex flex-wrap gap-4'>
5657
{images.map((url, index) => (
5758
<ImageCardWithDelete
@@ -64,15 +65,7 @@ export const ImageStep = ({onNext}: {onNext: (images: string[]) => void}) => {
6465
{images.length < 4 && (
6566
<AddItemCard
6667
label='이미지 추가'
67-
onClick={() => {
68-
// 웹뷰 연결 후 input 파일 변경
69-
const input = document.createElement('input');
70-
input.type = 'file';
71-
input.accept = 'image/*';
72-
input.multiple = false;
73-
input.onchange = handleImageUpload;
74-
input.click();
75-
}}
68+
onClick={() => fileInputRef.current?.click()}
7669
className='h-40 w-40'
7770
/>
7871
)}

apps/front/wraffle-webview/src/widgets/product-list/create/ui/ProductImageStepList.tsx

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
import {useState, useRef} from 'react';
12
import type {UseFormSetValue} from 'react-hook-form';
23
import type {CreateEventPayload, Product} from '@/entities/product/model';
4+
import {uploadImage} from '@/features/image-handle/api/imageUpload';
5+
import {AddItemCard} from '@/features/image-handle/ui/AddItemCard';
6+
import {ImageCardWithDelete} from '@/features/image-handle/ui/ImageCardWithDelete';
37
import {Button, Typography} from '@wraffle/ui';
48

5-
// TODO
6-
// image api 연동
7-
// 이미지 추가 부분은 api연동하며 구현하겠습니다
89
export const ProductImageStep = ({
910
products,
1011
title,
@@ -16,7 +17,26 @@ export const ProductImageStep = ({
1617
setValue: UseFormSetValue<CreateEventPayload>;
1718
onReturn: () => void;
1819
}) => {
19-
const imageUrl = '';
20+
const [imageUrl, setImageUrl] = useState<string>('');
21+
const fileInputRef = useRef<HTMLInputElement>(null);
22+
23+
const handleImageUpload = async (
24+
event: React.ChangeEvent<HTMLInputElement>,
25+
) => {
26+
const file = event.target.files?.[0];
27+
if (!file) return;
28+
29+
try {
30+
const uploadedImageUrl = await uploadImage(file);
31+
setImageUrl(uploadedImageUrl);
32+
} catch (error) {
33+
console.error('이미지 업로드 중 오류가 발생했습니다:', error);
34+
}
35+
};
36+
37+
const handleDeleteImage = () => {
38+
setImageUrl('');
39+
};
2040

2141
return (
2242
<div className='flex h-full flex-col px-5 pb-20'>
@@ -35,17 +55,33 @@ export const ProductImageStep = ({
3555
</Typography>
3656
</div>
3757

38-
<div className='flex h-60 w-60 items-center justify-center rounded-lg border border-solid border-[#F5F5F7] bg-[#FAFAFB]'>
39-
<span className='text-center text-sm font-medium text-[#ADB5BD]'>
40-
이미지 추가
41-
</span>
42-
</div>
58+
<input
59+
type='file'
60+
ref={fileInputRef}
61+
className='hidden'
62+
accept='image/*'
63+
onChange={handleImageUpload}
64+
/>
65+
66+
{imageUrl ? (
67+
<ImageCardWithDelete
68+
url={imageUrl}
69+
onClick={handleDeleteImage}
70+
className='h-60 w-60'
71+
/>
72+
) : (
73+
<AddItemCard
74+
label={'이미지 추가'}
75+
onClick={() => fileInputRef.current?.click()}
76+
className='h-60 w-60'
77+
/>
78+
)}
4379

4480
<div className='fixed inset-x-0 bottom-0 bg-white px-4'>
4581
<Button
4682
type='button'
4783
className='mb-5 mt-3 disabled:text-[#A1A1AA]'
48-
// disabled={!imageUrl}
84+
disabled={!imageUrl}
4985
onClick={() => {
5086
const updatedProducts = [
5187
...products,

0 commit comments

Comments
 (0)