AWS S3μμ νΉμ νμΌμ μ λ‘λν μ μλλ‘ μ νλ μκ° λμ μ ν¨ν μλͺ λ URLμ λ°κΈνλ κΈ°λ₯
μ΄λ₯Ό νμ©νλ©΄ μλ²λ₯Ό κ±°μΉμ§ μκ³ ν΄λΌμ΄μΈνΈμμ μ§μ S3μ μ΄λ―Έμ§λ₯Ό μ λ‘λ ν μ μλ€.
- ν΄λΌμ΄μΈνΈμμ νμΌ μ 보λ₯Ό ν¬ν¨ν μμ²μ λ°±μλμ 보λ
- λ°±μλμμ Presigned URLμ μμ±νμ¬ μλ΅
- ν΄λΌμ΄μΈνΈμμ ν΄λΉ Presigned URLμ μ¬μ©νμ¬ S3μ μ§μ μ λ‘λ
- μ λ‘λκ° μλ£λλ©΄ μ΄λ―Έμ§ URLμ μλ²μ μ μ₯
// utils/uploadImageToS3.ts
const getPresignedUrl = async (file: File, type: string) => {
try {
const fileName = file.name;
const fileType = file.type;
const response = await axios.post(`${BASE_URL}/upload/${type}`, {
fileName,
fileType,
});
if (response.data) {
const { presignedUrl, s3ObjectUrl } = response.data;
return { presignedUrl, s3ObjectUrl };
}
throw new Error("Presigned URL not found in response.");
} catch (error) {
console.error("π Failed to fetch presigned URL: ", error);
throw error;
}
};
const uploadImageToS3 = async (file: File, type: string) => {
try {
const fileType = file.type;
// Presigned URL μμ²
const { presignedUrl, s3ObjectUrl } = await getPresignedUrl(file, type);
// Presigned URLμ μ΄μ©ν΄ νμΌ μ
λ‘λ
await axios.put(presignedUrl, file, {
headers: {
"Content-Type": fileType,
},
});
return s3ObjectUrl;
} catch (error) {
console.error("π Image upload failed: ", error);
throw new Error("Image upload failed");
}
};
export default uploadImageToS3;
// μ¬μ©μκ° μ΄λ―Έμ§ μ νμ μ€ν
// hooks/useHandleImageChange.tsx
import { useState, useRef } from "react";
import { toast } from "react-toastify";
import uploadImageToS3 from "utils/uploadImageToS3";
const useHandleImageChange = (type: string) => {
const imgInputRef = useRef<HTMLInputElement | null>(null);
const [preview, setPreview] = useState<string>("");
const [hasFile, setHasFile] = useState<boolean>(false);
const handleImageChange = async () => {
const files = imgInputRef.current?.files;
if (!files || files.length === 0) {
toast.error("μ΄λ―Έμ§κ° μ νλμ§ μμμ΅λλ€.");
return;
}
const file = files[0];
try {
// ploadImageToS3λ₯Ό νΈμΆνμ¬ μ΄λ―Έμ§λ₯Ό μ
λ‘λ ν 미리보기 URL μ
λ°μ΄νΈ
const uploadedImageUrl = await uploadImageToS3(file, type);
setPreview(uploadedImageUrl);
setHasFile(true);
} catch (error) {
toast.error("μ΄λ―Έμ§ μ
λ‘λ μ€ μ€λ₯κ° λ°μνμ΅λλ€.");
}
};
return {
imgInputRef,
preview,
hasFile,
handleImageChange,
};
};
export default useHandleImageChange;
const { imgInputRef, preview, hasFile, handleImageChange } =
useHandleImageChange("product");
<S.UploadImgBox onClick={handleImgInputClick}>
{hasFile ? (
<>
<S.UploadedImg src={preview} alt="미리보기" />
</>
) : (
<>
<S.UploadIcon />
<S.UploadText>μ΄λ―Έμ§ λ±λ‘</S.UploadText>
<S.Essential>νμ λ±λ‘</S.Essential>
</>
)}
<S.ImgUpload
type="file"
accept="image/jpg, image/jpeg"
multiple
ref={imgInputRef}
onChange={handleImageChange}
/>
</S.UploadImgBox>
{hasFile && <S.EditImgBtn>μ¬μ§ μμ νκΈ°</S.EditImgBtn>}
μ¬μ©μκ° μ΄λ―Έμ§λ₯Ό μ ννλ©΄ handleImageChange
κ° μ€ν !
μ΄λ―Έμ§ μ
λ‘λκ° μ±κ³΅νλ©΄ preview μνκ° μ
λ°μ΄νΈ λκ³ , 미리보기 μ΄λ―Έμ§κ° νμλλ€.
μ΄λ―Έμ§κ° μμΌλ©΄ 'μ¬μ§ μμ νκΈ°' λ²νΌμ΄ λνλλ€.