|
1 | 1 | import checkerImg from '@shared/assets/icons/common/checker.png'; |
| 2 | +import { useModal, useToast } from '@shared/hooks'; |
| 3 | +import { MOCK_SELL_CONFIRM_DATA } from '@shared/mocks/data'; |
2 | 4 | import { Button } from '@shared/ui/Button/Button'; |
3 | 5 | import { Modal } from '@shared/ui/Modal/Modal'; |
4 | 6 | import { useEffect, useState } from 'react'; |
5 | 7 | import { useLocation, useNavigate } from 'react-router'; |
6 | 8 | import type { SellState } from '@shared/types/sell'; |
7 | 9 |
|
| 10 | +const CONDITION_LABELS = { |
| 11 | + productCondition: { used: '개봉-중고', unopened: '미개봉-새상품' }, |
| 12 | + scratchCondition: { clean: '스크래치 없음', scratch: '스크래치 있음' }, |
| 13 | + screenCondition: { clean: '화면 깨짐 없음', broken: '화면 깨짐' }, |
| 14 | + batteryCondition: { |
| 15 | + '80plus': '배터리 성능 80% 이상', |
| 16 | + '80minus': '배터리 성능 80% 미만', |
| 17 | + '50minus': '배터리 성능 50% 미만', |
| 18 | + }, |
| 19 | +} as const; |
| 20 | + |
8 | 21 | type SellConfirmState = SellState; |
9 | 22 |
|
10 | 23 | export default function SellConfirmPage() { |
11 | 24 | const location = useLocation(); |
12 | 25 | const navigate = useNavigate(); |
| 26 | + const { showToast } = useToast(); |
13 | 27 | const [imageError, setImageError] = useState(false); |
14 | | - const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); |
15 | | - const state = (location.state ?? {}) as SellConfirmState; |
| 28 | + const deleteModal = useModal(); |
| 29 | + const state = (location.state ?? MOCK_SELL_CONFIRM_DATA) as SellConfirmState; |
| 30 | + |
| 31 | + const displayPrice = `${new Intl.NumberFormat('ko-KR').format(Number(state.price))}원`; |
| 32 | + const showImage = Boolean(state.imageUrl) && !imageError; |
| 33 | + |
| 34 | + const productInfo = [ |
| 35 | + { label: '제조사', value: state.manufacturer }, |
| 36 | + { label: '모델명', value: state.modelName }, |
| 37 | + { label: '색상', value: state.colorName }, |
| 38 | + ]; |
| 39 | + |
| 40 | + const conditionTags = [ |
| 41 | + CONDITION_LABELS.productCondition[state.productCondition as keyof typeof CONDITION_LABELS.productCondition], |
| 42 | + CONDITION_LABELS.scratchCondition[state.scratchCondition as keyof typeof CONDITION_LABELS.scratchCondition], |
| 43 | + CONDITION_LABELS.screenCondition[state.screenCondition as keyof typeof CONDITION_LABELS.screenCondition], |
| 44 | + CONDITION_LABELS.batteryCondition[state.batteryCondition as keyof typeof CONDITION_LABELS.batteryCondition], |
| 45 | + ]; |
16 | 46 |
|
17 | | - const displayTitle = state.title ?? '아이폰 14 맥스'; |
18 | | - const displayPrice = state.price |
19 | | - ? `${new Intl.NumberFormat('ko-KR').format(Number(state.price || 0))}원` |
20 | | - : '580,000원'; |
21 | | - const displayManufacturer = state.manufacturer ?? '애플'; |
22 | | - const displayModelName = state.modelName ?? '아이폰 14 MAX'; |
23 | | - const displayColor = state.colorName ?? '실버'; |
24 | | - const displayDescription = |
25 | | - state.description ?? |
26 | | - `쓴지는 1년정도 되었습니다. |
27 | | -잔기스는 살짝 있습니다. |
28 | | -택배거래 희망합니다. |
29 | | -케이스랑 충전기 줄 드립니다!`; |
30 | | - const displayImageUrl = state.imageUrl ?? null; |
31 | | - const showImage = Boolean(displayImageUrl) && !imageError; |
32 | | - const productCondition = state.productCondition ?? 'used'; |
33 | | - const scratchCondition = state.scratchCondition ?? 'scratch'; |
34 | | - const screenCondition = state.screenCondition ?? 'clean'; |
35 | | - const batteryCondition = state.batteryCondition ?? '80plus'; |
36 | | - const displayProductCondition = productCondition === 'used' ? '개봉-중고' : '미개봉-새상품'; |
37 | | - const displayScratchCondition = scratchCondition === 'clean' ? '스크래치 없음' : '스크래치 있음'; |
38 | | - const displayScreenCondition = screenCondition === 'clean' ? '화면 깨짐 없음' : '화면 깨짐'; |
39 | | - const displayBatteryCondition = |
40 | | - batteryCondition === '80minus' |
41 | | - ? '배터리 성능 80% 미만' |
42 | | - : batteryCondition === '50minus' |
43 | | - ? '배터리 성능 50% 미만' |
44 | | - : '배터리 성능 80% 이상'; |
| 47 | + const actionButtons = [ |
| 48 | + { label: '수정', variant: 'outline' as const, onClick: () => navigate('/sell', { state }) }, |
| 49 | + { label: '삭제', variant: 'fill' as const, onClick: deleteModal.open }, |
| 50 | + ]; |
45 | 51 |
|
46 | 52 | useEffect(() => { |
47 | 53 | setImageError(false); |
48 | | - }, [displayImageUrl]); |
| 54 | + }, [state.imageUrl]); |
49 | 55 |
|
50 | 56 | return ( |
51 | 57 | <div className="w-full bg-white"> |
52 | 58 | <div className="mx-auto flex min-h-screen w-full max-w-[1440px] flex-col items-center bg-white px-4"> |
53 | | - <main className="mt-[90px] flex items-start gap-[var(--spacing-l)]"> |
| 59 | + <main className="gap-l mt-[90px] flex items-start"> |
54 | 60 | {showImage ? ( |
55 | 61 | <img |
56 | | - src={displayImageUrl ?? ''} |
| 62 | + src={state.imageUrl ?? ''} |
57 | 63 | alt="업로드된 이미지" |
58 | | - className="h-[568px] w-[590px] rounded-[var(--radius-s)] object-cover" |
| 64 | + className="h-[568px] w-[590px] rounded-s object-cover" |
59 | 65 | onError={() => setImageError(true)} |
60 | 66 | /> |
61 | 67 | ) : ( |
62 | 68 | <div |
63 | | - className="h-[568px] w-[590px] rounded-[var(--radius-s)] bg-[var(--color-gray-100)] bg-cover bg-center" |
| 69 | + className="h-[568px] w-[590px] rounded-s bg-gray-100 bg-cover bg-center" |
64 | 70 | style={{ backgroundImage: `url(${checkerImg})` }} |
65 | 71 | aria-label="확인 이미지" |
66 | 72 | /> |
67 | 73 | )} |
68 | 74 | <div className="flex h-[568px] w-[590px] flex-col items-start justify-between"> |
69 | 75 | <div className="flex w-full flex-col items-start gap-[21px]"> |
70 | | - <h1 className="typo-title-2 line-clamp-2 w-full text-[var(--color-gray-900)]">{displayTitle}</h1> |
71 | | - <p className="typo-title-1 text-[var(--color-gray-900)]">{displayPrice}</p> |
| 76 | + <h1 className="typo-title-2 line-clamp-2 w-full text-gray-900">{state.title}</h1> |
| 77 | + <p className="typo-title-1 text-gray-900">{displayPrice}</p> |
72 | 78 |
|
73 | | - <div className="h-px w-full bg-[var(--color-gray-200)]" /> |
| 79 | + <div className="h-px w-full bg-gray-200" /> |
74 | 80 |
|
75 | | - <div className="flex w-full flex-col items-start gap-[var(--spacing-xxs)]"> |
76 | | - <div className="flex items-center gap-[11px]"> |
77 | | - <span className="typo-caption-1 text-[var(--color-gray-500)]">제조사</span> |
78 | | - <span className="typo-body-1 text-[var(--color-gray-900)]">{displayManufacturer}</span> |
79 | | - </div> |
80 | | - <div className="flex items-center gap-[11px]"> |
81 | | - <span className="typo-caption-1 text-[var(--color-gray-500)]">모델명</span> |
82 | | - <span className="typo-body-1 text-[var(--color-gray-900)]">{displayModelName}</span> |
83 | | - </div> |
84 | | - <div className="flex items-center gap-[11px]"> |
85 | | - <span className="typo-caption-1 text-[var(--color-gray-500)]">색상</span> |
86 | | - <span className="typo-body-1 text-[var(--color-gray-900)]">{displayColor}</span> |
87 | | - </div> |
| 81 | + <div className="gap-xxs flex w-full flex-col items-start"> |
| 82 | + {productInfo.map(({ label, value }) => ( |
| 83 | + <div key={label} className="flex items-center gap-[11px]"> |
| 84 | + <span className="typo-caption-1 text-gray-500">{label}</span> |
| 85 | + <span className="typo-body-1 text-gray-900">{value}</span> |
| 86 | + </div> |
| 87 | + ))} |
88 | 88 | </div> |
89 | 89 |
|
90 | 90 | <div className="flex w-full items-center gap-[3px] text-center"> |
91 | | - <span className="typo-caption-1 whitespace-nowrap text-[var(--color-gray-300)]"> |
92 | | - {displayProductCondition} |
93 | | - </span> |
94 | | - <span className="typo-caption-1 text-[var(--color-gray-300)]">•</span> |
95 | | - <span className="typo-caption-1 whitespace-nowrap text-[var(--color-gray-300)]"> |
96 | | - {displayScratchCondition} |
97 | | - </span> |
98 | | - <span className="typo-caption-1 text-[var(--color-gray-300)]">•</span> |
99 | | - <span className="typo-caption-1 whitespace-nowrap text-[var(--color-gray-300)]"> |
100 | | - {displayScreenCondition} |
101 | | - </span> |
102 | | - <span className="typo-caption-1 text-[var(--color-gray-300)]">•</span> |
103 | | - <span className="typo-caption-1 whitespace-nowrap text-[var(--color-gray-300)]"> |
104 | | - {displayBatteryCondition} |
105 | | - </span> |
| 91 | + {conditionTags.map((tag, index) => ( |
| 92 | + <span key={tag} className="typo-caption-1 flex items-center gap-[3px] text-gray-300"> |
| 93 | + {index > 0 && '•'} |
| 94 | + <span className="whitespace-nowrap">{tag}</span> |
| 95 | + </span> |
| 96 | + ))} |
106 | 97 | </div> |
107 | 98 |
|
108 | | - <div className="h-px w-[584px] bg-[var(--color-gray-200)]" /> |
| 99 | + <div className="h-px w-[584px] bg-gray-200" /> |
109 | 100 |
|
110 | | - <p className="typo-body-2 line-clamp-6 w-full text-ellipsis whitespace-pre-line text-[var(--color-gray-900)]"> |
111 | | - {displayDescription} |
| 101 | + <p className="typo-body-2 line-clamp-6 w-full text-ellipsis whitespace-pre-line text-gray-900"> |
| 102 | + {state.description} |
112 | 103 | </p> |
113 | 104 | </div> |
114 | 105 |
|
115 | 106 | <div className="flex h-[44px] w-full items-center gap-[34px]"> |
116 | | - <Button |
117 | | - variant="outline" |
118 | | - size="full" |
119 | | - className="h-[44px] px-[var(--padding-xl)] py-[var(--padding-m)] text-[var(--color-gray-900)]" |
120 | | - onClick={() => navigate('/sell', { state })} |
121 | | - > |
122 | | - 수정 |
123 | | - </Button> |
124 | | - <Button |
125 | | - variant="fill" |
126 | | - size="full" |
127 | | - className="h-[44px] px-[var(--padding-xl)] py-[var(--padding-m)]" |
128 | | - onClick={() => setIsDeleteModalOpen(true)} |
129 | | - > |
130 | | - 삭제 |
131 | | - </Button> |
| 107 | + {actionButtons.map(({ label, variant, onClick }) => ( |
| 108 | + <Button key={label} variant={variant} size="full" className="px-xl py-m h-[44px]" onClick={onClick}> |
| 109 | + {label} |
| 110 | + </Button> |
| 111 | + ))} |
132 | 112 | </div> |
133 | 113 | </div> |
134 | 114 | </main> |
135 | | - {isDeleteModalOpen && ( |
| 115 | + {deleteModal.isOpen && ( |
136 | 116 | <Modal |
137 | 117 | title="삭제하시겠어요?" |
138 | 118 | subtitle="삭제하면 복구할 수 없어요." |
139 | | - onCancel={() => setIsDeleteModalOpen(false)} |
| 119 | + onCancel={deleteModal.close} |
140 | 120 | onConfirm={() => { |
141 | | - setIsDeleteModalOpen(false); |
142 | | - navigate('/', { state: { deleted: true } }); |
| 121 | + deleteModal.close(); |
| 122 | + showToast('삭제되었습니다', 'success'); |
| 123 | + navigate('/'); |
143 | 124 | }} |
144 | 125 | cancelText="취소" |
145 | 126 | confirmText="삭제" |
|
0 commit comments