diff --git a/next.config.ts b/next.config.ts index 12baefaf..763cbb32 100644 --- a/next.config.ts +++ b/next.config.ts @@ -5,7 +5,8 @@ const nextConfig: NextConfig = { remotePatterns: [ { protocol: 'https', - hostname: 'storage.googleapis.com', + hostname: 'api.wishpool.store', + pathname: '/image/**', }, ], }, diff --git a/package.json b/package.json index f909fc2a..ce76d2a4 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "private": true, "packageManager": "pnpm@10.11.0", "scripts": { - "dev": "next dev", + "dev": "next dev --webpack", "build": "next build --webpack", "start": "next start", "lint": "eslint .", diff --git a/src/api/domain/join/types.ts b/src/api/domain/join/types.ts index 577ab7ae..05443040 100644 --- a/src/api/domain/join/types.ts +++ b/src/api/domain/join/types.ts @@ -1,6 +1,7 @@ export type GiftItemDto = { itemUrl: string; itemName: string; + imageUrl?: string; }; export type WishpoolJoinRequest = { diff --git a/src/app/pick/list/page.tsx b/src/app/pick/list/page.tsx index f4e81116..cd45f43c 100644 --- a/src/app/pick/list/page.tsx +++ b/src/app/pick/list/page.tsx @@ -20,12 +20,13 @@ const ListPage = () => { {items.length}개
- {items.map(({ giftId, itemName, itemUrl }) => ( + {items.map(({ giftId, itemName, itemUrl, imageUrl }) => ( ))}
diff --git a/src/app/pick/preview/page.tsx b/src/app/pick/preview/page.tsx index f98bcae2..e3e7b71f 100644 --- a/src/app/pick/preview/page.tsx +++ b/src/app/pick/preview/page.tsx @@ -48,13 +48,14 @@ const PreviewPage = () => {

최종 점검

최종 선택한 선물

- {pickedItems.map(({ giftId, itemName, itemUrl }) => ( + {pickedItems.map(({ giftId, itemName, itemUrl, imageUrl }) => ( ))}
diff --git a/src/app/pick/select/page.tsx b/src/app/pick/select/page.tsx index d2a0897f..9f00720c 100644 --- a/src/app/pick/select/page.tsx +++ b/src/app/pick/select/page.tsx @@ -52,7 +52,7 @@ const SelectPage = () => { className="no-scrollbar bg-blue-5 flex snap-x snap-mandatory gap-[2.4rem] overflow-x-auto overflow-y-hidden px-[2rem] pt-[7rem] pb-[5rem]" >
- {items.map(({ giftId, itemName, itemUrl }, i) => ( + {items.map(({ giftId, itemName, itemUrl, imageUrl }, i) => ( { itemName={itemName} itemUrl={itemUrl} onRemove={handleRemove} + imageUrl={imageUrl} /> ))}
diff --git a/src/app/wishpool/(viewer)/[id]/date/page.tsx b/src/app/wishpool/(viewer)/[id]/date/page.tsx index 7f1a4e5c..ab9de915 100644 --- a/src/app/wishpool/(viewer)/[id]/date/page.tsx +++ b/src/app/wishpool/(viewer)/[id]/date/page.tsx @@ -36,9 +36,9 @@ const DatePage = () => { pickDate: endDate, }); - const chosenUrl = res.chosenUrl; - sessionStorage.setItem('chosenUrl', chosenUrl); - router.push(PATH.WISHPOOL_INVITE(wishpoolId)); + router.push( + PATH.WISHPOOL_INVITE(wishpoolId) + `?chosenUrl=${res.chosenUrl}`, + ); } catch { alert('선물 고르기 마감일 설정에 실패했습니다.'); } diff --git a/src/app/wishpool/(viewer)/[id]/final/page.tsx b/src/app/wishpool/(viewer)/[id]/final/page.tsx index 06b93875..1a70f726 100644 --- a/src/app/wishpool/(viewer)/[id]/final/page.tsx +++ b/src/app/wishpool/(viewer)/[id]/final/page.tsx @@ -49,6 +49,7 @@ const FinalPage = () => { giftId={giftId} itemName={giftName} itemUrl={giftImage} + imageUrl={giftImage} /> ), )} diff --git a/src/app/wishpool/(viewer)/[id]/gifts/page.tsx b/src/app/wishpool/(viewer)/[id]/gifts/page.tsx index 9aed45bf..cf699dc7 100644 --- a/src/app/wishpool/(viewer)/[id]/gifts/page.tsx +++ b/src/app/wishpool/(viewer)/[id]/gifts/page.tsx @@ -30,6 +30,7 @@ const GiftPage = () => { guest={gift.guest || ''} itemName={gift.itemName} itemUrl={gift.itemUrl} + imageUrl={gift.imageUrl} /> ))}
diff --git a/src/app/wishpool/(viewer)/[id]/invite/page.tsx b/src/app/wishpool/(viewer)/[id]/invite/page.tsx index f321aef1..1fc4bbc6 100644 --- a/src/app/wishpool/(viewer)/[id]/invite/page.tsx +++ b/src/app/wishpool/(viewer)/[id]/invite/page.tsx @@ -7,6 +7,7 @@ import invite from '@/assets/images/invite.png'; import WishpoolShareSection from '@/components/common/WishpoolShareBox'; import { PATH } from '@/constants/common/path'; import { useGetWishpoolId } from '@/hooks/common/useGetWishpoolId'; +import { useGetChosenUrl } from '@/hooks/pick/useGetChosenUrl'; import { ShareSectionType } from '@/types/common/ShareSectionType'; const getOrigin = () => { @@ -17,7 +18,7 @@ const getOrigin = () => { const InvitePage = () => { const content = 'invite' as ShareSectionType; - const chosenUrl = sessionStorage.getItem('chosenUrl') || ''; + const chosenUrl = useGetChosenUrl(); const origin = getOrigin(); const inviteUrl = `${origin}${PATH.PICK_INVITE}?chosenUrl=${chosenUrl}`; diff --git a/src/app/wishpool/join/[id]/add/page.tsx b/src/app/wishpool/join/[id]/add/page.tsx index c22814c1..33b169df 100644 --- a/src/app/wishpool/join/[id]/add/page.tsx +++ b/src/app/wishpool/join/[id]/add/page.tsx @@ -99,8 +99,10 @@ const AddPage = () => { maxLength={20} valueItemName={gift.itemName} valueLink={gift.itemUrl} + valueImageUrl={gift.imageUrl} onChangeItemName={(v) => handleGiftChange(idx, 'itemName', v)} onChangeLink={(v) => handleGiftChange(idx, 'itemUrl', v)} + onChangeImage={(v) => handleGiftChange(idx, 'imageUrl', v)} onRemove={() => removeGift(idx)} />
diff --git a/src/app/wishpool/join/[id]/preview/page.tsx b/src/app/wishpool/join/[id]/preview/page.tsx index 94862813..e90a8363 100644 --- a/src/app/wishpool/join/[id]/preview/page.tsx +++ b/src/app/wishpool/join/[id]/preview/page.tsx @@ -9,8 +9,8 @@ import GiftCardImage from '@/assets/images/gift-card.png'; import Button from '@/components/common/Button'; import UserTag from '@/components/common/UserTag'; import { PATH } from '@/constants/common/path'; +import { WISHPOOL_IMAGE_BASE_URL } from '@/constants/wishpool/image'; import { useGetWishpoolId } from '@/hooks/common/useGetWishpoolId'; - const PreviewPage = () => { const router = useRouter(); const wishpoolId = useGetWishpoolId(); @@ -63,12 +63,23 @@ const PreviewPage = () => { key={`${gift.itemName}-${idx}`} className="flex flex-col items-center justify-center" > - 선물 카드 이미지 + {gift.imageUrl ? ( +
+ 등록 선물 이미지 +
+ ) : ( + 기본 선물 이미지 + )} {gift.itemName} diff --git a/src/assets/icons/iconMap.ts b/src/assets/icons/iconMap.ts index d40794ca..66747d9c 100644 --- a/src/assets/icons/iconMap.ts +++ b/src/assets/icons/iconMap.ts @@ -8,6 +8,7 @@ import CalendarIcon from '@/assets/icons/svg/icon_calender.svg'; import CameraIcon from '@/assets/icons/svg/icon_camera.svg'; import CancelIcon from '@/assets/icons/svg/icon_cancel.svg'; import CheerIcon from '@/assets/icons/svg/icon_cheer.svg'; +import DeleteIcon from '@/assets/icons/svg/icon_delete.svg'; import DotIcon from '@/assets/icons/svg/icon_dot.svg'; import DownIcon from '@/assets/icons/svg/icon_down.svg'; import GiftIcon from '@/assets/icons/svg/icon_gift.svg'; @@ -54,6 +55,7 @@ export const iconMap = { next: NextIcon, ribbon: RibbonIcon, loading: LoadingIcon, + delete: DeleteIcon, } as const; export type IconName = keyof typeof iconMap; diff --git a/src/assets/icons/svg/icon_delete.svg b/src/assets/icons/svg/icon_delete.svg new file mode 100644 index 00000000..069221c4 --- /dev/null +++ b/src/assets/icons/svg/icon_delete.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/common/Form/GiftField.tsx b/src/components/common/Form/GiftField.tsx index 06a71c7f..5c5de9bf 100644 --- a/src/components/common/Form/GiftField.tsx +++ b/src/components/common/Form/GiftField.tsx @@ -1,5 +1,10 @@ -import { GiftItemDto } from '@/api/domain/join/types'; +import Image from 'next/image'; +import { useEffect, useRef } from 'react'; + import Icon from '@/components/common/Icon'; +import { WISHPOOL_IMAGE_BASE_URL } from '@/constants/wishpool/image'; +import { useImageUpload } from '@/hooks/wishpool/useImageUpload'; +import { patchGift } from '@/utils/wishpool/viewer/manageGifts'; type GiftFieldProps = { index: number; @@ -8,35 +13,12 @@ type GiftFieldProps = { maxLength?: number; valueItemName: string; valueLink: string; + valueImageUrl?: string | null; onChangeItemName: (v: string) => void; onChangeLink: (v: string) => void; + onChangeImage: (v: string) => void; onRemove: () => void; }; -const STORAGE_KEY = 'wishpool_gifts'; - -function readGifts(): GiftItemDto[] { - try { - const raw = sessionStorage.getItem(STORAGE_KEY); - if (!raw) return []; - const parsed = JSON.parse(raw); - return Array.isArray(parsed) ? parsed : []; - } catch { - return []; - } -} - -function writeGifts(next: GiftItemDto[]) { - sessionStorage.setItem(STORAGE_KEY, JSON.stringify(next)); -} - -function patchGift(index: number, patch: Partial) { - const current = readGifts(); - - const base: GiftItemDto = current[index] ?? { itemName: '', itemUrl: '' }; - const next = [...current]; - next[index] = { ...base, ...patch }; - writeGifts(next); -} const GiftField = ({ index, @@ -45,10 +27,16 @@ const GiftField = ({ maxLength, valueItemName, valueLink, + valueImageUrl, onChangeItemName, onChangeLink, + onChangeImage, onRemove, }: GiftFieldProps) => { + const fileInputRef = useRef(null); + const { isUploading, preview, imageKey, handleImageChange, reset } = + useImageUpload(); + const handleNameChange = (e: React.ChangeEvent) => { const newItemName = e.target.value; onChangeItemName(newItemName); @@ -61,6 +49,23 @@ const GiftField = ({ patchGift(index, { itemUrl: newUrlName }); }; + useEffect(() => { + if (!imageKey) return; + onChangeImage(imageKey); + patchGift(index, { imageUrl: imageKey }); + }, [imageKey]); + + const handleDeleteImage = () => { + reset(); + onChangeImage(''); + patchGift(index, { imageUrl: '' }); + }; + + const imageSrc = valueImageUrl + ? `${WISHPOOL_IMAGE_BASE_URL}/${valueImageUrl}` + : null; + + const displayImageSrc = preview ?? imageSrc; return ( <>
@@ -100,6 +105,59 @@ const GiftField = ({ className="body1 flex h-[5.6rem] w-full rounded-[12px] border border-gray-400 px-[1.6rem] py-[1.6rem] placeholder:text-gray-400 focus:border-gray-400 focus:outline-none" />
+ + +
+ {displayImageSrc ? ( +
+ 선물 이미지 미리보기 + + + +
+ ) : ( +
+
+ +

선물 사진 추가

+
+

+ *사진을 추가하지 않을 때는 기본 이미지로 보여줄게요. +

+
+ )} +
); }; diff --git a/src/components/pick/list/GiftCard.tsx b/src/components/pick/list/GiftCard.tsx index f1baccdf..6a7c2f65 100644 --- a/src/components/pick/list/GiftCard.tsx +++ b/src/components/pick/list/GiftCard.tsx @@ -1,5 +1,6 @@ import Image from 'next/image'; +import { useGetWishpoolImage } from '@/api/domain/detail/hooks'; import GiftCardImage from '@/assets/images/gift-card.png'; import type { GiftCardType } from '@/types/common/giftCardType'; @@ -13,16 +14,19 @@ const GiftCard = ({ size = 'small', giftId, itemName, - //itemUrl, + imageUrl, }: GiftCardProps) => { const isSmall = size === 'small'; + const { data: imageData } = useGetWishpoolImage(imageUrl); + const finalSrc = imageData && imageData.key ? imageData.key : GiftCardImage; + return (
{`선물 {renderSpacer && ( @@ -73,7 +77,7 @@ export default function CarouselCard({ ].join(' ')} > 선물 카드 이미지 { transition={{ duration, ease: 'linear', repeat: Infinity }} aria-hidden > - {items.map(({ giftId, itemName, itemUrl }) => ( + {items.map(({ giftId, itemName, itemUrl, imageUrl }) => ( ))} diff --git a/src/components/wishpool/viewer/list/ItemCard.tsx b/src/components/wishpool/viewer/list/ItemCard.tsx index 19d8dbbf..94fcdb0c 100644 --- a/src/components/wishpool/viewer/list/ItemCard.tsx +++ b/src/components/wishpool/viewer/list/ItemCard.tsx @@ -1,6 +1,7 @@ import Image from 'next/image'; import Link from 'next/link'; +import { useGetWishpoolImage } from '@/api/domain/detail/hooks'; import GiftCardImage from '@/assets/images/gift-card.png'; import UserTag from '@/components/common/UserTag'; @@ -8,14 +9,18 @@ type ItemCardProps = { guest: string; itemName: string; itemUrl: string; + imageUrl: string; }; -const ItemCard = ({ guest, itemName, itemUrl }: ItemCardProps) => { +const ItemCard = ({ guest, itemName, itemUrl, imageUrl }: ItemCardProps) => { + const { data: imageData } = useGetWishpoolImage(imageUrl); + const finalSrc = imageData && imageData.key ? imageData.key : GiftCardImage; + return ( <> 프로필 이미지 { } }; + const reset = () => { + if (preview?.startsWith('blob:')) URL.revokeObjectURL(preview); + setPreview(null); + setImageKey(null); + setError(null); + }; + return { preview, error, imageKey, handleImageChange, isUploading: uploadMutation.isPending, + reset, }; }; diff --git a/src/types/common/giftCardType.ts b/src/types/common/giftCardType.ts index 8c00338c..7691adb3 100644 --- a/src/types/common/giftCardType.ts +++ b/src/types/common/giftCardType.ts @@ -3,4 +3,5 @@ export type GiftCardType = { itemUrl: string; itemName: string; giftId: number; + imageUrl: string; }; diff --git a/src/utils/wishpool/viewer/manageGifts.ts b/src/utils/wishpool/viewer/manageGifts.ts new file mode 100644 index 00000000..41083c64 --- /dev/null +++ b/src/utils/wishpool/viewer/manageGifts.ts @@ -0,0 +1,30 @@ +import { GiftItemDto } from '@/api/domain/join/types'; +const STORAGE_KEY = 'wishpool_gifts'; + +export function readGifts(): GiftItemDto[] { + try { + const raw = sessionStorage.getItem(STORAGE_KEY); + if (!raw) return []; + const parsed = JSON.parse(raw); + return Array.isArray(parsed) ? parsed : []; + } catch { + return []; + } +} + +export function writeGifts(next: GiftItemDto[]) { + sessionStorage.setItem(STORAGE_KEY, JSON.stringify(next)); +} + +export function patchGift(index: number, patch: Partial) { + const current = readGifts(); + + const base: GiftItemDto = current[index] ?? { + itemName: '', + itemUrl: '', + imageUrl: '', + }; + const next = [...current]; + next[index] = { ...base, ...patch }; + writeGifts(next); +}