diff --git a/.gitignore b/.gitignore index a547bf3..1cac559 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ dist-ssr *.njsproj *.sln *.sw? +.env \ No newline at end of file diff --git a/package.json b/package.json index 17b8765..ebe6217 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,6 @@ "stylelint-order": "^6.0.4", "typescript": "~5.6.2", "vite": "^5.4.10", - "vite-tsconfig-paths": "^5.1.2" + "vite-tsconfig-paths": "^5.1.3" } } diff --git a/src/App.tsx b/src/App.tsx index f0e9844..f1ab9ec 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,13 +1,19 @@ import { Global, ThemeProvider } from '@emotion/react'; + import Route from '@route/Route'; import GLOBALSTYLE from '@styles/global'; import Theme from '@styles/theme'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; + +const queryClient = new QueryClient(); const App = () => ( - - - - + + + + + + ); export default App; diff --git a/src/apis/instance.ts b/src/apis/instance.ts new file mode 100644 index 0000000..3527281 --- /dev/null +++ b/src/apis/instance.ts @@ -0,0 +1,10 @@ +import axios from 'axios'; + +const instance = axios.create({ + baseURL: import.meta.env.VITE_APP_BASE_URL, + headers: { + 'Content-Type': 'application/json', + }, +}); + +export default instance; diff --git a/src/apis/productPage/product/getProduct.ts b/src/apis/productPage/product/getProduct.ts new file mode 100644 index 0000000..5c505d5 --- /dev/null +++ b/src/apis/productPage/product/getProduct.ts @@ -0,0 +1,29 @@ +import instance from '@apis/instance'; + +interface ProductResponse { + productId: number; + productImage: string; + detail: string; + priceOriginal: number; + percent: number; + priceDiscount: number; + isCoupon: boolean; + categoryName: string; + reviewCount: number; + rating: number; +} + +interface RelatedProductsResponse { + success: boolean; + data: { + products: ProductResponse[]; + }; + error: string | null; +} + +const fetchRelatedProducts = async (productId: number): Promise => { + const response = await instance.get(`/api/products/${productId}/related`); + return response.data.data.products; +}; + +export default fetchRelatedProducts; \ No newline at end of file diff --git a/src/apis/productPage/product/productQueries.ts b/src/apis/productPage/product/productQueries.ts new file mode 100644 index 0000000..b00ec5e --- /dev/null +++ b/src/apis/productPage/product/productQueries.ts @@ -0,0 +1,12 @@ +import { useQuery } from '@tanstack/react-query'; +import fetchRelatedProducts from './getProduct'; + +const useRelatedProducts = (productId: number) => { + return useQuery({ + queryKey: ['relatedProducts', productId], + queryFn: () => fetchRelatedProducts(productId), + initialData: [], + }); +}; + +export default useRelatedProducts; diff --git a/src/assets/icons/btn_left_defalut.svg b/src/assets/icons/btn_left_defalut.svg new file mode 100644 index 0000000..0179379 --- /dev/null +++ b/src/assets/icons/btn_left_defalut.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/btn_right_default.svg b/src/assets/icons/btn_right_default.svg new file mode 100644 index 0000000..dc9dbaa --- /dev/null +++ b/src/assets/icons/btn_right_default.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/ic_deliverstatus_1_complete_56.svg b/src/assets/icons/card_deliverstatus_1_complete.svg similarity index 88% rename from src/assets/icons/ic_deliverstatus_1_complete_56.svg rename to src/assets/icons/card_deliverstatus_1_complete.svg index 733a510..b422f1d 100644 --- a/src/assets/icons/ic_deliverstatus_1_complete_56.svg +++ b/src/assets/icons/card_deliverstatus_1_complete.svg @@ -1,4 +1,6 @@ + + diff --git a/src/assets/icons/card_deliverstatus_1_final.svg b/src/assets/icons/card_deliverstatus_1_final.svg new file mode 100644 index 0000000..c79975d --- /dev/null +++ b/src/assets/icons/card_deliverstatus_1_final.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/icons/ic_deliverstatus_2_complete_56.svg b/src/assets/icons/card_deliverstatus_2_complete.svg similarity index 68% rename from src/assets/icons/ic_deliverstatus_2_complete_56.svg rename to src/assets/icons/card_deliverstatus_2_complete.svg index 20e9ff7..c158ff0 100644 --- a/src/assets/icons/ic_deliverstatus_2_complete_56.svg +++ b/src/assets/icons/card_deliverstatus_2_complete.svg @@ -1,4 +1,6 @@ + + diff --git a/src/assets/icons/card_deliverstatus_2_final.svg b/src/assets/icons/card_deliverstatus_2_final.svg new file mode 100644 index 0000000..90f0b2e --- /dev/null +++ b/src/assets/icons/card_deliverstatus_2_final.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/icons/ic_deliverstatus_3_complete_56.svg b/src/assets/icons/card_deliverstatus_3_complete.svg similarity index 90% rename from src/assets/icons/ic_deliverstatus_3_complete_56.svg rename to src/assets/icons/card_deliverstatus_3_complete.svg index 6556f04..95f10b5 100644 --- a/src/assets/icons/ic_deliverstatus_3_complete_56.svg +++ b/src/assets/icons/card_deliverstatus_3_complete.svg @@ -1,8 +1,10 @@ - + - + + + diff --git a/src/assets/icons/card_deliverstatus_3_final.svg b/src/assets/icons/card_deliverstatus_3_final.svg new file mode 100644 index 0000000..2f80457 --- /dev/null +++ b/src/assets/icons/card_deliverstatus_3_final.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/icons/ic_deliverstatus_3_complete_56-1.svg b/src/assets/icons/card_deliverstatus_4_complete.svg similarity index 93% rename from src/assets/icons/ic_deliverstatus_3_complete_56-1.svg rename to src/assets/icons/card_deliverstatus_4_complete.svg index 9115f5f..4f89a07 100644 --- a/src/assets/icons/ic_deliverstatus_3_complete_56-1.svg +++ b/src/assets/icons/card_deliverstatus_4_complete.svg @@ -1,4 +1,6 @@ + + diff --git a/src/assets/icons/card_deliverstatus_4_final.svg b/src/assets/icons/card_deliverstatus_4_final.svg new file mode 100644 index 0000000..6de3ecb --- /dev/null +++ b/src/assets/icons/card_deliverstatus_4_final.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/icons/ic_deliverstatus_5_complete_56.svg b/src/assets/icons/card_deliverstatus_5_complete.svg similarity index 89% rename from src/assets/icons/ic_deliverstatus_5_complete_56.svg rename to src/assets/icons/card_deliverstatus_5_complete.svg index f4a5152..f00e5e0 100644 --- a/src/assets/icons/ic_deliverstatus_5_complete_56.svg +++ b/src/assets/icons/card_deliverstatus_5_complete.svg @@ -1,4 +1,6 @@ + + diff --git a/src/assets/icons/card_deliverstatus_5_final.svg b/src/assets/icons/card_deliverstatus_5_final.svg new file mode 100644 index 0000000..65098cf --- /dev/null +++ b/src/assets/icons/card_deliverstatus_5_final.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/icons/card_deliverstatus_6_complete.svg b/src/assets/icons/card_deliverstatus_6_complete.svg new file mode 100644 index 0000000..efca644 --- /dev/null +++ b/src/assets/icons/card_deliverstatus_6_complete.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/icons/ic_deliverstatus_6_final_56.svg b/src/assets/icons/card_deliverstatus_6_final.svg similarity index 59% rename from src/assets/icons/ic_deliverstatus_6_final_56.svg rename to src/assets/icons/card_deliverstatus_6_final.svg index da362f6..8db434b 100644 --- a/src/assets/icons/ic_deliverstatus_6_final_56.svg +++ b/src/assets/icons/card_deliverstatus_6_final.svg @@ -5,4 +5,6 @@ + + diff --git a/src/assets/icons/ic_arrowbottom_s_white_12.svg b/src/assets/icons/ic_arrowbottom_s_white_12.svg new file mode 100644 index 0000000..19781f1 --- /dev/null +++ b/src/assets/icons/ic_arrowbottom_s_white_12.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/icons/ic_comet_review_star_half_18.svg b/src/assets/icons/ic_comet_review_star_half_18.svg index 2e63791..3c9cfb4 100644 --- a/src/assets/icons/ic_comet_review_star_half_18.svg +++ b/src/assets/icons/ic_comet_review_star_half_18.svg @@ -1,3 +1,11 @@ - + + + + + + + + + diff --git a/src/assets/icons/ic_deliverstatus_1_white_36.svg b/src/assets/icons/ic_deliverstatus_1_white_36.svg deleted file mode 100644 index 56181d1..0000000 --- a/src/assets/icons/ic_deliverstatus_1_white_36.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/assets/icons/ic_deliverstatus_2_white_36.svg b/src/assets/icons/ic_deliverstatus_2_white_36.svg deleted file mode 100644 index 700d04c..0000000 --- a/src/assets/icons/ic_deliverstatus_2_white_36.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/assets/icons/ic_deliverstatus_3_white_36.svg b/src/assets/icons/ic_deliverstatus_3_white_36.svg deleted file mode 100644 index 7ba24ed..0000000 --- a/src/assets/icons/ic_deliverstatus_3_white_36.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/assets/icons/ic_deliverstatus_4_white_36.svg b/src/assets/icons/ic_deliverstatus_4_white_36.svg deleted file mode 100644 index 1db8f9b..0000000 --- a/src/assets/icons/ic_deliverstatus_4_white_36.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/assets/icons/ic_deliverstatus_5_white_36.svg b/src/assets/icons/ic_deliverstatus_5_white_36.svg deleted file mode 100644 index 151e32e..0000000 --- a/src/assets/icons/ic_deliverstatus_5_white_36.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/assets/icons/ic_deliverstatus_6_white_36.svg b/src/assets/icons/ic_deliverstatus_6_white_36.svg deleted file mode 100644 index ea7ca44..0000000 --- a/src/assets/icons/ic_deliverstatus_6_white_36.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/assets/icons/index.ts b/src/assets/icons/index.ts index 4562425..b23b196 100644 --- a/src/assets/icons/index.ts +++ b/src/assets/icons/index.ts @@ -1,3 +1,17 @@ +export { default as BtnLeftDefault } from './btn_left_defalut.svg'; +export { default as BtnRightDefault } from './btn_right_default.svg'; +export { default as CardDelieverStatus1 } from './card_deliverstatus_1_complete.svg'; +export { default as CardDelieverStatus2 } from './card_deliverstatus_2_complete.svg'; +export { default as CardDelieverStatus3 } from './card_deliverstatus_3_complete.svg'; +export { default as CardDelieverStatus4 } from './card_deliverstatus_4_complete.svg'; +export { default as CardDelieverStatus5 } from './card_deliverstatus_5_complete.svg'; +export { default as CardDelieverStatus6 } from './card_deliverstatus_6_complete.svg'; +export { default as CardDelieverFinalStatus1 } from './card_deliverstatus_1_final.svg'; +export { default as CardDelieverFinalStatus2 } from './card_deliverstatus_2_final.svg'; +export { default as CardDelieverFinalStatus3 } from './card_deliverstatus_3_final.svg'; +export { default as CardDelieverFinalStatus4 } from './card_deliverstatus_4_final.svg'; +export { default as CardDelieverFinalStatus5 } from './card_deliverstatus_5_final.svg'; +export { default as CardDelieverFinalStatus6 } from './card_deliverstatus_6_final.svg'; export { default as DividerVerticalBlackSmall } from './divider_ vertical_black_small.svg'; export { default as DividerVerticalGraySmall } from './divider_ vertical_gray_small.svg'; export { default as DividerGray } from './divider_gray.svg'; @@ -48,18 +62,6 @@ export { default as IcCometReviewStarHalfBlack8 } from './ic_comet_review_star_h export { default as IcCountdownGray18 } from './ic_countdown_gray_18.svg'; export { default as IcCountupGray18 } from './ic_countup_gray_18.svg'; export { default as IcCouponBlack12 } from './ic_coupon_black_12.svg'; -export { default as IcDeliverstatus1Complete56 } from './ic_deliverstatus_1_complete_56.svg'; -export { default as IcDeliverstatus1White36 } from './ic_deliverstatus_1_white_36.svg'; -export { default as IcDeliverstatus2Complete56 } from './ic_deliverstatus_2_complete_56.svg'; -export { default as IcDeliverstatus2White36 } from './ic_deliverstatus_2_white_36.svg'; -export { default as IcDeliverstatus3Complete561 } from './ic_deliverstatus_3_complete_56-1.svg'; -export { default as IcDeliverstatus3Complete56 } from './ic_deliverstatus_3_complete_56.svg'; -export { default as IcDeliverstatus3White36 } from './ic_deliverstatus_3_white_36.svg'; -export { default as IcDeliverstatus4White36 } from './ic_deliverstatus_4_white_36.svg'; -export { default as IcDeliverstatus5Complete56 } from './ic_deliverstatus_5_complete_56.svg'; -export { default as IcDeliverstatus5White36 } from './ic_deliverstatus_5_white_36.svg'; -export { default as IcDeliverstatus6Final56 } from './ic_deliverstatus_6_final_56.svg'; -export { default as IcDeliverstatus6White36 } from './ic_deliverstatus_6_white_36.svg'; export { default as IcDeliveryBlack20 } from './ic_delivery_black_20.svg'; export { default as IcDetailedstatusDotDefault } from './ic_detailedstatus_dot_default.svg'; export { default as IcDetailedstatusDotVarient } from './ic_detailedstatus_dot_varient.svg'; @@ -130,3 +132,11 @@ export { default as IcUserWhite24 } from './ic_user_white_24.svg'; export { default as IcWarningBrandYellow16 } from './ic_warning_brand_yellow_16.svg'; export { default as IcWatchBlack16 } from './ic_watch_black_16.svg'; export { default as IcWomanfashionBlack16 } from './ic_womanfashion_black_16.svg'; +export { default as ImgLogotypeL } from '../images/img_logotype_l.svg'; +export { default as ImgFlagKorL } from '../images/img_flag_kor_l.svg'; +export { default as IcArrowbottomSWhite12 } from './ic_arrowbottom_s_white_12.svg'; +export { default as ImgFlagKorS } from '../images/img_flag_kor_s.svg'; +export { default as ImgLine } from '../images/Line 18.svg'; +export { default as ImgVector7192 } from '../images/Vector 7192.svg'; +export { default as ImgProfile30 } from '../images/img_profile_30.svg'; +export { default as ImgGraph } from '@assets/images/img_graph.svg'; diff --git a/src/assets/images/Frame1.png b/src/assets/images/Frame1.png new file mode 100644 index 0000000..5d97cfb Binary files /dev/null and b/src/assets/images/Frame1.png differ diff --git a/src/assets/images/Frame2.png b/src/assets/images/Frame2.png new file mode 100644 index 0000000..cd9f7cc Binary files /dev/null and b/src/assets/images/Frame2.png differ diff --git a/src/assets/images/Frame3.png b/src/assets/images/Frame3.png new file mode 100644 index 0000000..e242e89 Binary files /dev/null and b/src/assets/images/Frame3.png differ diff --git a/src/assets/images/Frame4.png b/src/assets/images/Frame4.png new file mode 100644 index 0000000..761b619 Binary files /dev/null and b/src/assets/images/Frame4.png differ diff --git a/src/assets/images/Frame5.png b/src/assets/images/Frame5.png new file mode 100644 index 0000000..8f2cde7 Binary files /dev/null and b/src/assets/images/Frame5.png differ diff --git a/src/assets/images/Line 18.svg b/src/assets/images/Line 18.svg new file mode 100644 index 0000000..288a6d6 --- /dev/null +++ b/src/assets/images/Line 18.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/Vector 7192.svg b/src/assets/images/Vector 7192.svg new file mode 100644 index 0000000..205b5a0 --- /dev/null +++ b/src/assets/images/Vector 7192.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/frame.ts b/src/assets/images/frame.ts new file mode 100644 index 0000000..1696ddc --- /dev/null +++ b/src/assets/images/frame.ts @@ -0,0 +1,7 @@ +import frame1 from '@assets/images/Frame1.png'; +import frame2 from '@assets/images/Frame2.png'; +import frame3 from '@assets/images/Frame3.png'; +import frame4 from '@assets/images/Frame4.png'; +import frame5 from '@assets/images/Frame5.png'; + +export const frameImg: string[] = [frame1, frame2, frame3, frame4, frame5]; diff --git a/src/assets/images/img_1.png b/src/assets/images/img_1.png new file mode 100644 index 0000000..16c7ce7 Binary files /dev/null and b/src/assets/images/img_1.png differ diff --git a/src/assets/images/img_2.png b/src/assets/images/img_2.png new file mode 100644 index 0000000..d074697 Binary files /dev/null and b/src/assets/images/img_2.png differ diff --git a/src/assets/images/img_3.png b/src/assets/images/img_3.png new file mode 100644 index 0000000..931cbc1 Binary files /dev/null and b/src/assets/images/img_3.png differ diff --git a/src/assets/images/img_4.png b/src/assets/images/img_4.png new file mode 100644 index 0000000..2d2304e Binary files /dev/null and b/src/assets/images/img_4.png differ diff --git a/src/assets/images/img_5.png b/src/assets/images/img_5.png new file mode 100644 index 0000000..5212f36 Binary files /dev/null and b/src/assets/images/img_5.png differ diff --git a/src/assets/images/img_6.png b/src/assets/images/img_6.png new file mode 100644 index 0000000..c448fd1 Binary files /dev/null and b/src/assets/images/img_6.png differ diff --git a/src/assets/images/img_appapproachqr.png b/src/assets/images/img_appapproachqr.png new file mode 100644 index 0000000..ae241e6 Binary files /dev/null and b/src/assets/images/img_appapproachqr.png differ diff --git a/src/assets/images/img_avata1.png b/src/assets/images/img_avata1.png new file mode 100644 index 0000000..b2a84fd Binary files /dev/null and b/src/assets/images/img_avata1.png differ diff --git a/src/assets/images/img_avata2.png b/src/assets/images/img_avata2.png new file mode 100644 index 0000000..944cb49 Binary files /dev/null and b/src/assets/images/img_avata2.png differ diff --git a/src/assets/images/img_avata3.png b/src/assets/images/img_avata3.png new file mode 100644 index 0000000..3cd03c3 Binary files /dev/null and b/src/assets/images/img_avata3.png differ diff --git a/src/assets/images/img_avata4.png b/src/assets/images/img_avata4.png new file mode 100644 index 0000000..5b66799 Binary files /dev/null and b/src/assets/images/img_avata4.png differ diff --git a/src/assets/images/img_avata5.png b/src/assets/images/img_avata5.png new file mode 100644 index 0000000..615af21 Binary files /dev/null and b/src/assets/images/img_avata5.png differ diff --git a/src/assets/images/img_avata6.png b/src/assets/images/img_avata6.png new file mode 100644 index 0000000..6536dc1 Binary files /dev/null and b/src/assets/images/img_avata6.png differ diff --git a/src/assets/images/img_avata7.png b/src/assets/images/img_avata7.png new file mode 100644 index 0000000..f2e88b1 Binary files /dev/null and b/src/assets/images/img_avata7.png differ diff --git a/src/assets/images/img_detail_xl.png b/src/assets/images/img_detail_xl.png new file mode 100644 index 0000000..008ba88 Binary files /dev/null and b/src/assets/images/img_detail_xl.png differ diff --git a/src/assets/images/img_flag_kor_l.svg b/src/assets/images/img_flag_kor_l.svg new file mode 100644 index 0000000..7142a3f --- /dev/null +++ b/src/assets/images/img_flag_kor_l.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/images/img_flag_kor_s.svg b/src/assets/images/img_flag_kor_s.svg new file mode 100644 index 0000000..234f798 --- /dev/null +++ b/src/assets/images/img_flag_kor_s.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/images/img_graph.svg b/src/assets/images/img_graph.svg new file mode 100644 index 0000000..95b8b30 --- /dev/null +++ b/src/assets/images/img_graph.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/images/img_logotype_l.svg b/src/assets/images/img_logotype_l.svg new file mode 100644 index 0000000..0f10b9b --- /dev/null +++ b/src/assets/images/img_logotype_l.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/images/img_profile_30.svg b/src/assets/images/img_profile_30.svg new file mode 100644 index 0000000..639c9d8 --- /dev/null +++ b/src/assets/images/img_profile_30.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/images/img_purchasedproduct_113.png b/src/assets/images/img_purchasedproduct_113.png new file mode 100644 index 0000000..f447165 Binary files /dev/null and b/src/assets/images/img_purchasedproduct_113.png differ diff --git a/src/assets/images/img_review_4.png b/src/assets/images/img_review_4.png new file mode 100644 index 0000000..eeb5ef3 Binary files /dev/null and b/src/assets/images/img_review_4.png differ diff --git a/src/assets/images/userImg.ts b/src/assets/images/userImg.ts new file mode 100644 index 0000000..edc7045 --- /dev/null +++ b/src/assets/images/userImg.ts @@ -0,0 +1,11 @@ +import avata1 from '@assets/images/img_avata1.png'; +import avata2 from '@assets/images/img_avata2.png'; +import avata3 from '@assets/images/img_avata3.png'; +import avata4 from '@assets/images/img_avata4.png'; +import avata5 from '@assets/images/img_avata5.png'; +import avata6 from '@assets/images/img_avata6.png'; +import avata7 from '@assets/images/img_avata7.png'; + +const userImg: string[] = [avata1, avata2, avata3, avata4, avata5, avata6, avata7]; + +export default userImg; diff --git a/src/components/ProductInfo/ProductInfo.tsx b/src/components/ProductInfo/ProductInfo.tsx index 5dfcbe5..0dac3ed 100644 --- a/src/components/ProductInfo/ProductInfo.tsx +++ b/src/components/ProductInfo/ProductInfo.tsx @@ -24,10 +24,11 @@ import { discountPercentStyle, endSaleTiemStyle, reviewBoxStyle, + columnflexStyle, } from '@components/ProductInfo/ProductInfoStyle'; const ProductInfo = () => ( - <> +
@@ -76,7 +77,7 @@ const ProductInfo = () => ( 충전 및 보관 시 잠재적인 화재 위험을 유의하시고, 반드시 사용 설명서를 엄격히 준수해 주세요.
- +
); export default ProductInfo; diff --git a/src/components/ProductInfo/ProductInfoStyle.ts b/src/components/ProductInfo/ProductInfoStyle.ts index 9b2b489..727e907 100644 --- a/src/components/ProductInfo/ProductInfoStyle.ts +++ b/src/components/ProductInfo/ProductInfoStyle.ts @@ -165,3 +165,8 @@ export const warnDescriptionStyle = (theme: Theme) => css` ${theme.fonts.kor.captionMedium11} color: ${theme.colors.gray6}; `; + +export const columnflexStyle = css` + display: flex; + flex-direction: column; +`; diff --git a/src/components/button/categoryItemBtn/CategoryItemBtn.tsx b/src/components/button/categoryItemBtn/CategoryItemBtn.tsx index 8571fcc..5ec60c6 100644 --- a/src/components/button/categoryItemBtn/CategoryItemBtn.tsx +++ b/src/components/button/categoryItemBtn/CategoryItemBtn.tsx @@ -1,6 +1,6 @@ -import { ReactElement } from 'react'; +import { defaultBtnStyle, btnSizeMap } from '@components/button/categoryItemBtn/CategoryItemBtnStyle'; -import { defaultBtnStyle, btnSizeMap } from './CategoryItemBtnStyle'; +import { ReactElement } from 'react'; interface CategoryItemBtnProps { btnText: string; diff --git a/src/components/button/categoryItemBtn/CategoryItemBtnStyle.ts b/src/components/button/categoryItemBtn/CategoryItemBtnStyle.ts index 2fa51fd..f10601c 100644 --- a/src/components/button/categoryItemBtn/CategoryItemBtnStyle.ts +++ b/src/components/button/categoryItemBtn/CategoryItemBtnStyle.ts @@ -25,14 +25,18 @@ export const smallBtnContainerStyle = css` gap: 0.3rem; width: 17rem; height: 2.5rem; - padding: 0 0.4rem; + padding: 0.6rem 0 0.6rem 0.4rem; + + border-radius: 6px; `; export const mediumBtnContainerStyle = css` gap: 0.6rem; width: 19.3rem; height: 3rem; - padding: 0 0.6rem; + padding: 0.7rem 3.3rem 0.7rem 0.6rem; + + border-radius: 6px; `; export const btnSizeMap = { diff --git a/src/components/button/contactBtn/ContactBtn.tsx b/src/components/button/contactBtn/ContactBtn.tsx index f97cdfe..dc17217 100644 --- a/src/components/button/contactBtn/ContactBtn.tsx +++ b/src/components/button/contactBtn/ContactBtn.tsx @@ -1,4 +1,4 @@ -import { IcMessageBlack24 } from '../../../assets/icons/index'; +import { IcMessageBlack24 } from '@assets/icons/index'; import { flexStyle, buttonStyle, fontStyle } from './ContactBtnStyle'; diff --git a/src/components/button/contactBtn/ContactBtnStyle.ts b/src/components/button/contactBtn/ContactBtnStyle.ts index cd6410a..7d182c9 100644 --- a/src/components/button/contactBtn/ContactBtnStyle.ts +++ b/src/components/button/contactBtn/ContactBtnStyle.ts @@ -12,11 +12,11 @@ export const buttonStyle = (theme: Theme) => css` padding: 0.3rem 1.2rem; background: none; - border: none; + border: 1px solid ${theme.colors.gray9}; + border-radius: 999px; &:hover { border: 1px solid ${theme.colors.gray7}; - border-radius: 999px; } `; diff --git a/src/components/button/emojiBtn/EmojiBtn.tsx b/src/components/button/emojiBtn/EmojiBtn.tsx index cafa8e2..5a4aaaf 100644 --- a/src/components/button/emojiBtn/EmojiBtn.tsx +++ b/src/components/button/emojiBtn/EmojiBtn.tsx @@ -1,4 +1,4 @@ -import { IcShareBlack24, IcShopBlack24 } from '../../../assets/icons/index'; +import { IcShareBlack24, IcShopBlack24 } from '@assets/icons/index'; import defaultStyle from './EmojiBtnStyle'; diff --git a/src/components/button/likeBtn/LikeBtn.tsx b/src/components/button/likeBtn/LikeBtn.tsx index 4255b92..6ce3f74 100644 --- a/src/components/button/likeBtn/LikeBtn.tsx +++ b/src/components/button/likeBtn/LikeBtn.tsx @@ -1,4 +1,4 @@ -import { IcFvrBlack24 } from '../../../assets/icons/index'; +import { IcFvrBlack24 } from '@assets/icons/index'; import { defaultStyle, buttonStyle, contentContainer, fontStyle } from './LikeBtnStyle'; diff --git a/src/components/button/outlineTextBtn/OutlineTextBtnStyle.ts b/src/components/button/outlineTextBtn/OutlineTextBtnStyle.ts index e64a5f7..90bddf0 100644 --- a/src/components/button/outlineTextBtn/OutlineTextBtnStyle.ts +++ b/src/components/button/outlineTextBtn/OutlineTextBtnStyle.ts @@ -6,6 +6,7 @@ export const defaultBtnStyle = (theme: Theme) => css` color: ${theme.colors.black}; background-color: transparent; + cursor: pointer; border: 1px solid ${theme.colors.gray7}; ${theme.fonts.kor.bodySemibold13}; @@ -24,7 +25,7 @@ export const smallBtnContainerStyle = css` export const mediumBtnContainerStyle = css` width: 14.2rem; - height: 3.4rem; + height: 3rem; border-radius: 20px; `; diff --git a/src/components/button/recommendBtn/RecommanedBtn.tsx b/src/components/button/recommendBtn/RecommanedBtn.tsx new file mode 100644 index 0000000..5a15c5d --- /dev/null +++ b/src/components/button/recommendBtn/RecommanedBtn.tsx @@ -0,0 +1,19 @@ +import { css, Theme } from '@emotion/react'; + +const recommanedBtnContainer = (theme: Theme) => css` + align-content: center; + width: 5.6rem; + height: 2.2rem; + + text-align: center; + + background-color: ${theme.colors.brandDisable}; + border: 1px solid ${theme.colors.brandPrimary}; + border-radius: 2px; + + ${theme.fonts.eng.captionBold12} +`; + +const RecommanedBtn = () =>
추천 상점
; + +export default RecommanedBtn; diff --git a/src/components/button/recommendBtn/reviewBtn.tsx b/src/components/button/recommendBtn/reviewBtn.tsx new file mode 100644 index 0000000..996851d --- /dev/null +++ b/src/components/button/recommendBtn/reviewBtn.tsx @@ -0,0 +1,53 @@ +import { + IcGoodGray16, + IcHandshakeGray16, + IcCometGray16, + IcGoodBlue16, + IcHandshakeBlue16, + IcCometBlue16, +} from '@assets/icons'; +import { useState } from 'react'; + +import { flexStyle, btnStyleMap, countStyle } from './reviewBtnStyle'; + +interface ReviewBtnProps { + type: 'useful' | 'recommend' | 'like'; + clickedCount: number; +} + +const ReviewBtn = ({ type, clickedCount }: ReviewBtnProps) => { + const [isClicked, setIsClicked] = useState(false); + const [count, setCount] = useState(clickedCount); + const handleClick = (): void => { + setCount(isClicked ? count - 1 : count + 1); // 클릭시 이미 클릭된 버튼이면 -1, 클릭이 안 됐던 버튼은 +1 + setIsClicked(!isClicked); // 버튼 클릭 상태 바꾸기 + }; + const formatCount = (value: number) => (value > 999 ? '999+' : value); + const buttonType = { + useful: { + icon: isClicked ? : , + btnText: '유용해요', + }, + recommend: { + icon: isClicked ? : , + btnText: '추천해요', + }, + like: { + icon: isClicked ? : , + btnText: '공감해요', + }, + }; + const { icon, btnText } = buttonType[type]; + + return ( + + ); +} + +export default ReviewBtn; diff --git a/src/components/button/recommendBtn/reviewBtnStyle.ts b/src/components/button/recommendBtn/reviewBtnStyle.ts new file mode 100644 index 0000000..4dff774 --- /dev/null +++ b/src/components/button/recommendBtn/reviewBtnStyle.ts @@ -0,0 +1,67 @@ +import { Theme, css } from '@emotion/react'; + +// 공통 FlexStyle +const commonFlexStyle = css` + display: flex; + gap: 0.3rem; + align-items: center; + justify-content: center; + + white-space: nowrap; +`; + +// 한글 및 아이콘 FlexStyle +export const flexStyle = (theme: Theme) => css` + ${commonFlexStyle}; + ${theme.fonts.kor.captionMedium12}; +`; + +// 영문 FlexStyle +export const countStyle = (theme: Theme) => css` + ${commonFlexStyle}; + ${theme.fonts.eng.captionMedium12}; +`; + +// 공통 버튼 스타일 +const commonBtnContainerStyle = css` + display: flex; + align-items: center; + box-sizing: border-box; + width: auto; + min-width: 9.2rem; + height: 2.4rem; + padding: 0.4rem 0.8rem; + + border-radius: 4px; +`; + +// 버튼 클릭이 됐을때 스타일 +export const clickedBtnContainerStyle = (theme: Theme) => css` + ${commonBtnContainerStyle}; + color: ${theme.colors.notificationPrimary}; + + background-color: ${theme.colors.notificationBg}; + border: 1px solid ${theme.colors.notificationSecondary}; + + &:hover { + background-color: ${theme.colors.notificationSecondary}; + } +`; + +// 버튼 클릭이 안됐을때 스타일 +export const nonClickedBtnContainerStyle = (theme: Theme) => css` + ${commonBtnContainerStyle}; + color: ${theme.colors.gray9}; + + background-color: ${theme.colors.gray2}; + border: 1px solid ${theme.colors.gray2}; + + &:hover { + background-color: ${theme.colors.gray3}; + } +`; + +export const btnStyleMap = { + true: clickedBtnContainerStyle, + false: nonClickedBtnContainerStyle, +}; diff --git a/src/components/constants/menuItems.ts b/src/components/constants/menuItems.ts new file mode 100644 index 0000000..0c97333 --- /dev/null +++ b/src/components/constants/menuItems.ts @@ -0,0 +1,18 @@ +const MENU_ITEMS = [ + '개요', + '주문 & 배송', + '지불', + '환불 & 반품', + '설정', + '배송지 주소', + '문의 내역', + '친구 초대', + '고객센터', + '보고서 관리', + '피드백', + '제안서 목록', + '드롭쉬핑 센터', + '페널티 정보', +] as const; + +export default MENU_ITEMS; diff --git a/src/components/footer/Footer.tsx b/src/components/footer/Footer.tsx new file mode 100644 index 0000000..04781ff --- /dev/null +++ b/src/components/footer/Footer.tsx @@ -0,0 +1,31 @@ +import MESSAGE from '@constants/footerMsg'; + +import { grayLayout, footerStyle, subtitleStyle, desc } from './FooterStyle'; + +const Footer = () => ( +
+
+
+ 도움말 +

{MESSAGE.HELP}

+
+
+ AliExpress 다국어 사이트 +

{MESSAGE.ALI}

+
+
+ 카테고리별 검색 +

{MESSAGE.CATEGORY}

+
+
+ Alibaba 그룹 +

{MESSAGE.GROUP}

+
+
+
+

{MESSAGE.FOOTER}

+
+
+); + +export default Footer; diff --git a/src/components/footer/FooterStyle.ts b/src/components/footer/FooterStyle.ts new file mode 100644 index 0000000..c487bdd --- /dev/null +++ b/src/components/footer/FooterStyle.ts @@ -0,0 +1,33 @@ +import { Theme, css } from '@emotion/react'; + +export const grayLayout = (theme: Theme) => css` + display: grid; + grid-gap: 2.4rem 3rem; + grid-template-columns: 1fr 1fr; + width: 100%; + height: 18.5rem; + padding: 2.5rem 7.2rem 3.2rem 4.4rem; + + background-color: ${theme.colors.gray2}; +`; + +export const footerStyle = (theme: Theme) => css` + display: flex; + align-items: center; + width: 100%; + height: 4.2rem; + padding: 0 7.2rem 0 4.4rem; + + color: ${theme.colors.white}; + ${theme.fonts.kor.captionBold11} + + background-color: ${theme.colors.black}; +`; + +export const subtitleStyle = (theme: Theme) => css` + ${theme.fonts.kor.bodyBold13} +`; + +export const desc = (theme: Theme) => css` + ${theme.fonts.kor.captionMedium11}; +`; diff --git a/src/components/header/orderHeader/OrderHeader.tsx b/src/components/header/orderHeader/OrderHeader.tsx new file mode 100644 index 0000000..06c4260 --- /dev/null +++ b/src/components/header/orderHeader/OrderHeader.tsx @@ -0,0 +1,56 @@ +import { + ImgLogotypeL, + ImgFlagKorS, + IcArrowbottomSWhite12, + ImgLine, + IcUserWhite16, + IcCartWhite16, +} from '@assets/icons/index'; + +import { + headerStyle, + rightSectionLayout, + rightSectionGap, + textKoStyle, + textEnStyle, + textCircleStyle, +} from './OrderHeaderStyle'; + +const OrderHeader = () => ( +
+ {/* 로고섹션 */} +
+ +
+ {/* 오른쪽 섹션 */} +
+ {/* 국가섹션 */} +
+ +
+

수원시 장안구

+

/KO/KRW

+ +
+
+ + {/* 유저섹션 */} +
+ +
+

Hi,

+

+ +
+
+ {/* 카트섹션 */} +
+ +

장바구니

+

9

+
+
+
+); + +export default OrderHeader; diff --git a/src/components/header/orderHeader/OrderHeaderStyle.ts b/src/components/header/orderHeader/OrderHeaderStyle.ts new file mode 100644 index 0000000..c2b4ef7 --- /dev/null +++ b/src/components/header/orderHeader/OrderHeaderStyle.ts @@ -0,0 +1,48 @@ +import { Theme, css } from '@emotion/react'; + +export const headerStyle = (theme: Theme) => css` + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + height: 5.4rem; + padding: 0.9rem 5.2rem; + + background-color: ${theme.colors.gray10}; +`; + +export const rightSectionLayout = css` + display: flex; + align-items: center; +`; + +export const rightSectionGap = css` + display: flex; + gap: 0.4rem; + align-items: center; + padding: 1.2rem 0.8rem; +`; + +export const textKoStyle = (theme: Theme) => css` + ${theme.fonts.kor.captionMedium11}; + color: ${theme.colors.white}; +`; + +export const textEnStyle = (theme: Theme) => css` + ${theme.fonts.eng.captionMedium11}; + color: ${theme.colors.white}; +`; + +export const textCircleStyle = (theme: Theme) => css` + display: flex; + align-items: center; + justify-content: center; + width: 2.3rem; + height: 1.4rem; + padding: 0 0.8rem; + + color: ${theme.colors.black}; + + background-color: ${theme.colors.white}; + border-radius: 999px; +`; diff --git a/src/components/header/productHeader/Category.tsx b/src/components/header/productHeader/Category.tsx new file mode 100644 index 0000000..b517ac7 --- /dev/null +++ b/src/components/header/productHeader/Category.tsx @@ -0,0 +1,28 @@ +import { IcHamburgermenuBlack16, ImgVector7192 } from '@assets/icons/index'; +import CategoryItemBtn from '@components/button/categoryItemBtn/CategoryItemBtn'; +import { + CategoryLayout, + CategoryContainerStyle, + TitleStyle, + ScrollStyle, +} from '@components/header/productHeader/CategoryStyle'; +import { CATEGORIES } from '@constants/categoryList'; + +const Category = () => ( +
+ +
+
+ +

모든 카테고리

+
+
+ {CATEGORIES.map(({ icon: IconComponent, label }) => { + return } />; + })} +
+
+
+); + +export default Category; diff --git a/src/components/header/productHeader/CategoryStyle.ts b/src/components/header/productHeader/CategoryStyle.ts new file mode 100644 index 0000000..5a34887 --- /dev/null +++ b/src/components/header/productHeader/CategoryStyle.ts @@ -0,0 +1,46 @@ +import { Theme, css } from '@emotion/react'; + +export const CategoryLayout = css` + position: absolute; + top: 3.5rem; + left: -9rem; + display: flex; + flex-direction: column; + gap: 0; + align-items: center; + justify-content: center; +`; + +export const CategoryContainerStyle = (theme: Theme) => css` + display: flex; + flex-direction: column; + gap: 0.2rem; + padding: 0 0.1rem 0.6rem 0.6rem; + + background-color: ${theme.colors.white}; + border-radius: 9px; +`; + +export const ScrollStyle = (theme: Theme) => css` + height: 38.4rem; + overflow: hidden scroll; + + /* 스크롤바 스타일링 */ + &::-webkit-scrollbar { + width: 0.4rem; + } + + &::-webkit-scrollbar-thumb { + background-color: ${theme.colors.gray4}; + border-radius: 999px; + } +`; + +export const TitleStyle = (theme: Theme) => css` + display: flex; + ${theme.fonts.kor.bodyBold13} + gap: 0.6rem; + padding: 1.2rem 9.425rem 1.2rem 0.575rem; + + white-space: nowrap; +`; diff --git a/src/components/header/productHeader/MyList.tsx b/src/components/header/productHeader/MyList.tsx new file mode 100644 index 0000000..c5691d5 --- /dev/null +++ b/src/components/header/productHeader/MyList.tsx @@ -0,0 +1,48 @@ +import { ImgProfile30, ImgVector7192 } from '@assets/icons/index'; +import CategoryItemBtn from '@components/button/categoryItemBtn/CategoryItemBtn'; +import { + MyLayoutStyle, + MyContainerStyle, + MyWrapperStyle, + infoStyle, + mFontStyle, + bFontStyle, + mFontColorStyle, + dividerStyle, +} from '@components/header/productHeader/MyListStyle'; +import { MY_CATEGORIES } from '@constants/myList'; +import { useNavigate } from 'react-router-dom'; + +const MyList = () => { + const nav = useNavigate(); + + return ( +
+ +
+
+ +
+

환영합니다

+

데2걸

+

로그아웃

+
+
+
+
+ {MY_CATEGORIES.map(({ icon: IconComponent, label }, index) => ( + } + onClick={index === 0 ? () => nav('/order') : undefined} + /> + ))} +
+
+
+ ); +}; + +export default MyList; diff --git a/src/components/header/productHeader/MyListStyle.ts b/src/components/header/productHeader/MyListStyle.ts new file mode 100644 index 0000000..aed2d4d --- /dev/null +++ b/src/components/header/productHeader/MyListStyle.ts @@ -0,0 +1,53 @@ +import { Theme, css } from '@emotion/react'; + +export const MyLayoutStyle = css` + position: absolute; + top: 4.5rem; + right: -4rem; + display: flex; + flex-direction: column; + gap: 0; + align-items: center; + justify-content: center; +`; + +export const MyContainerStyle = (theme: Theme) => css` + display: flex; + flex-direction: column; + gap: 1.2rem; + width: 20rem; + height: 25.7rem; + padding: 2rem 1.3rem; + + background-color: ${theme.colors.white}; + border-radius: 12px; +`; + +export const MyWrapperStyle = css` + display: flex; + gap: 0.9rem; +`; + +export const infoStyle = css` + display: flex; + flex-direction: column; + gap: 0.3rem; +`; + +export const mFontStyle = (theme: Theme) => css` + ${theme.fonts.kor.captionMedium10} +`; +export const bFontStyle = (theme: Theme) => css` + ${theme.fonts.eng.captionBold11} + margin-bottom: 0.3rem; +`; +export const mFontColorStyle = (theme: Theme) => css` + color: ${theme.colors.notificationPrimary}; +`; + +export const dividerStyle = (theme: Theme) => css` + width: 16.95rem; + height: 0; + + border-top: 1px solid ${theme.colors.gray3}; +`; diff --git a/src/components/header/productHeader/ProductHeader.tsx b/src/components/header/productHeader/ProductHeader.tsx new file mode 100644 index 0000000..009481b --- /dev/null +++ b/src/components/header/productHeader/ProductHeader.tsx @@ -0,0 +1,120 @@ +import { + ImgLogotypeL, + IcHamburgermenuWhite14, + IcSearchWhite24, + IcImagesearchBlack24, + IcQrcodeWhite24, + ImgFlagKorL, + IcArrowbottomSWhite12, + IcUserWhite24, + IcCartWhite24, +} from '@assets/icons/index'; +import Category from '@components/header/productHeader/Category'; +import MyList from '@components/header/productHeader/MyList'; +import { useState } from 'react'; + +import { + relativeStyle, + headerStyle, + logoDescStyle, + fontEnStyle, + fontKoStyle, + hambergerStyle, + inputStyle, + IconContainer, + searchStyle, + appLogLayout, + textContainer, + fontEnAppStyle, + flagLayout, + flagContainer, + fontKRWStyle, + arrowStyle, + fontMStyle, + fontBStyle, + cartLayout, + numBackStyle, +} from './ProductHeaderStyle'; + +const ProductHeader = () => { + const [activePopup, setActivePopup] = useState<'burger' | 'info' | null>(null); + + const togglePopup = (popup: 'burger' | 'info') => { + setActivePopup(activePopup === popup ? null : popup); + }; + return ( +
+ {/* 로고섹션 */} +
+ +
+

Alibaba Group

+

해외 직구 플랫폼

+
+
+ + {/* 햄버거 메뉴 */} + + + {/* 검색섹션 */} +
+ +
+ + +
+
+ + {/* 유틸리티 섹션 */} + {/* 앱 다운로드 */} +
+ +
+

AliExpress

+

앱 다운로드

+
+
+ + {/* 국가 및 통화 */} +
+ +
+
+

KO/

+

KRW

+
+ +
+
+ + {/* 사용자 계정 */} +
togglePopup('info')}> + +
+
+

환영합니다!/

+

로그인/회원가입

+
+ +
+ {/* 카테고리 팝업 */} + {activePopup === 'info' && } +
+ + {/* 장바구니 */} +
+ +
+

0

+

장바구니

+
+
+
+ ); +}; + +export default ProductHeader; diff --git a/src/components/header/productHeader/ProductHeaderStyle.ts b/src/components/header/productHeader/ProductHeaderStyle.ts new file mode 100644 index 0000000..938275b --- /dev/null +++ b/src/components/header/productHeader/ProductHeaderStyle.ts @@ -0,0 +1,146 @@ +import { Theme, css } from '@emotion/react'; + +export const relativeStyle = css` + position: relative; +`; + +export const headerStyle = (theme: Theme) => css` + display: flex; + gap: 1.6rem; + justify-content: space-between; + width: 100vw; + height: 5.4rem; + padding: 0.5rem 5.6rem 0.5rem 2.6rem; + + background-color: ${theme.colors.gray10}; + + & > *:not(:first-of-type) { + display: flex; + align-items: center; + } +`; + +export const logoDescStyle = css` + position: absolute; + bottom: 0.7rem; + display: flex; + gap: 0.2rem; + align-items: center; + justify-content: center; +`; + +export const fontEnStyle = (theme: Theme) => css` + color: ${theme.colors.white}; + ${theme.fonts.eng.captionMedium11}; +`; + +export const fontKoStyle = (theme: Theme) => css` + color: ${theme.colors.white}; + ${theme.fonts.kor.captionMedium09}; +`; + +export const hambergerStyle = (theme: Theme) => css` + width: 2.4rem; + height: 2.4rem; + margin: 1rem 0; + padding: 0.5rem; + + background-color: ${theme.colors.gray7}; + border-radius: 999px; +`; + +export const inputStyle = (theme: Theme) => css` + display: flex; + align-items: center; + justify-content: space-between; + width: 67.7rem; + height: 3.2rem; + padding: 0.3rem 2rem; + + border: none; + border-radius: 999px; + + &::placeholder { + color: ${theme.colors.gray4}; + ${theme.fonts.kor.captionBold11} + } +`; + +export const IconContainer = css` + position: absolute; + right: 0.3rem; + display: flex; + gap: 0.5rem; + align-items: center; +`; + +export const searchStyle = (theme: Theme) => css` + width: 4.4rem; + height: 2.6rem; + + background-color: ${theme.colors.black}; + border-radius: 99px; +`; + +export const appLogLayout = css` + display: flex; + gap: 0.4rem; + align-items: center; + padding: 1rem 0.8rem; +`; + +export const textContainer = (theme: Theme) => css` + display: flex; + flex-direction: column; + + color: ${theme.colors.white}; +`; + +export const fontEnAppStyle = (theme: Theme) => css` + ${theme.fonts.eng.captionMedium10}; +`; + +export const fontMStyle = (theme: Theme) => css` + ${theme.fonts.kor.captionMedium10}; +`; +export const flagLayout = css` + display: flex; + gap: 0.2rem; + align-items: center; + padding: 1rem 0.8rem; +`; + +export const flagContainer = css` + display: flex; +`; + +export const fontKRWStyle = (theme: Theme) => css` + ${theme.fonts.eng.captionBold10}; +`; + +export const arrowStyle = css` + align-self: flex-end; +`; + +export const fontBStyle = (theme: Theme) => css` + ${theme.fonts.kor.captionBold10}; +`; + +export const cartLayout = css` + display: flex; + gap: 0.3rem; + align-items: center; + padding: 1rem 0.8rem; +`; + +export const numBackStyle = (theme: Theme) => css` + display: flex; + justify-content: center; + width: 1.9rem; + height: 1.2rem; + + color: ${theme.colors.gray10}; + + background-color: ${theme.colors.white}; + border-radius: 999px; +`; diff --git a/src/components/infoTable/InfoTable.tsx b/src/components/infoTable/InfoTable.tsx new file mode 100644 index 0000000..df76d0a --- /dev/null +++ b/src/components/infoTable/InfoTable.tsx @@ -0,0 +1,50 @@ +import { IcBoxGray16, IcShopGray16, IcBatteryGray16, IcPortGray16, IcPhoneGray16, IcChargeGray16 } from '@assets/icons'; +import { infoTableLayoutStyle, infoTableStyle, infoTitleStyle } from '@components/infoTable/InfoTableStyle'; + +const InfoTable = () => ( +
+

상품 정보

+ + + + + + + + + + + + + + + + + + + + + +
+ + 무게 + 100g + + 포트 + USB-A
+ + 포장 여부 + 포장됨 + + 호환 + 다양한 기기
+ + 용량 + 10000mAh + + 충전 속도 + 고속 충전
+
+); + +export default InfoTable; diff --git a/src/components/infoTable/InfoTableStyle.ts b/src/components/infoTable/InfoTableStyle.ts new file mode 100644 index 0000000..2a25a0b --- /dev/null +++ b/src/components/infoTable/InfoTableStyle.ts @@ -0,0 +1,44 @@ +import { css, Theme } from '@emotion/react'; + +export const infoTableLayoutStyle = css` + width: 96rem; +`; + +export const infoTitleStyle = (theme: Theme) => css` + height: 4.6rem; + align-content: center; + + ${theme.fonts.kor.titleBold20} +`; + +export const infoTableStyle = (theme: Theme) => css` + width: 96rem; + height: 13.2rem; + border-radius: 8px; + + th, + td { + width: 34.1rem; + height: 4.4rem; + padding: 0 1.2rem; + box-sizing: border-box; + + text-align: left; + ${theme.fonts.eng.captionMedium12} + display: flex; + align-items: center; + } + + th { + width: 13.7rem; + + background-color: ${theme.colors.gray2}; + ${theme.fonts.kor.captionSemibold12} + gap: 0.6rem; + } + + tr { + display: flex; + border: 1px solid ${theme.colors.gray3}; + } +`; diff --git a/src/components/orderBox/OrderBox.tsx b/src/components/orderBox/OrderBox.tsx new file mode 100644 index 0000000..b74d1e2 --- /dev/null +++ b/src/components/orderBox/OrderBox.tsx @@ -0,0 +1,99 @@ +import { + IcMapBlackStorke18, + IcDeliveryBlack20, + IcArrowrightSBlack24, + IcShieldBlack20, + IcCountdownGray18, + IcCountupGray18, +} from '@assets/icons'; +import EmojiBtn from '@components/button/emojiBtn/EmojiBtn'; +import LikeBtn from '@components/button/likeBtn/LikeBtn'; +import TextBtn from '@components/button/textBtn/TextBtn'; +import { + dividerStyle, + orderBoxContinerStyle, + orderInfoLayoutStyle, + orderTitleWrapperStyle, + korTitleStyle, + adressStyle, + orderInfoLayouttyle, + freeOrderInfoWrapperStyle, + flexBoxStyle, + arrivalDateWrapperStyle, + arriveTitleStyle, + engTitleStyle, + descriptionBoxStyle, + privacyInfoLayoutStyle, + privacyTitleBoxStyle, + orderButtonsLayoutStyle, + countButtonsStyle, + iconWrapperStyle, + engCaptionBoldStyle, + orderBtnWrapperStyle, + emojiBtnWrapperStyle, +} from '@components/orderBox/OrderboxStyle'; + +const OrderBox = () => ( +
+
+
+

배송지:

+
+ +

Gangnam-gu, Seoul, Korea

+
+
+
+
+
+
+
+ + 무료 배송 + {/* 영경이가 만든 무료 반품 버튼 넣기 */} +
+ +
+
+ 도착일: + 11 월 19 일-26 일 +
+
+
+
+ + 개인 정보 보호 +
+
+

안심 결제: 카드 정보는 안전하게 보호되며 유출되지 않습니다.

+

개인정보 보호: 개인정보 보안을 최우선으로 생각합니다.

+
+
+
+
+
+
+
수량
+
+
+ +
+

1

+
+ +
+
+
+ + +
+ + + +
+
+
+
+); + +export default OrderBox; diff --git a/src/components/orderBox/OrderboxStyle.ts b/src/components/orderBox/OrderboxStyle.ts new file mode 100644 index 0000000..e55dae8 --- /dev/null +++ b/src/components/orderBox/OrderboxStyle.ts @@ -0,0 +1,169 @@ +import { css, Theme } from '@emotion/react'; + +export const orderBoxContinerStyle = (theme: Theme) => css` + display: flex; + flex-direction: column; + gap: 1.6rem; + box-sizing: border-box; + width: 30rem; + height: 38rem; + padding: 1.1rem 1.3rem; + + border: 1px solid ${theme.colors.gray4}; + border-radius: 6px; +`; + +export const dividerStyle = (theme: Theme) => css` + width: 27.4rem; + height: 0.1rem; + + background-color: ${theme.colors.gray2}; +`; + +export const orderInfoLayoutStyle = css` + display: flex; + flex-direction: column; + width: 100%; + height: 14.6rem; +`; + +export const orderTitleWrapperStyle = css` + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + height: 1.8rem; + margin-bottom: 1rem; +`; + +export const korTitleStyle = (theme: Theme) => css` + ${theme.fonts.kor.captionBold12} +`; + +export const engTitleStyle = (theme: Theme) => css` + ${theme.fonts.eng.captionMedium12} +`; + +export const adressStyle = (theme: Theme) => css` + ${theme.fonts.eng.captionMedium12} + display: flex; + align-items: center; +`; + +export const orderInfoLayouttyle = css` + display: flex; + flex-direction: column; + width: 100%; + height: 10.8rem; + margin-top: 1rem; +`; + +export const freeOrderInfoWrapperStyle = css` + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + height: 2.4rem; + margin-bottom: 0.4rem; +`; + +export const flexBoxStyle = css` + display: flex; + align-items: center; + height: 100%; +`; + +export const arrivalDateWrapperStyle = css` + box-sizing: border-box; + width: 100%; + height: 1.9rem; + padding-left: 2.2rem; +`; + +export const arriveTitleStyle = (theme: Theme) => css` + margin-right: 0.3rem; + + color: ${theme.colors.gray6}; + ${theme.fonts.kor.captionMedium12} +`; + +export const privacyInfoLayoutStyle = css` + display: flex; + flex-direction: column; + gap: 0.3rem; + width: 100%; + height: 4.9rem; +`; + +export const privacyTitleBoxStyle = css` + display: flex; + align-items: center; + height: 4rem; + margin-top: 1.2rem; +`; + +export const descriptionBoxStyle = (theme: Theme) => css` + display: flex; + flex-direction: column; + gap: 0.3rem; + align-items: flex-start; + box-sizing: border-box; + width: 100%; + height: 2.5rem; + padding: 0 2.2rem; + + color: ${theme.colors.gray6}; + ${theme.fonts.kor.captionMedium9} +`; + +export const orderButtonsLayoutStyle = css` + width: 100%; + height: 17.8rem; +`; + +export const countButtonsStyle = css` + display: flex; + align-items: center; + justify-content: space-between; + width: 6.7rem; + height: 1.8rem; + margin: 0.8rem 0 1.6rem; +`; + +export const iconWrapperStyle = (theme: Theme) => css` + display: flex; + align-items: center; + justify-content: center; + width: 1.8rem; + height: 1.8rem; + + background-color: ${theme.colors.gray2}; + border-radius: 50px; + + &:hover { + background-color: ${theme.colors.gray4}; + } + + &:hover svg path { + stroke: ${theme.colors.gray6}; + } +`; + +export const engCaptionBoldStyle = (theme: Theme) => css` + ${theme.fonts.eng.captionBold12} +`; + +export const orderBtnWrapperStyle = css` + display: flex; + flex-direction: column; + gap: 0.8rem; + width: 100%; + height: 12.5rem; +`; + +export const emojiBtnWrapperStyle = css` + display: flex; + gap: 0.8rem; + width: 100%; + height: 3.6rem; +`; diff --git a/src/components/orderDetail/orderHistory/orderHistory.tsx b/src/components/orderDetail/orderHistory/orderHistory.tsx new file mode 100644 index 0000000..0fa0f2a --- /dev/null +++ b/src/components/orderDetail/orderHistory/orderHistory.tsx @@ -0,0 +1,72 @@ +import { IcArrowrightGray12, IcChatBlack24, IcArrowbottomGray12, IcCameraBlack24 } from '@assets/icons'; +import productImage from '@assets/images/img_purchasedproduct_113.png'; +import OutlineTextBtn from '@components/button/outlineTextBtn/OutlineTextBtn'; +import ORDER_HISTORYT from '@constants/orderHisotry'; +import { + orderHistoryContainerStyle, + headerStyle, + storeNameContainerStyle, + storeNameStyle, + productContentStyle, + productImageStyle, + productInfoContainerStyle, + productInfoStyle, + productTitleStyle, + productPriceStyle, + blueInfoStyle, + buttonContainerStyle, + productCostStyle, + costRowStyle, + costLabelStyle, + costValueStyle, + costGrayLabelStyle, + costBoldLabelStyle, +} from './orderHistoryStyle'; + +const OrderHistory = () => ( +
+
주문 내역
+
+

Toocki Flagship Direct store

+ + +
+
+
+ 상품 이미지 + +
+
+
+

{ORDER_HISTORYT.detail}

+

Clear

+
+

₩{ORDER_HISTORYT.price.toLocaleString()}

+

x{ORDER_HISTORYT.quantity}

+
+
+

빠른 배송 · 무료 반품 · 배송 약속

+
+
+ + + +
+
+
+
+

합계

+
+

₩{ORDER_HISTORYT.price.toLocaleString()}

+ +
+
+
+

총 금액

+

₩1,202

+
+
+
+); + +export default OrderHistory; diff --git a/src/components/orderDetail/orderHistory/orderHistoryStyle.ts b/src/components/orderDetail/orderHistory/orderHistoryStyle.ts new file mode 100644 index 0000000..342c05a --- /dev/null +++ b/src/components/orderDetail/orderHistory/orderHistoryStyle.ts @@ -0,0 +1,123 @@ +import { Theme, css } from '@emotion/react'; + +const flexCenter = css` + display: flex; + align-items: center; +`; + +export const orderHistoryContainerStyle = (theme: Theme) => css` + display: flex; + flex-direction: column; + width: 91.1rem; + height: 29.4rem; + padding: 2.4rem 2.1rem; + + background-color: ${theme.colors.white}; +`; + +export const headerStyle = (theme: Theme) => css` + ${theme.fonts.kor.titleBold20}; +`; + +export const storeNameContainerStyle = css` + ${flexCenter}; + height: 2.4rem; + margin: 1rem 0 1.3rem; +`; + +export const storeNameStyle = (theme: Theme) => css` + ${theme.fonts.eng.bodyBold14}; +`; + +export const productContentStyle = css` + display: flex; + min-width: 55.4rem; + margin-bottom: 2.4rem; +`; + +export const productImageStyle = css` + position: relative; + margin-right: 1.4rem; + + svg { + position: absolute; + right: 0; + bottom: 0; + z-index: 1; + } +`; + +export const productInfoContainerStyle = (theme: Theme) => css` + display: flex; + flex-direction: column; + gap: 1.5rem; + margin-right: 4.3rem; + ${theme.fonts.eng.bodyBold14}; +`; + +export const productInfoStyle = css` + display: flex; + flex-direction: column; + gap: 0.9rem; + width: 56rem; +`; + +export const productTitleStyle = (theme: Theme) => css` + ${theme.fonts.kor.bodyBold13}; + width: 56rem; + overflow: hidden; + + white-space: nowrap; + text-overflow: ellipsis; +`; + +export const productPriceStyle = css` + ${flexCenter}; + gap: 1.1rem; +`; + +export const blueInfoStyle = (theme: Theme) => css` + ${theme.fonts.kor.captionMedium12}; + color: ${theme.colors.blue}; +`; + +export const buttonContainerStyle = css` + display: flex; + flex-direction: column; + gap: 1.2rem; +`; + +export const productCostStyle = css` + display: flex; + flex-direction: column; + gap: 0.9rem; + padding-left: 41.7rem; +`; + +export const costRowStyle = css` + ${flexCenter}; + justify-content: space-between; +`; + +export const costGrayLabelStyle = (theme: Theme) => css` + ${theme.fonts.kor.bodySemibold13}; + color: ${theme.colors.gray7}; +`; + +export const costLabelStyle = (theme: Theme) => css` + ${theme.fonts.kor.bodySemibold13}; +`; + +export const costBoldLabelStyle = (theme: Theme) => css` + ${theme.fonts.eng.bodyBold13}; + padding-right: 2rem; +`; + +export const costValueStyle = (theme: Theme) => css` + display: flex; + gap: 0.9rem; + align-items: center; + + ${theme.fonts.eng.bodyMedium13}; + color: ${theme.colors.gray7}; +`; diff --git a/src/components/orderDetail/orderInfo/buyerInfo/buyerInfo.tsx b/src/components/orderDetail/orderInfo/buyerInfo/buyerInfo.tsx new file mode 100644 index 0000000..3918c27 --- /dev/null +++ b/src/components/orderDetail/orderInfo/buyerInfo/buyerInfo.tsx @@ -0,0 +1,15 @@ +import { IcMapBlackStorke24 } from '@assets/icons'; +import BUYER_INFO from '@constants/buyerInfo'; + +import { buyerInfoComponentStyle, korStringStyle, engStringStyle, infoDetailStyle } from './buyerInfoStyle'; + +export const BuyerInfoComponent = () => ( +
+ +
+ {BUYER_INFO.name} + {BUYER_INFO.phone} + {BUYER_INFO.address} +
+
+); diff --git a/src/components/orderDetail/orderInfo/buyerInfo/buyerInfoStyle.ts b/src/components/orderDetail/orderInfo/buyerInfo/buyerInfoStyle.ts new file mode 100644 index 0000000..1e18053 --- /dev/null +++ b/src/components/orderDetail/orderInfo/buyerInfo/buyerInfoStyle.ts @@ -0,0 +1,25 @@ +import { Theme, css } from '@emotion/react'; + +export const buyerInfoComponentStyle = (theme: Theme) => css` + display: flex; + gap: 1.7rem; + width: 44.8rem; + height: 10.8rem; + padding: 1.6rem 2.4rem; + + background-color: ${theme.colors.white}; +`; + +export const infoDetailStyle = css` + display: flex; + flex-direction: column; + gap: 0.9rem; +`; + +export const korStringStyle = (theme: Theme) => css` + ${theme.fonts.kor.bodyMedium14} +`; + +export const engStringStyle = (theme: Theme) => css` + ${theme.fonts.eng.bodyBold14} +`; diff --git a/src/components/orderDetail/orderInfo/orderInfo.tsx b/src/components/orderDetail/orderInfo/orderInfo.tsx new file mode 100644 index 0000000..6c468fc --- /dev/null +++ b/src/components/orderDetail/orderInfo/orderInfo.tsx @@ -0,0 +1,17 @@ +import { PurchaseInfoComponent } from './purchaseInfo/purchaseInfo'; +import { BuyerInfoComponent } from './buyerInfo/buyerInfo'; +import { orderInfoComponentStyle, contentComponentStyle, headerComponentStyle } from './orderInfoStyle'; + +export const OrderInfoComponent = () => { + return ( +
+
+

주문 & 배송

+
+
+ + +
+
+ ); +}; diff --git a/src/components/orderDetail/orderInfo/orderInfoStyle.ts b/src/components/orderDetail/orderInfo/orderInfoStyle.ts new file mode 100644 index 0000000..c2059d4 --- /dev/null +++ b/src/components/orderDetail/orderInfo/orderInfoStyle.ts @@ -0,0 +1,27 @@ +import { Theme, css } from '@emotion/react'; + +export const orderInfoComponentStyle = css` + display: flex; + flex-direction: column; + gap: 1.6rem; + width: 91.1rem; + height: 17.6rem; + margin-top: 4.8rem; +`; + +export const headerComponentStyle = (theme: Theme) => css` + display: flex; + align-items: center; + width: 91.1rem; + min-height: 5.2rem; + padding-left: 2.1rem; + + ${theme.fonts.kor.titleBold20} + background-color: ${theme.colors.white}; +`; + +export const contentComponentStyle = css` + display: flex; + gap: 1.5rem; + width: 100%; +`; diff --git a/src/components/orderDetail/orderInfo/purchaseInfo/purchaseInfo.tsx b/src/components/orderDetail/orderInfo/purchaseInfo/purchaseInfo.tsx new file mode 100644 index 0000000..47ccb4e --- /dev/null +++ b/src/components/orderDetail/orderInfo/purchaseInfo/purchaseInfo.tsx @@ -0,0 +1,46 @@ +import { IcListBlack24 } from '@assets/icons'; +import BUYER_INFO from '@constants/buyerInfo'; + +import { + purchaseInfoComponentStyle, + korStringStyle, + engStringStyle, + infoDetailStyle, + copyButtonStyle, + copyStringStyle, +} from './purchaseInfoStyle'; + +export const PurchaseInfoComponent = () => { + const handleCopyClick = () => { + const copyText = `${BUYER_INFO.orderId}`; + navigator.clipboard.writeText(copyText); + }; + + return ( +
+ +
+
+ 주문 + ID: + {BUYER_INFO.orderId} +
+
+ 주문일: + {BUYER_INFO.orderYear} + + {BUYER_INFO.orderMonth} + + {BUYER_INFO.orderDay} + +
+ 결제 수단: {BUYER_INFO.paymentMethod} +
+
+ + 복사 + +
+
+ ); +}; diff --git a/src/components/orderDetail/orderInfo/purchaseInfo/purchaseInfoStyle.ts b/src/components/orderDetail/orderInfo/purchaseInfo/purchaseInfoStyle.ts new file mode 100644 index 0000000..4d61c78 --- /dev/null +++ b/src/components/orderDetail/orderInfo/purchaseInfo/purchaseInfoStyle.ts @@ -0,0 +1,35 @@ +import { Theme, css } from '@emotion/react'; + +export const purchaseInfoComponentStyle = (theme: Theme) => css` + display: flex; + gap: 1rem; + width: 44.8rem; + height: 10.8rem; + padding: 1.6rem 2.4rem; + + background-color: ${theme.colors.white}; +`; + +export const infoDetailStyle = css` + display: flex; + flex-direction: column; + gap: 1.1rem; +`; + +export const copyButtonStyle = css` + display: flex; + padding-left: 1rem; +`; + +export const korStringStyle = (theme: Theme) => css` + ${theme.fonts.kor.bodyMedium14} +`; + +export const engStringStyle = (theme: Theme) => css` + ${theme.fonts.eng.bodyBold14} +`; + +export const copyStringStyle = (theme: Theme) => css` + ${theme.fonts.kor.bodyMediumBlue14} + cursor: pointer; +`; diff --git a/src/components/orderDetail/orderStatus/deliveryStauts/deliveryStatus.tsx b/src/components/orderDetail/orderStatus/deliveryStauts/deliveryStatus.tsx new file mode 100644 index 0000000..5cb5180 --- /dev/null +++ b/src/components/orderDetail/orderStatus/deliveryStauts/deliveryStatus.tsx @@ -0,0 +1,93 @@ +import { + CardDelieverStatus1, + CardDelieverStatus2, + CardDelieverStatus3, + CardDelieverStatus4, + CardDelieverStatus5, + CardDelieverStatus6, + CardDelieverFinalStatus1, + CardDelieverFinalStatus2, + CardDelieverFinalStatus3, + CardDelieverFinalStatus4, + CardDelieverFinalStatus5, + CardDelieverFinalStatus6, + IcArrowrightGray24, +} from '@assets/icons'; +import { LAST_STATUS } from '@constants/orderStatusList'; + +import { + headerStringStyle, + textStringStyle, + deliveryStatusContainserStyle, + contentContainerStyle, + textStatusContainerStyle, + iconContainerStyle, + eachIconContainerStyle, + iconBoxStyle, + currentSpanStyle, + dateStatusContainerStyle, + arrowContainerStyle +} from './deliveryStatusStyle'; + +type StatusName = '결제 완료' | '상품 준비' | '국제 운송' | '국내 입고' | '국내 배송' | '배송 완료'; + +const getIconComponent = (status: StatusName, currentStatus: StatusName) => { + const isFinal = status === currentStatus; + + const iconMap = { + '결제 완료': isFinal ? : , + '상품 준비': isFinal ? : , + '국제 운송': isFinal ? : , + '국내 입고': isFinal ? : , + '국내 배송': isFinal ? : , + '배송 완료': isFinal ? : , + }; + + return iconMap[status] || null; +}; + +const statusList: StatusName[] = ['결제 완료', '상품 준비', '국제 운송', '국내 입고', '국내 배송', '배송 완료']; + +const DeliveryStatusComponent = () => { + const currentStatus = LAST_STATUS.name; + const isCurrentStatus = (status: StatusName) => currentStatus === status; + + return ( +
+
+

배송 현황

+
+
+
+
+

+ {LAST_STATUS.steps.month}월 {LAST_STATUS.steps.day}일 {LAST_STATUS.steps.time} +

+
+
+

{LAST_STATUS.steps.message}

+
+
+
+
+ {statusList.map((status, index) => ( + <> +
+ {getIconComponent(status, currentStatus)} + {status} +
+ {index !== 2 && index < statusList.length - 1 && ( +
+ +
+ )} + + ))} +
+
+
+
+ ); +}; + +export default DeliveryStatusComponent; diff --git a/src/components/orderDetail/orderStatus/deliveryStauts/deliveryStatusStyle.ts b/src/components/orderDetail/orderStatus/deliveryStauts/deliveryStatusStyle.ts new file mode 100644 index 0000000..ebf987d --- /dev/null +++ b/src/components/orderDetail/orderStatus/deliveryStauts/deliveryStatusStyle.ts @@ -0,0 +1,92 @@ +import { Theme, css } from '@emotion/react'; + +export const headerStringStyle = (theme: Theme) => css` + ${theme.fonts.kor.bodyMedium14}; + color: ${theme.colors.gray6}; +`; + +export const textStringStyle = (theme: Theme) => css` + ${theme.fonts.kor.titleBold20}; +`; + +export const deliveryStatusContainserStyle = (theme: Theme) => css` + display: flex; + flex-direction: column; + gap: 1.4rem; + align-items: flex-start; + width: 48.5rem; + height: 35.7rem; + padding: 2rem 2.4rem 3rem; + + background-color: ${theme.colors.gray1}; + border-radius: 8px; +`; + +export const contentContainerStyle = css` + display: flex; + flex-direction: column; + gap: 3.2rem; + align-items: center; + justify-content: center; +`; + +export const textStatusContainerStyle = (theme: Theme) => css` + display: flex; + flex-direction: column; + gap: 0.7rem; + align-items: center; + justify-content: center; + width: 43.7rem; + height: 7rem; + padding: 1.3rem 0; + + background: ${theme.colors.gray2}; + border: 1px solid ${theme.colors.gray3}; + border-radius: 12px; +`; + +export const dateStatusContainerStyle = (theme: Theme) => css` + display: flex; + align-items: center; + height: 1.6rem; + + ${theme.fonts.both.captionBothMedium12}; + color: ${theme.colors.gray6}; +`; + +export const iconContainerStyle = css` + display: flex; + flex-direction: column; + gap: 2rem; + align-items: center; + justify-content: center; +`; + +export const eachIconContainerStyle = (theme: Theme) => css` + display: grid; + grid-template-columns: repeat(5, 1fr); + gap: 1.6rem; + align-items: center; + width: 31.7rem; + height: 17.2rem; + + color: ${theme.colors.gray6}; +`; + +export const iconBoxStyle = css` + display: flex; + flex-direction: column; + gap: 0.6rem; + align-items: center; + width: 5.6rem; +`; + +export const currentSpanStyle = (theme: Theme) => css` + ${theme.fonts.kor.captionBold12}; + color: ${theme.colors.black}; +`; + +export const arrowContainerStyle = css` + display: flex; + justify-content: center; +`; diff --git a/src/components/orderDetail/orderStatus/detailStatus/detailStatus.tsx b/src/components/orderDetail/orderStatus/detailStatus/detailStatus.tsx new file mode 100644 index 0000000..5ca5f10 --- /dev/null +++ b/src/components/orderDetail/orderStatus/detailStatus/detailStatus.tsx @@ -0,0 +1,54 @@ +import { IcDetailedstatusDotDefault, IcDetailedstatusDotVarient } from '@assets/icons'; +import { ORDER_STATUS } from '@constants/orderStatusList'; + +import { + dateStringStyle, + stepMessageStringStyle, + headerStringStyle, + deliveryStatusContainserStyle, + statusNameStringStyle, + firstStringStyle, + frameTitleStyle, + titleDateStringStyle, + deliveryStatusCardStyle, + frameDetailStyle, + frameDetailContentStyle, + frameContentStyle, + lineConnectorStyle, +} from './detailStatusStyle'; + +const DetailStatusComponent = () => { + const statuses = Object.values(ORDER_STATUS); + return ( +
+
+

상세 현황

+
+
+ {statuses.map((status, index) => ( +
+
+

+ {`${status.steps[status.steps.length - 1].month}월 ${status.steps[status.steps.length - 1].day}일`} +
+ {status.steps[status.steps.length - 1].time} +

+ {index === 0 ? : } +

{status.name}

+
+
    + {status.steps.map((step) => ( +
  • +

    {step.message}

    +

    {`${step.month}월 ${step.day}일 ${step.time}`}

    +
  • + ))} +
+
+ ))} +
+
+ ); +}; + +export default DetailStatusComponent; diff --git a/src/components/orderDetail/orderStatus/detailStatus/detailStatusStyle.ts b/src/components/orderDetail/orderStatus/detailStatus/detailStatusStyle.ts new file mode 100644 index 0000000..2ca48e5 --- /dev/null +++ b/src/components/orderDetail/orderStatus/detailStatus/detailStatusStyle.ts @@ -0,0 +1,120 @@ +import { Theme, css } from '@emotion/react'; + +export const headerStringStyle = (theme: Theme) => css` + display: flex; + align-items: center; + min-height: 5.7rem; + + ${theme.fonts.kor.bodyMedium14}; + color: ${theme.colors.gray6}; +`; + +export const deliveryStatusContainserStyle = (theme: Theme) => css` + display: flex; + flex-direction: column; + align-items: flex-start; + width: 35.6rem; + height: 35.7rem; + padding: 0 2.4rem 0 2.5rem; + + background-color: ${theme.colors.gray1}; + border-radius: 8px; +`; + +export const deliveryStatusCardStyle = (theme: Theme) => css` + display: flex; + flex-direction: column; + gap: 2.4rem; + box-sizing: border-box; + width: 30.4rem; + max-height: 30rem; + padding: 0 4rem 2.4rem 2rem; + overflow: hidden auto; + + &::-webkit-scrollbar { + width: 1rem; + } + + &::-webkit-scrollbar-thumb { + width: 1rem; + height: 10rem; + + background: ${theme.colors.gray6}; + border-radius: 24px; + } + + &::-webkit-scrollbar-track { + margin-bottom: 1rem; + + background: ${theme.colors.white}; + border-radius: 24px; + } +`; + +export const lineConnectorStyle = (theme: Theme) => css` + position: relative; + + &::after { + position: absolute; + top: 1.6rem; + left: 6.5rem; + width: 0.2rem; + height: calc(100% + 0.7rem); + + background-color: ${theme.colors.gray3}; + transform: translateX(-50%); + + content: ''; + } +`; + +export const statusNameStringStyle = (theme: Theme) => css` + ${theme.fonts.kor.bodyBold13}; + color: ${theme.colors.gray7}; +`; + +export const firstStringStyle = (theme: Theme) => css` + color: ${theme.colors.brandPrimary}; +`; +export const stepMessageStringStyle = (theme: Theme) => css` + ${theme.fonts.kor.captionMedium10}; + color: ${theme.colors.gray7}; +`; + +export const dateStringStyle = (theme: Theme) => css` + ${theme.fonts.both.captionBothMedium10}; + color: ${theme.colors.gray4}; +`; + +export const titleDateStringStyle = (theme: Theme) => css` + width: 4.3rem; + height: 2.4rem; + + color: ${theme.colors.gray4}; + text-align: right; + ${theme.fonts.both.captionBothMedium10}; +`; + +export const frameContentStyle = css` + display: flex; + flex-direction: column; + min-width: 22.3rem; +`; + +export const frameTitleStyle = css` + display: flex; + gap: 1.4rem; +`; + +export const frameDetailStyle = css` + display: flex; + flex-direction: column; + gap: 0.8rem; + padding-left: 8.7rem; +`; + +export const frameDetailContentStyle = css` + display: flex; + flex-direction: column; + gap: 0.4rem; +`; diff --git a/src/components/orderDetail/orderStatus/orderStatus.tsx b/src/components/orderDetail/orderStatus/orderStatus.tsx new file mode 100644 index 0000000..184b4dc --- /dev/null +++ b/src/components/orderDetail/orderStatus/orderStatus.tsx @@ -0,0 +1,25 @@ +import { LAST_STATUS } from '@constants/orderStatusList'; +import DeliveryStatusComponent from './deliveryStauts/deliveryStatus'; +import DetailStatusComponent from './detailStatus/detailStatus'; +import { + orderStatusComponentStyle, + componentsStyle, + headerStringStyle, + contentStringStyle, + headerComponentStyle, +} from './orderStatusStyle'; + +const OrderStatusComponent = () => ( +
+
+

상태: {LAST_STATUS.name}

+

{LAST_STATUS.description}

+
+
+ + +
+
+); + +export default OrderStatusComponent; diff --git a/src/components/orderDetail/orderStatus/orderStatusStyle.ts b/src/components/orderDetail/orderStatus/orderStatusStyle.ts new file mode 100644 index 0000000..7c7e77a --- /dev/null +++ b/src/components/orderDetail/orderStatus/orderStatusStyle.ts @@ -0,0 +1,33 @@ +import { Theme, css } from '@emotion/react'; + +export const orderStatusComponentStyle = (theme: Theme) => css` + display: flex; + flex-direction: column; + gap: 1.6rem; + align-items: flex-start; + width: 91.1rem; + height: 47.1rem; + padding: 2.4rem; + + background-color: ${theme.colors.white}; +`; + +export const headerComponentStyle = css` + display: flex; + flex-direction: column; + gap: 1.3rem; +`; + +export const componentsStyle = css` + display: flex; + gap: 2.1rem; + width: 86.3rem; +`; + +export const headerStringStyle = (theme: Theme) => css` + ${theme.fonts.kor.titleBold20}; +`; + +export const contentStringStyle = (theme: Theme) => css` + ${theme.fonts.kor.bodyMedium14}; +`; diff --git a/src/components/orderDetail/productRecommend/productRecommend.tsx b/src/components/orderDetail/productRecommend/productRecommend.tsx new file mode 100644 index 0000000..655bb0c --- /dev/null +++ b/src/components/orderDetail/productRecommend/productRecommend.tsx @@ -0,0 +1,59 @@ +import { BtnLeftDefault, BtnRightDefault } from '@assets/icons'; +import ProductCard from '@components/product/ProductCard'; +import PRODUCT_RECOMMEND_DATA from '@constants/productRecommend'; +import { useRef } from 'react'; + +import { + productRecommandContainer, + productListContainer, + headerStringStyle, + btnLeftStyle, + btnRightStyle, + scrollContainerStyle, +} from './productRecommendStyle'; + +const ProductRecommendComponent = () => { + const products = PRODUCT_RECOMMEND_DATA; + + const scrollContainerRef = useRef(null); + const handleScroll = (direction: 'prev' | 'next'): void => { + if (!scrollContainerRef.current) return; + + const { scrollLeft, clientWidth } = scrollContainerRef.current; + const movedWidth = clientWidth * (3 / 5); + + scrollContainerRef.current.scrollTo({ + left: direction === 'prev' ? scrollLeft - movedWidth : scrollLeft + movedWidth, + }); + }; + + return ( +
+
+

다른 상품 추천

+
+
+ handleScroll('prev')} /> +
+ {products.map((product) => ( + + ))} +
+ handleScroll('next')} /> +
+
+ ); +}; + +export default ProductRecommendComponent; diff --git a/src/components/orderDetail/productRecommend/productRecommendStyle.ts b/src/components/orderDetail/productRecommend/productRecommendStyle.ts new file mode 100644 index 0000000..7f6fdd1 --- /dev/null +++ b/src/components/orderDetail/productRecommend/productRecommendStyle.ts @@ -0,0 +1,58 @@ +import { Theme, css } from '@emotion/react'; + +export const productRecommandContainer = (theme: Theme) => css` + display: flex; + flex-direction: column; + gap: 0.4rem; + width: 91.1rem; + padding: 0.8rem 2.4rem 6.8rem; + + background-color: ${theme.colors.white}; +`; + +export const headerStringStyle = (theme: Theme) => css` + display: flex; + align-items: center; + ${theme.fonts.kor.titleBold20}; + height: 4.6rem; +`; + +export const productListContainer = css` + position: relative; + display: flex; + justify-content: space-between; + width: 86.5rem; + height: 33rem; + padding: 0 1.3rem; + overflow: hidden; +`; + +export const scrollContainerStyle = css` + display: flex; + gap: 1.3rem; + overflow: hidden; + scroll-behavior: smooth; +`; + +export const navigationButtonStyle = css` + position: absolute; + top: 48%; + z-index: 2; + + transform: translateY(-50%); + cursor: pointer; + + &:hover rect { + fill-opacity: 0.6; + } +`; + +export const btnLeftStyle = css` + ${navigationButtonStyle} + left: 0; +`; + +export const btnRightStyle = css` + ${navigationButtonStyle} + right: 0; +`; diff --git a/src/components/orderDetail/serviceIntroduce/serviceIntroduce.tsx b/src/components/orderDetail/serviceIntroduce/serviceIntroduce.tsx new file mode 100644 index 0000000..2aa889e --- /dev/null +++ b/src/components/orderDetail/serviceIntroduce/serviceIntroduce.tsx @@ -0,0 +1,43 @@ +import { DividerGray, IcArrowrightGray12 } from '@assets/icons'; +import SERVICE_INTRODUCE_MSG from '@constants/serviceIntroduceMsg'; + +import { + serviceIntroduceContainer, + hedarTitleStyle, + contentTitleStyle, + contentStyle, + sectionContainerStyle, + contentContainerStyle, + contentBoxStyle, + contentLineStyle, + diverStyle, +} from './serviceIntroduceStyle'; + +const ServiceIntroduce = () => { + const contentData = SERVICE_INTRODUCE_MSG; + return ( +
+
+

서비스 안내

+
+
+ {contentData.map((content, index) => ( +
+
+ {content.icon} +
+

{content.title}

+
+

{content.content}

+
+
+
+ {index < contentData.length - 1 && } +
+ ))} +
+
+ ); +}; + +export default ServiceIntroduce; diff --git a/src/components/orderDetail/serviceIntroduce/serviceIntroduceStyle.ts b/src/components/orderDetail/serviceIntroduce/serviceIntroduceStyle.ts new file mode 100644 index 0000000..0c5d265 --- /dev/null +++ b/src/components/orderDetail/serviceIntroduce/serviceIntroduceStyle.ts @@ -0,0 +1,59 @@ +import { Theme, css } from '@emotion/react'; + +export const serviceIntroduceContainer = (theme: Theme) => css` + display: flex; + flex-direction: column; + gap: 1.6rem; + width: 91.1rem; + height: 28rem; + padding: 2.4rem 1.6rem; + + background-color: ${theme.colors.white}; +`; + +export const hedarTitleStyle = (theme: Theme) => css` + ${theme.fonts.kor.bodyBold14}; + color: ${theme.colors.gray9}; +`; + +export const sectionContainerStyle = css` + display: flex; + flex-direction: column; + gap: 1.6rem; + width: 87.9rem; + height: 19.6rem; +`; + +export const contentTitleStyle = (theme: Theme) => css` + ${theme.fonts.kor.bodyBold14}; + color: ${theme.colors.gray10}; +`; + +export const contentStyle = (theme: Theme) => css` + ${theme.fonts.kor.captionMedium12}; + color: ${theme.colors.gray7}; +`; + +export const contentContainerStyle = css` + display: flex; + gap: 0.8rem; + align-items: center; + height: 4.4rem; +`; + +export const contentBoxStyle = css` + display: flex; + flex-direction: column; + gap: 0.8rem; + width: 100%; +`; + +export const contentLineStyle = css` + display: flex; + align-items: center; + justify-content: space-between; +`; + +export const diverStyle = css` + margin-top: 1.6rem; +`; diff --git a/src/components/orderDetail/sideBar/menuBar/menuBar.tsx b/src/components/orderDetail/sideBar/menuBar/menuBar.tsx new file mode 100644 index 0000000..6482df6 --- /dev/null +++ b/src/components/orderDetail/sideBar/menuBar/menuBar.tsx @@ -0,0 +1,40 @@ +import { useState } from 'react'; +import { menuComponentStyle, menuTitleStyle, inviteStyle, selectedMenuItemStyle } from './menuBarStyle'; +import { DividerSmall } from '@assets/icons'; +import MENU_ITEMS from '@components/constants/menuItems'; + +export const MenuBarComponent = () => { + const [currentPage, setCurrentPage] = useState('주문 & 배송'); + + return ( +
+

계정

+
    + {MENU_ITEMS.map((item) => { + if (item === '친구 초대') { + return ( + + ); + } + return ( +
  • setCurrentPage(item)}> + {item} +
  • + ); + })} +
+
+ ); +}; diff --git a/src/components/orderDetail/sideBar/menuBar/menuBarStyle.ts b/src/components/orderDetail/sideBar/menuBar/menuBarStyle.ts new file mode 100644 index 0000000..4bdf895 --- /dev/null +++ b/src/components/orderDetail/sideBar/menuBar/menuBarStyle.ts @@ -0,0 +1,66 @@ +import { Theme, css } from '@emotion/react'; + +export const menuComponentStyle = (theme: Theme) => css` + display: inline-flex; + flex-direction: column; + gap: 1.4rem; + align-items: center; + justify-content: center; + margin-bottom: 1.8rem; + padding: 1.4rem 0 1.2rem; + + background-color: ${theme.colors.white}; +`; + +export const menuTitleStyle = (theme: Theme) => css` + width: 24.8rem; + ${theme.fonts.kor.bodyBold16}; +`; + +export const inviteStyle = (theme: Theme) => css` + display: flex; + align-items: center; + width: 28rem; + height: 3.4rem; + padding-left: 1.5rem; + + color: ${theme.colors.gray10}; + font-size: 1rem; + + cursor: pointer; + + ${theme.fonts.kor.bodyMedium14}; +`; + +export const menuItemStyle = (theme: Theme) => css` + display: flex; + align-items: center; + width: 28rem; + height: 3.4rem; + padding-left: 1.5rem; + + color: ${theme.colors.gray10}; + font-size: 1rem; + + cursor: pointer; + + ${theme.fonts.kor.bodyMedium14}; +`; + +export const selectedMenuItemStyle = (theme: Theme) => css` + position: relative; + + background-color: ${theme.colors.gray2}; + + ::before { + position: absolute; + left: 0; + display: block; + width: 0.4rem; + height: 100%; + + background-color: ${theme.colors.warningPrimary}; + + content: ''; + } +`; diff --git a/src/components/orderDetail/sideBar/qrCode/qrCode.tsx b/src/components/orderDetail/sideBar/qrCode/qrCode.tsx new file mode 100644 index 0000000..2924a52 --- /dev/null +++ b/src/components/orderDetail/sideBar/qrCode/qrCode.tsx @@ -0,0 +1,15 @@ +import qrCodeImg from '@assets/images/img_appapproachqr.png'; +import { qrCodeComponentStyle, qrTitleStyle, qrCodeTextStyle, qrContentStyle, qrImageStyle } from './qrCodeStyle'; + +export const QrCodeComponent = () => { + return ( +
+
+ AliExpress 모바일 앱 + 언제 어디서든 검색해보세요! + qr코드 이미지 + 스캔 또는 다운로드 클릭 +
+
+ ); +}; diff --git a/src/components/orderDetail/sideBar/qrCode/qrCodeStyle.ts b/src/components/orderDetail/sideBar/qrCode/qrCodeStyle.ts new file mode 100644 index 0000000..4d0da92 --- /dev/null +++ b/src/components/orderDetail/sideBar/qrCode/qrCodeStyle.ts @@ -0,0 +1,33 @@ +import { Theme, css } from '@emotion/react'; + +export const qrCodeComponentStyle = (theme: Theme) => css` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 28rem; + height: 22.6rem; + + background-color: ${theme.colors.white}; +`; + +export const qrCodeTextStyle = css` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +`; + +export const qrTitleStyle = (theme: Theme) => css` + ${theme.fonts.eng.bodyBold16}; + margin-bottom: 0.6rem; +`; + +export const qrContentStyle = (theme: Theme) => css` + ${theme.fonts.kor.captionMedium12}; + color: ${theme.colors.gray6}; +`; + +export const qrImageStyle = css` + margin: 0.8rem 0; +`; diff --git a/src/components/orderDetail/sideBar/sideBar.tsx b/src/components/orderDetail/sideBar/sideBar.tsx new file mode 100644 index 0000000..4c98583 --- /dev/null +++ b/src/components/orderDetail/sideBar/sideBar.tsx @@ -0,0 +1,30 @@ +import { MenuBarComponent } from './menuBar/menuBar'; +import { QrCodeComponent } from './qrCode/qrCode'; +import { sideBarComponentStyle, pathInfoStyle } from './sideBarStyle'; + +const pathRoutes = ['주문', '주문상세']; + +const PathInfoComponent = () => { + return ( + <> + {pathRoutes.map((item, index) => ( +
+  > {item} +
+ ))} + + ); +}; + +export const SideBarComponent = () => { + return ( +
+
+ + +
+ + +
+ ); +}; diff --git a/src/components/orderDetail/sideBar/sideBarStyle.ts b/src/components/orderDetail/sideBar/sideBarStyle.ts new file mode 100644 index 0000000..d78739c --- /dev/null +++ b/src/components/orderDetail/sideBar/sideBarStyle.ts @@ -0,0 +1,27 @@ +import { Theme, css } from '@emotion/react'; + +export const sideBarComponentStyle = (theme: Theme) => css` + display: flex; + flex-direction: column; + align-items: center; + align-items: flex-end; + width: 28rem; + height: 133.4rem; + padding-right: 1.1rem; + + background-color: ${theme.colors.gray2}; +`; + +export const pathInfoStyle = (theme: Theme) => css` + display: flex; + align-items: center; + width: 28rem; + height: 4.8rem; + padding-left: 1.2rem; + + .last-item { + color: ${theme.colors.gray10}; + } + color: ${theme.colors.gray6}; + ${theme.fonts.kor.captionRegular12}; +`; diff --git a/src/components/product/ProductCard.tsx b/src/components/product/ProductCard.tsx index 9f808f9..aebd210 100644 --- a/src/components/product/ProductCard.tsx +++ b/src/components/product/ProductCard.tsx @@ -1,42 +1,80 @@ import { IcArrowrightGray12 } from '@assets/icons'; import FreeTag from './FreeTag'; -import { couponBtnStyle, imageContainer, imageStyle, priceContainer, productContainer, productDiscountStyle, productInfoContainer, productInfoWrapper, productNameStyle, productPriceStyle, productWonStyle, tagContainer } from './ProductCardStyle'; +import { + couponBtnStyle, + imageContainer, + imageStyle, + priceContainer, + productContainer, + productDiscountStyle, + productInfoContainer, + productInfoWrapper, + productNameStyle, + productPriceStyle, + productWonStyle, + productWrapper, + tagContainer, +} from './ProductCardStyle'; import StarBtn from '@components/button/starBtn/StarBtn'; interface ProductProps { - image: string; - name: string; - price: number; - discountRate: number; - hasCoupon?: boolean; - rating: number; - reviewCount: number; + image: string; + name: string; + price: number; + discountRate: number; + hasCoupon?: boolean; + rating: number; + reviewCount: number; + width?: string; + hoverLarge?: boolean; } -const ProductCard = ({ image, name, price, discountRate, hasCoupon = false, rating, reviewCount }: ProductProps) => ( -
-
- {name} -
-
-
-

{name}

- -
- - {price.toLocaleString()} - {discountRate}% -
-
-
    -
  • -
  • -
-
- {hasCoupon && } - -
+const ProductCard = ({ + image, + name, + price, + discountRate, + hasCoupon = false, + rating, + reviewCount, + width = '20.2rem', + hoverLarge = true, +}: ProductProps) => ( +
productContainer(theme, width, hoverLarge)}> +
+
+ {name} +
+
+
+ {name} + +
+ + {price.toLocaleString()} + {discountRate}% +
+
+
    +
  • + +
  • +
  • + +
  • +
+
+ {hasCoupon && ( +
couponBtnStyle(theme, hoverLarge)}> + +
+ )} +
+
); -export default ProductCard; \ No newline at end of file +export default ProductCard; diff --git a/src/components/product/ProductCardList.tsx b/src/components/product/ProductCardList.tsx new file mode 100644 index 0000000..7a0262d --- /dev/null +++ b/src/components/product/ProductCardList.tsx @@ -0,0 +1,50 @@ +import useRelatedProducts from '@apis/productPage/product/productQueries'; +import ProductCard from './ProductCard'; +import { + productListContainer, + relatedProductsContainer, + relatedProductsHeaderStyle, + relatedProductsStyle, +} from './ProductCardListStyle'; +import TextBtn from '@components/button/textBtn/TextBtn'; +import { AxiosError } from 'axios'; +import MESSAGE from '@constants/errorMessages'; + +interface ProductCardListProps { + productId: number; +} + +const ProductCardList = ({ productId }: ProductCardListProps) => { + const { data, isError, error } = useRelatedProducts(productId); + + if (isError) { + const axiosError = error as AxiosError<{ error: { message: string } }>; + const errorMessage = axiosError.response?.data?.error?.message || MESSAGE.UNKNOWN_ERROR; + console.error(errorMessage); + } + + return ( +
+
+

연관 상품

+
+
+ {data.map((product) => ( + + ))} +
+ +
+ ); +}; + +export default ProductCardList; diff --git a/src/components/product/ProductCardListStyle.ts b/src/components/product/ProductCardListStyle.ts new file mode 100644 index 0000000..e6ce6fc --- /dev/null +++ b/src/components/product/ProductCardListStyle.ts @@ -0,0 +1,27 @@ +import { Theme, css } from '@emotion/react'; + +export const relatedProductsContainer = css` + display: flex; + flex-direction: column; + align-items: center; + margin-bottom: 2.4rem; +`; + +export const relatedProductsHeaderStyle = css` + display: flex; + align-items: center; + width: 127.7rem; + height: 4.6rem; +`; + +export const relatedProductsStyle = (theme: Theme) => css` + ${theme.fonts.kor.titleBold20}; +`; + +export const productListContainer = css` + display: flex; + flex-wrap: wrap; + gap: 2.4rem 1.3rem; + width: 127.7rem; + margin-bottom: 2.4rem; +`; diff --git a/src/components/product/ProductCardStyle.ts b/src/components/product/ProductCardStyle.ts index f65d5b2..c227fa8 100644 --- a/src/components/product/ProductCardStyle.ts +++ b/src/components/product/ProductCardStyle.ts @@ -1,103 +1,136 @@ import { Theme, css } from '@emotion/react'; -export const productContainer = (theme: Theme) => css` - - position: relative; - display: flex; - flex-direction: column; - gap: 12px; - align-items: center; - width: 20.2rem; - - background-color: ${theme.colors.white}; - border-radius: 12px; - - &:hover { - width: 22.8rem; - padding: 1.3rem; - - box-shadow: 0 6px 12px 0 rgb(0 0 0 / 12%), 0 4px 8px 0 rgb(0 0 0 / 8%), 0 0 4px 0 rgb(0 0 0 / 8%); - transform-origin: top center; /* 상단의 시작 지점을 고정 */ - border-radius: 16px; - } +export const productContainer = (theme: Theme, width: string, hoverLarge: boolean) => css` + position: relative; + width: ${width}; + height: fit-content; + + background-color: ${theme.colors.white}; + border-radius: ${hoverLarge ? '12px' : '16px'}; + + &:hover { + z-index: 1; + + box-shadow: + 0 6px 12px 0 rgb(0 0 0 / 12%), + 0 4px 8px 0 rgb(0 0 0 / 8%), + 0 0 4px 0 rgb(0 0 0 / 8%); + ${hoverLarge + ? ` + transform: scale(1.13, 1.065); + transform-origin: top center; /* 상단의 시작 지점을 고정 */ + border-radius: 16px; + ` + : ` + padding-bottom: 1.6rem; + `} + } + + &:hover > * { + ${hoverLarge + ? ` + transform: scale(0.87, 0.935); /* 내부 요소 축소 */ + ` + : ''} + } `; -export const imageContainer = css` - position: relative; - width: 20.2rem; - height: 20.2rem; - overflow: hidden; +export const productWrapper = (theme: Theme) => css` + display: flex; + flex-direction: column; + gap: 1.2rem; + align-items: center; - border-radius: 12px; + /* width: ; */ + + background-color: ${theme.colors.white}; + border-radius: 12px; +`; + +export const imageContainer = (width: string) => css` + position: relative; + width: ${width}; + height: ${width}; + overflow: hidden; + + border-radius: 12px; `; export const imageStyle = css` - width: 100%; - height: 100%; - object-fit: cover; + width: 100%; + height: 100%; + object-fit: cover; `; -export const productInfoContainer = css` - display: flex; - flex-direction: column; - gap: 8px; +export const productInfoContainer = (width: string, hoverLarge: boolean) => css` + display: flex; + flex-direction: column; + gap: 0.8rem; + ${!hoverLarge && `padding: 0 1.6rem;`} + width: ${width}; `; export const productInfoWrapper = css` - display: flex; - flex-direction: column; - gap: 4px; + display: flex; + flex-direction: column; + gap: 0.4rem; `; export const productNameStyle = (theme: Theme) => css` - ${theme.fonts.eng.bodyMedium13}; - color: ${theme.colors.gray9}; + overflow: hidden; + + ${theme.fonts.eng.bodyMedium13}; + color: ${theme.colors.gray9}; + white-space: nowrap; + text-overflow: ellipsis; `; export const priceContainer = css` - display: flex; + display: flex; `; export const productWonStyle = (theme: Theme) => css` - ${theme.fonts.eng.captionBold10}; - display: flex; - align-items: end; + ${theme.fonts.eng.captionBold10}; + display: flex; + align-items: end; - color: ${theme.colors.gray9}; + color: ${theme.colors.gray9}; `; export const productPriceStyle = (theme: Theme) => css` - ${theme.fonts.eng.titleBold18}; - color: ${theme.colors.gray9}; + ${theme.fonts.eng.titleBold18}; + color: ${theme.colors.gray9}; `; export const productDiscountStyle = (theme: Theme) => css` - ${theme.fonts.eng.titleBold18}; - margin-left: 0.5rem; + ${theme.fonts.eng.titleBold18}; + margin-left: 0.5rem; - color: ${theme.colors.brandPrimary}; + color: ${theme.colors.brandPrimary}; `; export const tagContainer = css` - display: flex; - gap: 10px; + display: flex; + gap: 1rem; `; -export const couponBtnStyle = (theme: Theme) => css` - display: flex; - gap: 8px; - align-items: center; - justify-content: space-between; - width: 100%; - height: 2.9rem; - padding: 0.8rem 1.2rem; - - - color: ${theme.colors.gray9}; - ${theme.fonts.kor.captionBold11}; - - - background-color: ${theme.colors.gray2}; - border: 0; - border-radius: 12px; -` \ No newline at end of file +export const couponBtnStyle = (theme: Theme, hoverLarge: boolean) => css` + button { + display: flex; + gap: 0.8rem; + align-items: center; + justify-content: space-between; + width: 100%; + height: 2.9rem; + padding: 0.8rem 1.2rem; + + color: ${theme.colors.gray9}; + ${theme.fonts.kor.captionBold11}; + + background-color: ${theme.colors.gray2}; + border: 0; + border-radius: 12px; + } + width: 100%; + ${!hoverLarge && `padding: 0 1.6rem;`} +`; diff --git a/src/components/product/RecommandedList.tsx b/src/components/product/RecommandedList.tsx new file mode 100644 index 0000000..51edbe4 --- /dev/null +++ b/src/components/product/RecommandedList.tsx @@ -0,0 +1,30 @@ +import { IcArrowrightGray12 } from '@assets/icons'; +import ProductCard from '@components/product/ProductCard'; +import { ProductWrapper, RecommandedListContainer, TitleLayout } from '@components/product/RecommandedListStyle'; +import recommandedProductData from '@constants/recommandedProductData'; +import { DividerLarge } from '@assets/icons'; + +const RecommandedList = () => ( +
+ + 상점 내 추천 상품 + <IcArrowrightGray12 /> + +
+ {recommandedProductData.map((product) => ( + + ))} +
+ +
+); + +export default RecommandedList; diff --git a/src/components/product/RecommandedListStyle.ts b/src/components/product/RecommandedListStyle.ts new file mode 100644 index 0000000..93b8704 --- /dev/null +++ b/src/components/product/RecommandedListStyle.ts @@ -0,0 +1,28 @@ +import { css, Theme } from '@emotion/react'; + +export const RecommandedListContainer = css` + display: flex; + flex-direction: column; + width: 127.7rem; + margin-bottom: 2.4rem; +`; + +export const TitleLayout = (theme: Theme) => css` + display: flex; + gap: 0.4rem; + align-items: center; + width: 100%; + height: 1.6rem; + margin: 1.25rem 0; + + color: ${theme.colors.gray9}; + ${theme.fonts.kor.bodyBold16} +`; + +export const ProductWrapper = css` + display: flex; + gap: 1.3rem; + width: 100%; + height: 30rem; + margin-bottom: 4.4rem; +`; diff --git a/src/components/productOrderBox/ProductOrderBox.tsx b/src/components/productOrderBox/ProductOrderBox.tsx new file mode 100644 index 0000000..2e829a5 --- /dev/null +++ b/src/components/productOrderBox/ProductOrderBox.tsx @@ -0,0 +1,19 @@ +import OrderBox from '@components/orderBox/OrderBox'; +import ProductInfo from '@components/ProductInfo/ProductInfo'; +import { css } from '@emotion/react'; + +const ProductOrderBoxContainer = css` + display: flex; + gap: 1.8rem; + width: 127.7rem; + margin: 2.2rem 0 2.4rem; +`; + +const ProductOrderBox = () => ( +
+ + +
+); + +export default ProductOrderBox; diff --git a/src/components/productPage/review/review/Card.tsx b/src/components/productPage/review/review/Card.tsx new file mode 100644 index 0000000..da41f48 --- /dev/null +++ b/src/components/productPage/review/review/Card.tsx @@ -0,0 +1,87 @@ +import { IcShieldWhite12, IcMeatballLightgray20 } from '@assets/icons/index'; +import userImg from '@assets/images/userImg'; +import ReviewBtn from '@components/button/recommendBtn/reviewBtn'; +import { + cardLayout, + relativeStyle, + nameStyle, + dateStyle, + colorStyle, + sizeStyle, + circleStyle, + reviewStyle, + infoWrapper, + monthChipStyle, + reportBtnStyle, + cardTitleStyle, + nameRightStyle, + imgWrapper, + reviewBtnWrapper, +} from '@components/productPage/review/review/CardStyle'; +import RenderStar from '@components/productPage/review/review/RenderStar'; +import { Review } from '@constants/userReview'; +import { useState } from 'react'; + +interface CardProps { + review: Review; + isOriginal: boolean; +} + +const Card = ({ review, isOriginal }: CardProps) => { + const [isHovered, setIsHovered] = useState(false); + + return ( +
+ {/* 작성자 프로필 이미지 */} +
+ {userImg.map((image, index) => + index === review.reviewId % userImg.length ? ( + {`profile-${index}`} + ) : null, + )}{' '} + +
+ {/* 리뷰 상세 내용 */} +
+ {/* 작성자 정보, 신고기능 */} +
+ {review.username} +
+ 24.08.22 + setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} /> + {isHovered && ( + + )} +
+
+ + {/* 별점 렌더링 */} + + 색상: 검정 + + {/* 한달사용여부, 리뷰상세내용 */} +
+ {review.isMonth === true &&
한달사용리뷰
} + {isOriginal ? review.contentOriginal : review.contentKorean} +
+ + {/* 리뷰 이미지 데이터 받아오면 한개로 줄일예정 */} +
+ {`${review.username}님의 + {`${review.username}님의 +
+ + {/* 좋아요 버튼 */} +
+ + + +
+
+
+ ); +}; + +export default Card; diff --git a/src/components/productPage/review/review/CardList.tsx b/src/components/productPage/review/review/CardList.tsx new file mode 100644 index 0000000..9c227c3 --- /dev/null +++ b/src/components/productPage/review/review/CardList.tsx @@ -0,0 +1,36 @@ +import Card from '@components/productPage/review/review/Card'; +import { reviewContainer, reviewLayout } from '@components/productPage/review/review/CardListStyle'; +import { data } from '@constants/userReview'; + +interface CardListProps { + activeTab: string; + isOriginal: boolean; +} + +const CardList = ({ activeTab, isOriginal }: CardListProps) => { + const { goodReviews, badReviews } = data; + + return ( +
+ {/* 긍정 리뷰 */} + {activeTab !== 'negative' && ( +
+ {goodReviews.map((review) => ( + + ))} +
+ )} + + {/* 비판 리뷰 */} + {activeTab !== 'positive' && ( +
+ {badReviews.map((review) => ( + + ))} +
+ )} +
+ ); +}; + +export default CardList; diff --git a/src/components/productPage/review/review/CardListStyle.ts b/src/components/productPage/review/review/CardListStyle.ts new file mode 100644 index 0000000..e3a167f --- /dev/null +++ b/src/components/productPage/review/review/CardListStyle.ts @@ -0,0 +1,16 @@ +import { css } from '@emotion/react'; + +export const reviewContainer = css` + display: flex; + flex-wrap: wrap; + gap: 2rem; + width: 96rem; + height: 62rem; + overflow: hidden; +`; + +export const reviewLayout = (activeTab: string) => css` + display: flex; + flex-flow: ${activeTab === 'total' ? 'column' : 'row'} wrap; + gap: 2rem; +`; diff --git a/src/components/productPage/review/review/CardStyle.ts b/src/components/productPage/review/review/CardStyle.ts new file mode 100644 index 0000000..50c8927 --- /dev/null +++ b/src/components/productPage/review/review/CardStyle.ts @@ -0,0 +1,133 @@ +import { Theme, css } from '@emotion/react'; + +export const cardLayout = (theme: Theme) => css` + display: flex; + gap: 1.2rem; + width: 47rem; + height: 29.4rem; + padding: 1.6rem 1.2rem; + + background-color: ${theme.colors.white}; + border: 1px solid ${theme.colors.gray3}; + border-radius: 8px; +`; + +export const relativeStyle = css` + position: relative; +`; + +export const sizeStyle = css` + width: 4rem; + height: 4rem; +`; + +export const circleStyle = (theme: Theme) => css` + position: absolute; + top: 1.2rem; + left: 4rem; + z-index: 10; + display: flex; + align-items: center; + justify-content: center; + width: 2rem; + height: 2rem; + padding: 0.4rem; + + background: ${theme.colors.brandPrimary}; + border-radius: 99px; +`; + +export const infoWrapper = css` + display: flex; + flex-direction: column; +`; + +export const cardTitleStyle = css` + display: flex; + align-items: center; + justify-content: space-between; +`; + +export const nameRightStyle = css` + display: flex; + gap: 1.2rem; + align-items: center; +`; + +export const nameStyle = (theme: Theme) => css` + width: 31.2rem; + height: 2rem; + + color: ${theme.colors.gray8}; + ${theme.fonts.eng.titleBold16} +`; + +export const dateStyle = (theme: Theme) => css` + color: ${theme.colors.gray5}; + ${theme.fonts.eng.captionMedium10} +`; + +export const colorStyle = (theme: Theme) => css` + margin-top: 0.8rem; + + color: ${theme.colors.gray6}; + ${theme.fonts.eng.captionMedium12}; +`; +export const reviewStyle = (theme: Theme) => css` + display: -webkit-box; + width: 39rem; + height: 3.4rem; + margin-top: 1.2rem; + overflow: hidden; + + color: ${theme.colors.gray9}; + text-overflow: ellipsis; + word-break: break-word; + ${theme.fonts.eng.captionMedium12}; + + /* 말줄임표 처리 */ + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; +`; + +export const monthChipStyle = (theme: Theme) => css` + display: inline-flex; + align-items: center; + justify-content: center; + width: 7.5rem; + height: 2rem; + margin-right: 0.3rem; + padding: 0.2rem 0.4rem; + + color: ${theme.colors.brandPrimary}; + + ${theme.fonts.eng.captionMedium12}; + background-color: ${theme.colors.brandBg}; + border-radius: 2px; +`; + +export const imgWrapper = css` + display: flex; + gap: 0.8rem; + padding: 1.6rem 0; +`; + +export const reviewBtnWrapper = css` + display: flex; + gap: 0.8rem; +`; + +export const reportBtnStyle = (theme: Theme) => css` + position: absolute; + top: 2.4rem; + right: 0; + + width: 4.9rem; + height: 3.8rem; + padding: 1rem 1.4rem; + + background-color: ${theme.colors.white}; + border: 1px solid ${theme.colors.gray3}; + ${theme.fonts.kor.captionMedium10}; + border-radius: 4px; +`; diff --git a/src/components/productPage/review/review/FilterBtn.tsx b/src/components/productPage/review/review/FilterBtn.tsx new file mode 100644 index 0000000..6f6f1a0 --- /dev/null +++ b/src/components/productPage/review/review/FilterBtn.tsx @@ -0,0 +1,31 @@ +import { IcArrowbottomGray12 } from '@assets/icons/index'; +import { + fontStyle, + FilterBtnContainer, + dropDownLayoutStyle, + listStyle, +} from '@components/productPage/review/review/FilterBtnStyle'; +import { useState } from 'react'; + +const FilterBtn = () => { + const [isHovered, setIsHovered] = useState(false); + + return ( +
+
setIsHovered(true)} onMouseLeave={() => setIsHovered(false)}> +

최근 작성된 리뷰

+ +
+ + {isHovered && ( +
+

최근 작성된 리뷰

+

추천 리뷰

+

재구매 리뷰

+
+ )} +
+ ); +}; + +export default FilterBtn; diff --git a/src/components/productPage/review/review/FilterBtnStyle.ts b/src/components/productPage/review/review/FilterBtnStyle.ts new file mode 100644 index 0000000..8bee49b --- /dev/null +++ b/src/components/productPage/review/review/FilterBtnStyle.ts @@ -0,0 +1,43 @@ +import { Theme, css } from '@emotion/react'; + +export const FilterBtnContainer = (theme: Theme) => css` + position: relative; + display: flex; + align-items: center; + justify-content: space-between; + width: 15.4rem; + height: 3rem; + padding: 0.8rem 1.2rem; + + background-color: ${theme.colors.gray2}; + border-radius: 6px; +`; + +export const fontStyle = (theme: Theme) => css` + ${theme.fonts.kor.captionMedium12} + z-index: 5; +`; + +export const dropDownLayoutStyle = (theme: Theme) => css` + position: absolute; + top: 5rem; + width: 15.4rem; + + background-color: ${theme.colors.gray1}; + border: 1px solid ${theme.colors.gray3}; + border-radius: 6px; +`; + +export const listStyle = (theme: Theme) => css` + display: flex; + align-items: center; + justify-content: space-between; + height: 3rem; + padding: 0.8rem 1.2rem; + + border-bottom: 1px solid ${theme.colors.gray3}; + + &:nth-of-type(3) { + border-bottom: none; + } +`; diff --git a/src/components/productPage/review/review/OriginalTextBtn.tsx b/src/components/productPage/review/review/OriginalTextBtn.tsx new file mode 100644 index 0000000..7d42ab4 --- /dev/null +++ b/src/components/productPage/review/review/OriginalTextBtn.tsx @@ -0,0 +1,21 @@ +import { Theme, css } from '@emotion/react'; + +export const btnStyle = (theme: Theme) => css` + ${theme.fonts.kor.captionMedium12} + color: ${theme.colors.notificationPrimary}; + + background-color: transparent; + border: none; +`; + +interface OriginalTextBtnProps { + onClick: () => void; +} + +const OriginalTextBtn = ({ onClick }: OriginalTextBtnProps) => ( + +); + +export default OriginalTextBtn; diff --git a/src/components/productPage/review/review/RenderStar.tsx b/src/components/productPage/review/review/RenderStar.tsx new file mode 100644 index 0000000..5717b15 --- /dev/null +++ b/src/components/productPage/review/review/RenderStar.tsx @@ -0,0 +1,28 @@ +import { IcCometReviewStarFill18, IcCometReviewStarBlank18 } from '@assets/icons/index'; +import { css } from '@emotion/react'; + +interface RenderStarProps { + rating: number; +} + +export const starBox = css` + display: flex; +`; + +const RenderStar = ({ rating }: RenderStarProps) => ( +
+ {Array.from({ length: 5 }, (_, index) => + index < rating ? ( + + + + ) : ( + + + + ), + )} +
+); + +export default RenderStar; diff --git a/src/components/productPage/review/review/ReviewPage.tsx b/src/components/productPage/review/review/ReviewPage.tsx new file mode 100644 index 0000000..510b359 --- /dev/null +++ b/src/components/productPage/review/review/ReviewPage.tsx @@ -0,0 +1,36 @@ +import { DividerMedium } from '@assets/icons/index'; +import TextBtn from '@components/button/textBtn/TextBtn'; +import FilterBtn from '@components/productPage/review/review/FilterBtn'; +import Tab from '@components/productPage/review/review/Tab'; +import OriginalTextBtn from '@components/productPage/review/review/OriginalTextBtn'; +import { reviewContainer, flexStyle, commonBtnStyle } from '@components/productPage/review/review/ReviewPageStyle'; +import { reviewNum } from '@constants/userReview'; +import { useState } from 'react'; + +const ReviewPage = () => { + const [isOriginal, setIsOriginal] = useState(false); + + const handleShowOriginalText = () => { + setIsOriginal((prev) => !prev); + }; + return ( +
+
+ + +
+ +
+ +
+ +
+ ); +}; + +export default ReviewPage; diff --git a/src/components/productPage/review/review/ReviewPageStyle.ts b/src/components/productPage/review/review/ReviewPageStyle.ts new file mode 100644 index 0000000..8cadec3 --- /dev/null +++ b/src/components/productPage/review/review/ReviewPageStyle.ts @@ -0,0 +1,21 @@ +import { css } from '@emotion/react'; + +export const reviewContainer = css` + display: flex; + flex-direction: column; +`; + +export const flexStyle = css` + display: flex; + gap: 1.2rem; + align-items: center; + padding: 1.4rem 0; +`; + +export const commonBtnStyle = css` + display: flex; + justify-content: center; + width: 96rem; + height: 3.4rem; + margin: 1.3rem 0 2.4rem; +`; diff --git a/src/components/productPage/review/review/ReviewTypeLabel.tsx b/src/components/productPage/review/review/ReviewTypeLabel.tsx new file mode 100644 index 0000000..ba13e8b --- /dev/null +++ b/src/components/productPage/review/review/ReviewTypeLabel.tsx @@ -0,0 +1,21 @@ +import { + LabelWrapper, + fontBox, + fontEnStyle, + fontKoStyle, +} from '@components/productPage/review/review/ReviewTypeLabelStyle'; + +const ReviewTypeLabel = () => ( +
+
+

Best

+

긍정 리뷰

+
+
+

Best

+

비판 리뷰

+
+
+); + +export default ReviewTypeLabel; diff --git a/src/components/productPage/review/review/ReviewTypeLabelStyle.ts b/src/components/productPage/review/review/ReviewTypeLabelStyle.ts new file mode 100644 index 0000000..0e9f00c --- /dev/null +++ b/src/components/productPage/review/review/ReviewTypeLabelStyle.ts @@ -0,0 +1,23 @@ +import { Theme, css } from '@emotion/react'; + +export const LabelWrapper = css` + display: flex; + gap: 2rem; + margin: 1.4rem 0; +`; + +export const fontBox = css` + display: flex; + gap: 0.4rem; + width: 47rem; +`; + +export const fontEnStyle = (theme: Theme) => css` + color: ${theme.colors.brandPrimary}; + ${theme.fonts.eng.titleBold16} +`; + +export const fontKoStyle = (theme: Theme) => css` + color: ${theme.colors.gray9}; + ${theme.fonts.kor.bodyBold16} +`; diff --git a/src/components/productPage/review/review/Tab.tsx b/src/components/productPage/review/review/Tab.tsx new file mode 100644 index 0000000..f16cd44 --- /dev/null +++ b/src/components/productPage/review/review/Tab.tsx @@ -0,0 +1,41 @@ +import CardList from '@components/productPage/review/review/CardList'; +import ReviewTypeLabel from '@components/productPage/review/review/ReviewTypeLabel'; +import { tabLayout, btnStyle } from '@components/productPage/review/review/TabStyle'; +import { useTheme } from '@emotion/react'; +import { useState } from 'react'; + +interface TabProps { + total: number; + positive: number; + negative: number; + isOriginal: boolean; +} + +const Tab = ({ total, positive, negative, isOriginal }: TabProps) => { + const [activeTab, setActiveTab] = useState('total'); + const theme = useTheme(); + + const handleTab = (e: React.MouseEvent) => { + setActiveTab(e.currentTarget.name); + }; + + return ( +
+
+ + + +
+ {activeTab === 'total' && } + +
+ ); +}; + +export default Tab; diff --git a/src/components/productPage/review/review/TabStyle.ts b/src/components/productPage/review/review/TabStyle.ts new file mode 100644 index 0000000..60d3521 --- /dev/null +++ b/src/components/productPage/review/review/TabStyle.ts @@ -0,0 +1,24 @@ +import { Theme, css } from '@emotion/react'; + +export const tabLayout = (theme: Theme, isActive: boolean) => css` + display: flex; + gap: 0.6rem; + align-items: center; + width: 96rem; + height: 5.6rem; + margin-bottom: ${isActive ? '0rem' : '2rem'}; + padding: 0.4rem; + + background-color: ${theme.colors.gray2}; + border-radius: 12px; +`; + +export const btnStyle = (theme: Theme, isActive: boolean) => css` + width: 31.3rem; + height: 4.8rem; + + ${theme.fonts.kor.bodyBold13} + background-color: ${isActive ? theme.colors.white : theme.colors.gray2}; + border: none; + border-radius: 8px; +`; diff --git a/src/components/productPage/review/reviewTop/Level.tsx b/src/components/productPage/review/reviewTop/Level.tsx new file mode 100644 index 0000000..c72d48b --- /dev/null +++ b/src/components/productPage/review/reviewTop/Level.tsx @@ -0,0 +1,22 @@ +import { Theme, css } from '@emotion/react'; + +export const levelContainer = css` + display: flex; + justify-content: space-between; + width: 27.6rem; +`; + +export const fontStyle = (theme: Theme) => css` + ${theme.fonts.kor.captionMedium10} + color: ${theme.colors.gray6} +`; + +const Level = () => ( +
+

낮음

+

보통

+

높음

+
+); + +export default Level; diff --git a/src/components/productPage/review/reviewTop/Nav.tsx b/src/components/productPage/review/reviewTop/Nav.tsx new file mode 100644 index 0000000..75af99a --- /dev/null +++ b/src/components/productPage/review/reviewTop/Nav.tsx @@ -0,0 +1,22 @@ +import { + NavContainer, + fontLayout, + fontKoStyle, + fontEnStyle, + fontStyle, +} from '@components/productPage/review/reviewTop/NavStyle'; + +const Nav = () => ( +
+
+

리뷰

+

(497)

+
+

상품 정보

+

개요

+

상점

+

연관 상품

+
+); + +export default Nav; diff --git a/src/components/productPage/review/reviewTop/NavStyle.ts b/src/components/productPage/review/reviewTop/NavStyle.ts new file mode 100644 index 0000000..b8e61d9 --- /dev/null +++ b/src/components/productPage/review/reviewTop/NavStyle.ts @@ -0,0 +1,26 @@ +import { Theme, css } from '@emotion/react'; + +export const NavContainer = css` + display: flex; + gap: 1.6rem; + align-items: center; + height: 4.8rem; +`; + +export const fontLayout = css` + display: flex; + gap: 0.2rem; + align-items: center; +`; + +export const fontKoStyle = (theme: Theme) => css` + ${theme.fonts.kor.captionBold12} +`; + +export const fontEnStyle = (theme: Theme) => css` + ${theme.fonts.eng.captionBold12} +`; + +export const fontStyle = (theme: Theme) => css` + ${theme.fonts.kor.captionRegular12} +`; diff --git a/src/components/productPage/review/reviewTop/RatingAverageCard.tsx b/src/components/productPage/review/reviewTop/RatingAverageCard.tsx new file mode 100644 index 0000000..638f87f --- /dev/null +++ b/src/components/productPage/review/reviewTop/RatingAverageCard.tsx @@ -0,0 +1,25 @@ +import { IcCometReviewStarFill18, IcCometReviewStarHalf18 } from '@assets/icons/index'; +import { + cardLayout, + numStyle, + starWrapper, + descStyle, +} from '@components/productPage/review/reviewTop/RatingAverageCardStyle'; + +const RatingAverageCard = () => ( +
+ {/* 카드1 */} +
+

4.8

+
+ {[...Array(4)].map((_, idx) => ( + + ))} + +
+

모든 리뷰는 구매 인증 후 작성되었습니다

+
+
+); + +export default RatingAverageCard; diff --git a/src/components/productPage/review/reviewTop/RatingAverageCardStyle.ts b/src/components/productPage/review/reviewTop/RatingAverageCardStyle.ts new file mode 100644 index 0000000..8f97f1e --- /dev/null +++ b/src/components/productPage/review/reviewTop/RatingAverageCardStyle.ts @@ -0,0 +1,27 @@ +import { Theme, css } from '@emotion/react'; + +export const cardLayout = (theme: Theme) => css` + display: flex; + flex-direction: column; + gap: 0.6rem; + width: 26.6rem; + height: 13.2rem; + padding: 1.4rem 2.4rem; + + background-color: ${theme.colors.gray2}; + border-radius: 8px; +`; + +export const numStyle = (theme: Theme) => css` + ${theme.fonts.eng.headBold48}; +`; + +export const starWrapper = css` + display: flex; + gap: 0.6rem; +`; + +export const descStyle = (theme: Theme) => css` + ${theme.fonts.kor.captionSemibold12} + color: ${theme.colors.successPrimary}; +`; diff --git a/src/components/productPage/review/reviewTop/RatingCountCard.tsx b/src/components/productPage/review/reviewTop/RatingCountCard.tsx new file mode 100644 index 0000000..4258386 --- /dev/null +++ b/src/components/productPage/review/reviewTop/RatingCountCard.tsx @@ -0,0 +1,35 @@ +/* eslint-disable jsx-a11y/alt-text */ +import { frameImg } from '@assets/images/frame'; +import { cardLayout, cardWrapper, fontStyle } from '@components/productPage/review/reviewTop/RatingCountCardStyle'; +import RenderStars from '@components/productPage/review/reviewTop/RenderStar'; + +const RatingCountCard = () => ( +
+
+ + +

454

+
+
+ + +

22

+
+
+ + +

11

+
+
+ + +

2

+
+
+ + +

8

+
+
+); +export default RatingCountCard; diff --git a/src/components/productPage/review/reviewTop/RatingCountCardStyle.ts b/src/components/productPage/review/reviewTop/RatingCountCardStyle.ts new file mode 100644 index 0000000..2ca6ea4 --- /dev/null +++ b/src/components/productPage/review/reviewTop/RatingCountCardStyle.ts @@ -0,0 +1,23 @@ +import { Theme, css } from '@emotion/react'; + +export const cardLayout = (theme: Theme) => css` + display: flex; + flex-direction: column; + gap: 0.6rem; + justify-content: center; + width: 26.6rem; + height: 13.2rem; + padding: 1.4rem 2.4rem; + + background-color: ${theme.colors.gray2}; + border-radius: 8px; +`; + +export const cardWrapper = css` + display: flex; + gap: 0.8rem; +`; + +export const fontStyle = (theme: Theme) => css` + ${theme.fonts.eng.captionMedium10} +`; diff --git a/src/components/productPage/review/reviewTop/RatingPage.tsx b/src/components/productPage/review/reviewTop/RatingPage.tsx new file mode 100644 index 0000000..6f279d9 --- /dev/null +++ b/src/components/productPage/review/reviewTop/RatingPage.tsx @@ -0,0 +1,20 @@ +import RatingAverageCard from '@components/productPage/review/reviewTop/RatingAverageCard'; +import RatingCountCard from '@components/productPage/review/reviewTop/RatingCountCard'; +import RatingServiceCard from '@components/productPage/review/reviewTop/RatingServiceCard'; +import { css } from '@emotion/react'; + +export const AverageContainer = css` + display: flex; + gap: 1.2rem; + width: 96rem; +`; + +const RatingPage = () => ( +
+ + + +
+); + +export default RatingPage; diff --git a/src/components/productPage/review/reviewTop/RatingServiceCard.tsx b/src/components/productPage/review/reviewTop/RatingServiceCard.tsx new file mode 100644 index 0000000..a67e7b6 --- /dev/null +++ b/src/components/productPage/review/reviewTop/RatingServiceCard.tsx @@ -0,0 +1,38 @@ +import { ImgGraph } from '@assets/icons/index'; +import Level from '@components/productPage/review/reviewTop/Level'; +import { + cardLayout, + cardWrapper, + titleStyle, + cardBox, + svgStyle, +} from '@components/productPage/review/reviewTop/RatingServiceCardStyle'; + +const RatingServiceCard = () => ( +
+
+

성능

+
+ + +
+
+ +
+

안전성

+
+ + +
+
+
+

A/S 서비스

+
+ + +
+
+
+); + +export default RatingServiceCard; diff --git a/src/components/productPage/review/reviewTop/RatingServiceCardStyle.ts b/src/components/productPage/review/reviewTop/RatingServiceCardStyle.ts new file mode 100644 index 0000000..3182992 --- /dev/null +++ b/src/components/productPage/review/reviewTop/RatingServiceCardStyle.ts @@ -0,0 +1,37 @@ +import { Theme, css } from '@emotion/react'; + +export const cardLayout = (theme: Theme) => css` + display: flex; + flex-direction: column; + gap: 1.2rem; + justify-content: center; + width: 48rem; + height: 13.2rem; + padding: 2.1rem 2.4rem; + + background-color: ${theme.colors.gray2}; + border-radius: 8px; +`; + +export const cardWrapper = css` + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; +`; + +export const titleStyle = (theme: Theme) => css` + ${theme.fonts.kor.captionBold12} +`; + +export const cardBox = css` + display: flex; + flex-direction: column; + gap: 0.4rem; +`; + +export const svgStyle = ({ width }: { width: number }) => css` + rect:last-of-type { + width: ${width}rem; + } +`; diff --git a/src/components/productPage/review/reviewTop/RenderStar.tsx b/src/components/productPage/review/reviewTop/RenderStar.tsx new file mode 100644 index 0000000..8afb9c8 --- /dev/null +++ b/src/components/productPage/review/reviewTop/RenderStar.tsx @@ -0,0 +1,36 @@ +import { IcCometReviewStarFill12, IcCometReviewStarBlank12 } from '@assets/icons/index'; +import { css } from '@emotion/react'; + +export const starBox = css` + display: flex; + gap: 0; +`; + +type RenderStarsProps = { + rating: number; // 1부터 5까지의 숫자 (별점) +}; + +const RenderStars = ({ rating }: RenderStarsProps) => { + const totalStars = 5; // 별점은 총 5개 + + // rating에 해당하는 채운 별 개수 + const filledCount = Math.floor(rating); + + // 빈 별 개수는 5개에서 채운 별을 뺀 값 + const blankCount = totalStars - filledCount; + + return ( +
+ {/* 채운 별 */} + {[...Array(filledCount)].map((_, idx) => ( + + ))} + {/* 빈 별 */} + {[...Array(blankCount)].map((_, idx) => ( + + ))} +
+ ); +}; + +export default RenderStars; diff --git a/src/components/productPage/review/reviewTop/Title.tsx b/src/components/productPage/review/reviewTop/Title.tsx new file mode 100644 index 0000000..9a2cc58 --- /dev/null +++ b/src/components/productPage/review/reviewTop/Title.tsx @@ -0,0 +1,29 @@ +import { IcHelpGray20 } from '@assets/icons/index'; +import { + relativeStyle, + titleContainer, + fontKoStyle, + fontEnStyle, + alignStyle, + toolStyle, +} from '@components/productPage/review/reviewTop/TitleStyle'; +import { useState } from 'react'; + +const Title = () => { + const [isHovered, setIsHovered] = useState(false); + + const handleMouseEnter = () => setIsHovered(true); + const handleMouseLeave = () => setIsHovered(false); + const TOOL_MSG = '별점 4개 이상은 긍정 리뷰, 별점 3개 이하는 비판 리뷰로 분류했습니다.'; + + return ( +
+

리뷰

+

(497)

+ + {isHovered &&
{TOOL_MSG}
} +
+ ); +}; + +export default Title; diff --git a/src/components/productPage/review/reviewTop/TitleStyle.ts b/src/components/productPage/review/reviewTop/TitleStyle.ts new file mode 100644 index 0000000..1a0cc76 --- /dev/null +++ b/src/components/productPage/review/reviewTop/TitleStyle.ts @@ -0,0 +1,41 @@ +import { Theme, css } from '@emotion/react'; + +export const relativeStyle = css` + position: relative; +`; + +export const titleContainer = css` + display: flex; + align-items: center; + margin: 1.8rem 0 1.5rem; +`; + +export const fontKoStyle = (theme: Theme) => css` + ${theme.fonts.kor.titleBold20} +`; + +export const fontEnStyle = (theme: Theme) => css` + margin: 0 0.4rem 0 0.6rem; + ${theme.fonts.eng.titleBold20} +`; + +export const alignStyle = css` + margin-top: 0.2rem; + + cursor: pointer; +`; +export const toolStyle = (theme: Theme) => css` + position: absolute; + top: 2.8rem; + left: 9.9rem; + display: flex; + align-items: center; + justify-content: center; + padding: 1rem 1.4rem; + + color: ${theme.colors.white}; + + background: ${theme.colors.black}; + ${theme.fonts.eng.captionMedium12} + border-radius: 4px; +`; diff --git a/src/components/productSummary/ProductSummaryStyle.ts b/src/components/productSummary/ProductSummaryStyle.ts new file mode 100644 index 0000000..77e83d7 --- /dev/null +++ b/src/components/productSummary/ProductSummaryStyle.ts @@ -0,0 +1,69 @@ +import { css, Theme } from '@emotion/react'; + +export const ProductSummayContainer = css` + width: 96rem; +`; + +export const productInfoLayoutStyle = css` + display: flex; + flex-direction: column; + gap: 2.4rem; + align-items: center; + width: 96rem; + height: 27rem; +`; + +export const productImgLayoutStyle = (theme: Theme) => css` + display: flex; + flex-direction: column; + gap: 2.4rem; + align-items: center; + width: 96rem; + height: 111.3rem; + margin-bottom: 2.4rem; + padding: 2.4rem 0; + + border-top: 1px solid ${theme.colors.gray3}; + border-bottom: 1px solid ${theme.colors.gray3}; +`; + +export const titleStyle = (theme: Theme) => css` + display: flex; + align-items: center; + width: 100%; + height: 4.6rem; + margin-bottom: 0.8rem; + + ${theme.fonts.kor.titleBold20} +`; + +export const purchaseInquiryLayoutStyle = (theme: Theme) => css` + width: 96rem; + height: 13.4rem; + margin-bottom: 2.4rem; + + border-bottom: 1px solid ${theme.colors.gray3}; +`; + +export const agencyServiceInfoLayoutStyle = (theme: Theme) => css` + width: 96rem; + height: 9.8rem; + margin-bottom: 2.4rem; + + border-bottom: 1px solid ${theme.colors.gray3}; +`; + +export const textBoxStyle = css` + display: flex; + flex-direction: column; + gap: 1.6rem; +`; + +export const textStyle = (theme: Theme) => css` + display: flex; + gap: 0.8rem; + align-items: center; + height: 2rem; + + ${theme.fonts.eng.captionBold11} +`; diff --git a/src/components/productSummary/ProductSummay.tsx b/src/components/productSummary/ProductSummay.tsx new file mode 100644 index 0000000..9256ec4 --- /dev/null +++ b/src/components/productSummary/ProductSummay.tsx @@ -0,0 +1,52 @@ +import detailImg from '@assets/images/img_detail_xl.png'; +import TextBtn from '@components/button/textBtn/TextBtn'; +import InfoTable from '@components/infoTable/InfoTable'; +import { + agencyServiceInfoLayoutStyle, + productImgLayoutStyle, + productInfoLayoutStyle, + purchaseInquiryLayoutStyle, + titleStyle, + textStyle, + textBoxStyle, +} from '@components/productSummary/ProductSummaryStyle'; +import { IcAskBlack20, IcHandshakeGray20 } from '@assets/icons'; + +const ProductSummay = () => ( +
+
+ + +
+
+
+

개요

+ 디테일 상품 이미지 +
+ +
+
+

구매 문의 (2)

+
+

+ + 제품의 총 전력이 67W인지 궁금합니다. +

+

+ + 제품의 총 전력이 67W인지 궁금합니다. +

+
+
+
+

구매대행 서비스 안내

+

+ + 해당 제품은 구매대행을 통하여 유통되는 제품입니다.「전기용품 및 생활용품 안전관리법」에 따라 안전관리대상 + 제품으로 분류됩니다. +

+
+
+); + +export default ProductSummay; diff --git a/src/components/recommandedProducts/RecommandedBox.tsx b/src/components/recommandedProducts/RecommandedBox.tsx new file mode 100644 index 0000000..2184e2a --- /dev/null +++ b/src/components/recommandedProducts/RecommandedBox.tsx @@ -0,0 +1,29 @@ +import RecommanedBtn from '@components/button/recommendBtn/RecommanedBtn'; +import ContactBtn from '@components/button/contactBtn/ContactBtn'; +import { + contentLayout, + flexBoxStyle, + lineStyle, + recommandedBoxContainer, + subTextBoxStyle, + subTextStyle, + titleTextStyle, +} from '@components/recommandedProducts/RecommandedBoxStyle'; + +const RecommandedBox = () => ( +
+
+
+

Toocki Flagship Direct Store

+
+
+

98.3%가 긍정적 평가

+
+

100,000+ 개 판매 완료

+
+
+ +
+); + +export default RecommandedBox; diff --git a/src/components/recommandedProducts/RecommandedBoxStyle.ts b/src/components/recommandedProducts/RecommandedBoxStyle.ts new file mode 100644 index 0000000..4d86905 --- /dev/null +++ b/src/components/recommandedProducts/RecommandedBoxStyle.ts @@ -0,0 +1,49 @@ +import { css, Theme } from '@emotion/react'; + +export const recommandedBoxContainer = (theme: Theme) => css` + display: flex; + align-items: center; + justify-content: space-between; + box-sizing: border-box; + width: 127.7rem; + height: 6.2rem; + padding: 1rem 1.8rem; + + background-color: ${theme.colors.brandBg}; +`; + +export const contentLayout = css` + display: flex; + flex-direction: column; + gap: 0.4rem; + width: 29.9rem; + height: 4.2rem; +`; + +export const titleTextStyle = (theme: Theme) => css` + ${theme.fonts.eng.titleBold18} + color: ${theme.colors.gray9}; +`; + +export const subTextBoxStyle = css` + display: flex; + gap: 0.8rem; + align-items: center; +`; + +export const subTextStyle = (theme: Theme) => css` + ${theme.fonts.kor.captionBold11} + color: ${theme.colors.gray9}; +`; + +export const lineStyle = (theme: Theme) => css` + width: 0.1rem; + height: 1rem; + + background-color: ${theme.colors.gray6}; +`; + +export const flexBoxStyle = css` + display: flex; + gap: 0.8rem; +`; diff --git a/src/constants/buyerInfo.ts b/src/constants/buyerInfo.ts new file mode 100644 index 0000000..664534a --- /dev/null +++ b/src/constants/buyerInfo.ts @@ -0,0 +1,12 @@ +const BUYER_INFO = { + name: '데2걸', + phone: '+82 01029348372', + address: 'Jangan-gu, Suwon-si, Gyeonggi-do, Korea', + orderId: '1107109976926398', + orderYear: 2024, + orderMonth: 8, + orderDay: 22, + paymentMethod: '네이버페이', +} as const; + +export default BUYER_INFO; diff --git a/src/constants/categoryList.ts b/src/constants/categoryList.ts new file mode 100644 index 0000000..d0a3b4e --- /dev/null +++ b/src/constants/categoryList.ts @@ -0,0 +1,49 @@ +import { + IcPhoneBlack16, + IcBottleBlack16, + IcBeautyBlack16, + IcPaintBlack16, + IcBagBlack16, + IcRefrigeratorBlack16, + IcHomeBlack16, + IcHelmetBlack16, + IcBallBlack16, + IcJewelBlack16, + IcGameBlack16, + IcWatchBlack16, + IcUnderwearBlack16, + IcDogBlack16, + IcShoesBlack16, + IcOfficesuppliesBlack16, + IcElectronicdevicesBlack16, + IcFurnitureBlack16, + IcToolBlack16, + IcHomecamBlack16, + IcManfashionBlack16, + IcWomanfashionBlack16, +} from '@assets/icons/index'; + +export const CATEGORIES = Object.freeze([ + { icon: IcPhoneBlack16, label: '휴대폰/통신' }, + { icon: IcBottleBlack16, label: '유아용품' }, + { icon: IcBeautyBlack16, label: '뷰티/건강' }, + { icon: IcPaintBlack16, label: '시공/리모델링' }, + { icon: IcBagBlack16, label: '가방/소품' }, + { icon: IcRefrigeratorBlack16, label: '가전' }, + { icon: IcHomeBlack16, label: '홈인테리어' }, + { icon: IcHelmetBlack16, label: '자동차용품' }, + { icon: IcBallBlack16, label: '스포츠/레저' }, + { icon: IcJewelBlack16, label: '쥬얼리/시계' }, + { icon: IcUnderwearBlack16, label: '속옷/잠옷' }, + { icon: IcGameBlack16, label: '장난감/놀이' }, + { icon: IcWatchBlack16, label: '패션잡화' }, + { icon: IcDogBlack16, label: '반려동물' }, + { icon: IcShoesBlack16, label: '신발' }, + { icon: IcOfficesuppliesBlack16, label: '문구/사무용품' }, + { icon: IcElectronicdevicesBlack16, label: '디지털용품' }, + { icon: IcFurnitureBlack16, label: '가구' }, + { icon: IcToolBlack16, label: '공구/도구' }, + { icon: IcHomecamBlack16, label: '홈캠/도어룩' }, + { icon: IcManfashionBlack16, label: '남성패션' }, + { icon: IcWomanfashionBlack16, label: '여성패션' }, +] as const); diff --git a/src/constants/errorMessages.ts b/src/constants/errorMessages.ts new file mode 100644 index 0000000..f2c5a8c --- /dev/null +++ b/src/constants/errorMessages.ts @@ -0,0 +1,5 @@ +const MESSAGE = { + UNKNOWN_ERROR: '알 수 없는 오류가 발생했습니다.', +}; + +export default MESSAGE; \ No newline at end of file diff --git a/src/constants/footerMsg.ts b/src/constants/footerMsg.ts new file mode 100644 index 0000000..b68cf61 --- /dev/null +++ b/src/constants/footerMsg.ts @@ -0,0 +1,11 @@ +const MESSAGE = Object.freeze({ + FOOTER: + '지적 재산권 보호 - 개인정보 처리방침 - 위치 지도 - 이용 약관 - EU/UK 소비자를 위한 거래 서비스 계약 EU/EEA/UK 소비자를 위한 약관 - 사용자 정보 법적 문의 가이드 © 2010-2024 AliExpress.com. All rights reserved.', + HELP: '고객센터, 분쟁 및 신고, 구매자 보호, 지적 재산권 침해 신고, 규제 정보, 윤리 준수, 투명성 센터, 비로그인 불만 접수 창구', + ALI: '러시아어, 포르투갈어, 스페인어, 프랑스어, 독일어, 이탈리아어, 네덜란드어, 터키어, 일본어, 한국어, 태국어, 베트남어, 아랍어, 히브리어, 폴란드어', + CATEGORY: '모든 인기 상품, 제품, 프로모션, 저렴한 가격, 높은 가치, 리뷰', + GROUP: + 'Alibaba 그룹 웹사이트, AliExpress, Alimama, Alipay, Fliggy, Alibaba Cloud, Alibaba International, AliTelecom, DingTalk, Juhuasuan, Taobao 마켓플레이스, Tmall, Taobao Global, AliOS, 1688', +}); + +export default MESSAGE; diff --git a/src/constants/myList.ts b/src/constants/myList.ts new file mode 100644 index 0000000..ef51584 --- /dev/null +++ b/src/constants/myList.ts @@ -0,0 +1,35 @@ +import { + IcNoteBlack12, + IcCouponBlack12, + IcFvrBlack12, + IcPaymentBlack12, + IcMessageBlack12, + IcCoinBlack12, +} from '@assets/icons/index'; + +export const MY_CATEGORIES = Object.freeze([ + { + icon: IcNoteBlack12, + label: '주문 & 배송', + }, + { + icon: IcCouponBlack12, + label: '내 쿠폰', + }, + { + icon: IcFvrBlack12, + label: '위시리스트', + }, + { + icon: IcPaymentBlack12, + label: '결제', + }, + { + icon: IcMessageBlack12, + label: '문의 내역 (31)', + }, + { + icon: IcCoinBlack12, + label: '내 코인', + }, +] as const); diff --git a/src/constants/orderHisotry.ts b/src/constants/orderHisotry.ts new file mode 100644 index 0000000..be65f54 --- /dev/null +++ b/src/constants/orderHisotry.ts @@ -0,0 +1,8 @@ +const ORDER_HISTORYT = { + detail: + '소프트 실리콘 충전기 케이블 와인더, 고속 충전 케이블 보호대 슬리브, 애플 아이폰 데이터 코드, 투명 케이스와 많이 사용되는 핸드폰', + price: 1358, + quantity: 1, +} as const; + +export default ORDER_HISTORYT; diff --git a/src/constants/orderStatusList.ts b/src/constants/orderStatusList.ts new file mode 100644 index 0000000..8fd835f --- /dev/null +++ b/src/constants/orderStatusList.ts @@ -0,0 +1,159 @@ +const ORDER_STATUS = { + DELIVERY_COMPLETED: { + name: '배송 완료', + steps: [ + { + message: '배송이 완료되었습니다.', + month: 8, + day: 28, + time: '10:29', + }, + ], + }, + DOMESTIC_DELIVERY: { + name: '국내 배송', + steps: [ + { + message: '배송 준비 중', + month: 8, + day: 28, + time: '10:21', + }, + { + message: '목적지 국가/지역 물류센터 도착', + month: 8, + day: 28, + time: '02:20', + }, + { + message: '목적지 국가/지역 물류센터 출발', + month: 8, + day: 27, + time: '16:54', + }, + ], + }, + DOMESTIC_ARRIVAL: { + name: '국내 입고', + steps: [ + { + message: '현지 배송회사 전달 완료', + month: 8, + day: 27, + time: '13:53', + }, + { + message: '2차 통관 시작', + month: 8, + day: 27, + time: '13:19', + }, + { + message: '세관 도착', + month: 8, + day: 27, + time: '13:19', + }, + { + message: '통관 완료', + month: 8, + day: 27, + time: '13:19', + }, + { + message: '세관에서 출발', + month: 8, + day: 27, + time: '13:19', + }, + { + message: '목적지 공항 도착', + month: 8, + day: 24, + time: '12:00', + }, + ], + }, + INTERNATIONAL_SHIPPING: { + name: '국제 운송', + steps: [ + { + message: '출발 국가/지역에서 출발', + month: 8, + day: 23, + time: '21:00', + }, + { + message: '1차 통관 완료', + month: 8, + day: 23, + time: '15:08', + }, + { + message: '1차 통관 시작', + month: 8, + day: 23, + time: '14:43', + }, + { + message: '출발 국가의 공항 도착', + month: 8, + day: 23, + time: '10:38', + }, + { + message: '출발 국가/지역 물류센터에서 출발', + month: 8, + day: 22, + time: '10:08', + }, + { + message: '출발 배송업체에서 상품 수령 완료', + month: 8, + day: 22, + time: '10:04', + }, + ], + }, + PREPARE_SHIPPING: { + name: '배송 준비', + steps: [ + { + message: '상품 포장 중', + month: 8, + day: 22, + time: '03:27', + }, + { + message: '출발 물류창고에서 출발', + month: 8, + day: 22, + time: '03:17', + }, + ], + }, + PAYMENT_COMPLETED: { + name: '결제 완료', + steps: [ + { + message: '결제가 완료되었습니다.', + month: 8, + day: 21, + time: '01:17', + }, + ], + }, +} as const; + +const LAST_STATUS = { + name: '배송 완료', + steps: { + message: '배송이 완료되었습니다.', + month: 8, + day: 28, + time: '10:29', + }, + description: '받은 상품에 결함이 있거나 설명과 일치하지 않은 경우, 주문 후 90일 이내에 이의 제기를 하실 수 있습니다.', +} as const; + +export { ORDER_STATUS, LAST_STATUS }; diff --git a/src/constants/productRecommend.ts b/src/constants/productRecommend.ts new file mode 100644 index 0000000..9965b79 --- /dev/null +++ b/src/constants/productRecommend.ts @@ -0,0 +1,96 @@ +import productImg1 from '@assets/images/img_1.png'; +import productImg2 from '@assets/images/img_2.png'; +import productImg3 from '@assets/images/img_3.png'; +import productImg4 from '@assets/images/img_4.png'; +import productImg5 from '@assets/images/img_5.png'; +import productImg6 from '@assets/images/img_6.png'; + +const PRODUCT_RECOMMEND_DATA = [ + { + id: 1, + image: productImg1, + name: 'Toocki GaN 충전기, 고속 충전 4.0, ...', + price: 1652, + discountRate: 95, + hasCoupon: true, + rating: 4.5, + reviewCount: 30, + }, + { + id: 2, + image: productImg2, + name: 'Toocki 백라이트 매직 키보드, 분리형...', + price: 52100, + discountRate: 9, + rating: 4.5, + reviewCount: 324, + }, + { + id: 3, + image: productImg3, + name: 'Toocki GaN 100W 충전기, USB C...', + price: 28200, + discountRate: 10, + hasCoupon: true, + rating: 3.5, + reviewCount: 367, + }, + { + id: 4, + image: productImg4, + name: '멀티 디바이스 충전기, USB-A 타입 ...', + price: 30800, + discountRate: 54, + rating: 4.5, + reviewCount: 23, + }, + { + id: 5, + image: productImg5, + name: 'Toocki 차량용 고속 휴대폰 충전기, U...', + price: 5253, + discountRate: 75, + hasCoupon: true, + rating: 3.1, + reviewCount: 26, + }, + { + id: 6, + image: productImg6, + name: 'Toocki GaN 충전기 디지털 디스플레...', + price: 4208, + discountRate: 75, + rating: 4.5, + reviewCount: 446, + }, + { + id: 7, + image: productImg6, + name: 'Toocki GaN 충전기 디지털 디스플레...', + price: 4208, + discountRate: 75, + rating: 4.5, + reviewCount: 446, + }, + { + id: 8, + image: productImg5, + name: 'Toocki 차량용 고속 휴대폰 충전기, U...', + price: 5253, + discountRate: 75, + hasCoupon: true, + rating: 3.1, + reviewCount: 26, + }, + { + id: 9, + image: productImg6, + name: 'Toocki GaN 충전기 디지털 디스플레...', + price: 4208, + discountRate: 75, + rating: 4.5, + reviewCount: 446, + }, +]; + +export default PRODUCT_RECOMMEND_DATA; diff --git a/src/constants/recommandedProductData.ts b/src/constants/recommandedProductData.ts new file mode 100644 index 0000000..c6ee8f8 --- /dev/null +++ b/src/constants/recommandedProductData.ts @@ -0,0 +1,65 @@ +import productImg1 from '@assets/images/img_1.png'; +import productImg2 from '@assets/images/img_2.png'; +import productImg3 from '@assets/images/img_3.png'; +import productImg4 from '@assets/images/img_4.png'; +import productImg5 from '@assets/images/img_5.png'; +import productImg6 from '@assets/images/img_6.png'; + +const recommandedProductData = [ + { + id: 1, + image: productImg1, + name: 'Toocki GaN 충전기, 고속 충전 4.0, ...', + price: 1652, + discountRate: 95, + rating: 4.5, + reviewCount: 30, + }, + { + id: 2, + image: productImg2, + name: 'Toocki 백라이트 매직 키보드, 분리형...', + price: 52100, + discountRate: 9, + rating: 4.5, + reviewCount: 324, + }, + { + id: 3, + image: productImg3, + name: 'Toocki GaN 100W 충전기, USB C...', + price: 28200, + discountRate: 10, + rating: 3.5, + reviewCount: 367, + }, + { + id: 4, + image: productImg4, + name: '멀티 디바이스 충전기, USB-A 타입 ...', + price: 30800, + discountRate: 54, + rating: 4.5, + reviewCount: 23, + }, + { + id: 5, + image: productImg5, + name: 'Toocki 차량용 고속 휴대폰 충전기, U...', + price: 5253, + discountRate: 75, + rating: 3.1, + reviewCount: 26, + }, + { + id: 6, + image: productImg6, + name: 'Toocki GaN 충전기 디지털 디스플레...', + price: 4208, + discountRate: 75, + rating: 4.5, + reviewCount: 446, + }, +]; + +export default recommandedProductData; diff --git a/src/constants/sampleProducts.ts b/src/constants/sampleProducts.ts new file mode 100644 index 0000000..5afad61 --- /dev/null +++ b/src/constants/sampleProducts.ts @@ -0,0 +1,113 @@ +const SANPLE_PRODUCTS = [ + { + image: 'https://github.com/user-attachments/assets/fc877cbc-61a2-4d85-802c-331c53167df0', + name: 'Toocki GaN USB 충전기, 아이폰 15...', + price: 4500, + discountRate: 58, + hasCoupon: true, + rating: 3.5, + reviewCount: 123, + }, + { + image: 'https://github.com/user-attachments/assets/fc877cbc-61a2-4d85-802c-331c53167df0', + name: 'Toocki GaN USB 충전기, 아이폰 15...', + price: 4500, + discountRate: 58, + hasCoupon: false, + rating: 3.5, + reviewCount: 123, + }, + { + image: 'https://github.com/user-attachments/assets/fc877cbc-61a2-4d85-802c-331c53167df0', + name: 'Toocki GaN USB 충전기, 아이폰 15...', + price: 4500, + discountRate: 58, + hasCoupon: true, + rating: 4, + reviewCount: 12, + }, + { + image: 'https://github.com/user-attachments/assets/fc877cbc-61a2-4d85-802c-331c53167df0', + name: 'Toocki GaN USB 충전기, 아이폰 15...', + price: 4500, + discountRate: 58, + hasCoupon: true, + rating: 3, + reviewCount: 100, + }, + { + image: 'https://github.com/user-attachments/assets/fc877cbc-61a2-4d85-802c-331c53167df0', + name: 'Toocki GaN USB 충전기, 아이폰 15...', + price: 4500, + discountRate: 58, + hasCoupon: false, + rating: 4.5, + reviewCount: 89, + }, + { + image: 'https://github.com/user-attachments/assets/fc877cbc-61a2-4d85-802c-331c53167df0', + name: 'Toocki GaN USB 충전기, 아이폰 15...', + price: 4500, + discountRate: 58, + hasCoupon: false, + rating: 3.5, + reviewCount: 123, + }, + { + image: 'https://github.com/user-attachments/assets/fc877cbc-61a2-4d85-802c-331c53167df0', + name: 'Toocki GaN USB 충전기, 아이폰 15...', + price: 4500, + discountRate: 58, + hasCoupon: false, + rating: 2.5, + reviewCount: 50, + }, + { + image: 'https://github.com/user-attachments/assets/fc877cbc-61a2-4d85-802c-331c53167df0', + name: 'Toocki GaN USB 충전기, 아이폰 15...', + price: 4500, + discountRate: 58, + hasCoupon: true, + rating: 1, + reviewCount: 1, + }, + { + image: 'https://github.com/user-attachments/assets/fc877cbc-61a2-4d85-802c-331c53167df0', + name: 'Toocki GaN USB 충전기, 아이폰 15...', + price: 4500, + discountRate: 58, + hasCoupon: true, + rating: 3.5, + reviewCount: 123, + }, + { + image: 'https://github.com/user-attachments/assets/fc877cbc-61a2-4d85-802c-331c53167df0', + name: 'Toocki GaN USB 충전기, 아이폰 15...', + price: 4500, + discountRate: 58, + hasCoupon: true, + rating: 3.5, + reviewCount: 123, + }, + { + image: 'https://github.com/user-attachments/assets/fc877cbc-61a2-4d85-802c-331c53167df0', + name: 'Toocki GaN USB 충전기, 아이폰 15...', + price: 4500, + discountRate: 58, + hasCoupon: true, + rating: 3.5, + reviewCount: 123, + }, + { + image: 'https://github.com/user-attachments/assets/fc877cbc-61a2-4d85-802c-331c53167df0', + name: 'Toocki GaN USB 충전기, 아이폰 15...', + price: 4500, + discountRate: 58, + hasCoupon: true, + rating: 1, + reviewCount: 0, + }, + // Add more products... +]; + +export default SANPLE_PRODUCTS; diff --git a/src/constants/serviceIntroduceMsg.tsx b/src/constants/serviceIntroduceMsg.tsx new file mode 100644 index 0000000..88c1135 --- /dev/null +++ b/src/constants/serviceIntroduceMsg.tsx @@ -0,0 +1,23 @@ +import { IcBoxBlack24, IcReturnBlack24, IcHandshakeBlack24 } from '@assets/icons'; + +const SERVICE_INTRODUCE_MSG = [ + { + icon: , + title: '배송 안내 - 해외 배송 상품 구매시', + content: + '배송 지연 시 ₩1,300 쿠폰 지급 · 운송 중 분실된 상품 환불 처리 · 손상된 상품 환불 처리 ·  30일 이내 미배송 시 환불', + }, + { + icon: , + title: '무료 반품', + content: + "상품에 만족하지 않으신다면 상세페이지에 '무료 반품' 태그가 있는 상품에 경우 주문 접수 후 90일 내 반품이 가능합니다. 각 주문 건에 대한 첫 반품은 무료로 이용하실 수 있...", + }, + { + icon: , + title: '구매자 보호', + content: "‘구매자 보호'에 따라 고객님의 모든 상품 주문 과정이 보호됩니다.", + }, +] as const; + +export default SERVICE_INTRODUCE_MSG; diff --git a/src/constants/userReview.ts b/src/constants/userReview.ts new file mode 100644 index 0000000..0d82af1 --- /dev/null +++ b/src/constants/userReview.ts @@ -0,0 +1,128 @@ +import reviewImage from '@assets/images/img_review_4.png'; // 이미지 import + +export interface Review { + reviewId: number; + username: string; + rating: number; + isMonth: boolean; + contentKorean: string; + contentOriginal: string; + reviewImage: string; + usefulCount: number; + recommendCount: number; + likeCount: number; +} + +export const data = { + goodReviews: [ + { + reviewId: 1, + username: '알리알리', + rating: 4, + isMonth: true, + contentKorean: + '아 이거 참 좋다 C8타입 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다', + contentOriginal: 'Ah, this is so good', + reviewImage, + usefulCount: 123, + recommendCount: 45, + likeCount: 24, + }, + { + reviewId: 2, + username: '훌라훌라', + rating: 5, + isMonth: false, + contentKorean: + '아 이거 참 좋다 C8타입 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다', + contentOriginal: 'Ah, this is so good', + reviewImage, + usefulCount: 123, + recommendCount: 45, + likeCount: 24, + }, + { + reviewId: 3, + username: '알리알리', + rating: 4, + isMonth: true, + contentKorean: + '아 이거 참 좋다 C8타입 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다', + contentOriginal: 'Ah, this is so good', + reviewImage, + usefulCount: 123, + recommendCount: 45, + likeCount: 24, + }, + { + reviewId: 4, + username: '훌라훌라', + rating: 5, + isMonth: true, + contentKorean: + '아 이거 참 좋다 C8타입 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다 아 이거 참 좋다', + contentOriginal: 'Ah, this is so good', + reviewImage, + usefulCount: 123, + recommendCount: 45, + likeCount: 24, + }, + ], + badReviews: [ + { + reviewId: 5, + username: '망망!', + rating: 2, + isMonth: true, + contentKorean: '무슨 애기 옷이 왔어요', + contentOriginal: "Oops, I've never bought baby clothes", + reviewImage, + usefulCount: 123, + recommendCount: 45, + likeCount: 24, + }, + { + reviewId: 6, + username: '알리알리', + rating: 1, + isMonth: true, + contentKorean: '사진이랑 다름', + contentOriginal: 'What the?', + reviewImage, + usefulCount: 123, + recommendCount: 45, + likeCount: 24, + }, + { + reviewId: 7, + username: '망망!', + rating: 2, + isMonth: true, + contentKorean: + '무슨 애기 옷이 왔어요 무슨 애기 옷이 왔어요 무슨 애기 옷이 왔어요 무슨 애기 옷이 왔어요 무슨 애기 옷이 왔어요 무슨 애기 옷이 왔어요 무슨 애기 옷이 왔어요 무슨 애기 옷이 왔어요', + contentOriginal: "Oops, I've never bought baby clothes", + reviewImage, + usefulCount: 123, + recommendCount: 45, + likeCount: 24, + }, + { + reviewId: 8, + username: '알리알리', + rating: 1, + isMonth: false, + contentKorean: '사진이랑 다름', + contentOriginal: 'What the?', + reviewImage, + usefulCount: 123, + recommendCount: 45, + likeCount: 24, + }, + ], +}; + +export const reviewNum = { + total: 350, + positive: 330, + negative: 20, +}; diff --git a/src/layout/OrderLayout.tsx b/src/layout/OrderLayout.tsx new file mode 100644 index 0000000..eb8d132 --- /dev/null +++ b/src/layout/OrderLayout.tsx @@ -0,0 +1,19 @@ +import Footer from '@components/footer/Footer'; +import OrderHeader from '@components/header/orderHeader/OrderHeader'; +import { ReactNode } from 'react'; + +import { orderLayoutContainerStyle, orderLayoutStyle } from './orderLayoutStyle'; + +interface LayoutProps { + children: ReactNode; +} + +const OrderLayout = ({ children }: LayoutProps) => ( +
+ +
{children}
+
+
+); + +export default OrderLayout; diff --git a/src/layout/ProductLayout.tsx b/src/layout/ProductLayout.tsx new file mode 100644 index 0000000..55cd052 --- /dev/null +++ b/src/layout/ProductLayout.tsx @@ -0,0 +1,18 @@ +import Footer from '@components/footer/Footer'; +import ProductHeader from '@components/header/productHeader/ProductHeader'; +import { layoutContainer, productMainStyle } from 'layout/layoutStyle'; +import { ReactNode } from 'react'; + +interface LayoutProps { + children: ReactNode; +} + +const ProductLayout = ({ children }: LayoutProps) => ( +
+ +
{children}
+
+
+); + +export default ProductLayout; diff --git a/src/layout/layoutStyle.ts b/src/layout/layoutStyle.ts new file mode 100644 index 0000000..b813c77 --- /dev/null +++ b/src/layout/layoutStyle.ts @@ -0,0 +1,11 @@ +import { css } from '@emotion/react'; + +export const layoutContainer = css` + width: 100vw; + margin: 0 auto; +`; + +export const productMainStyle = css` + width: 127.7rem; + margin: 0 auto; +`; diff --git a/src/layout/orderLayoutStyle.ts b/src/layout/orderLayoutStyle.ts new file mode 100644 index 0000000..7a00e96 --- /dev/null +++ b/src/layout/orderLayoutStyle.ts @@ -0,0 +1,17 @@ +import { Theme, css } from '@emotion/react'; + +export const orderLayoutContainerStyle = (theme: Theme) => css` + width: 100vw; + margin: 0 auto; + + background-color: ${theme.colors.gray2}; +`; + +export const orderLayoutStyle = css` + display: flex; + gap: 1.2rem; + width: 121.4rem; + margin: 0 auto; + margin-top: 0.6rem; + margin-bottom: 4rem; +`; diff --git a/src/pages/OrderPage.tsx b/src/pages/OrderPage.tsx index a64db3b..42cdb64 100644 --- a/src/pages/OrderPage.tsx +++ b/src/pages/OrderPage.tsx @@ -1,3 +1,23 @@ -const OrderPage = () =>
OrderPage
; +import OrderLayout from 'layout/OrderLayout'; +import { SideBarComponent } from '@components/orderDetail/sideBar/sideBar'; +import { OrderInfoComponent } from '@components/orderDetail/orderInfo/orderInfo'; +import OrderStatusComponent from '@components/orderDetail/orderStatus/orderStatus'; +import OrderHistory from '@components/orderDetail/orderHistory/orderHistory'; +import ServiceIntroduce from '@components/orderDetail/serviceIntroduce/serviceIntroduce'; +import ProductRecommendComponent from '@components/orderDetail/productRecommend/productRecommend'; +import { mainComponentStyle } from './orderPageStyle'; + +const OrderPage = () => ( + + +
+ + + + + +
+
+); export default OrderPage; diff --git a/src/pages/ProductPage.tsx b/src/pages/ProductPage.tsx index 1ee27d7..fa1bce7 100644 --- a/src/pages/ProductPage.tsx +++ b/src/pages/ProductPage.tsx @@ -1,9 +1,26 @@ -import ProductInfo from '@components/ProductInfo/ProductInfo'; +import ProductCardList from '@components/product/ProductCardList'; +import RecommandedList from '@components/product/RecommandedList'; +import ProductOrderBox from '@components/productOrderBox/ProductOrderBox'; +import ReviewPage from '@components/productPage/review/review/ReviewPage'; +import Nav from '@components/productPage/review/reviewTop/Nav'; +import RatingPage from '@components/productPage/review/reviewTop/RatingPage'; +import Title from '@components/productPage/review/reviewTop/Title'; +import ProductSummay from '@components/productSummary/ProductSummay'; +import RecommandedBox from '@components/recommandedProducts/RecommandedBox'; +import ProductLayout from 'layout/ProductLayout'; const ProductPage = () => ( -
- -
+ + +