diff --git a/com.twin.app.shoptime/assets/images/partners/ic-tab-partners-focus@3x.png b/com.twin.app.shoptime/assets/images/partners/ic-tab-partners-focus@3x.png new file mode 100644 index 00000000..5243a60f Binary files /dev/null and b/com.twin.app.shoptime/assets/images/partners/ic-tab-partners-focus@3x.png differ diff --git a/com.twin.app.shoptime/assets/images/partners/ic-tab-partners-selected@3x.png b/com.twin.app.shoptime/assets/images/partners/ic-tab-partners-selected@3x.png new file mode 100644 index 00000000..c8495310 Binary files /dev/null and b/com.twin.app.shoptime/assets/images/partners/ic-tab-partners-selected@3x.png differ diff --git a/com.twin.app.shoptime/src/App/App.module.less b/com.twin.app.shoptime/src/App/App.module.less index f9db12c8..5c06eac1 100644 --- a/com.twin.app.shoptime/src/App/App.module.less +++ b/com.twin.app.shoptime/src/App/App.module.less @@ -1,3 +1,71 @@ -.app { - /* styles can be put here */ +/* Reset */ +* { + box-sizing: border-box; + color: #1a1a1a; +} +body, +div, +h1, +h2, +h3, +h4, +h5, +h6, +table, +th, +td, +ul, +ol, +li, +dl, +dt, +dd, +p, +form, +input, +fieldset { + padding: 0; + margin: 0; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: 100%; + font-weight: normal; +} +ol, +ul, +li { + list-style: none; +} +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +menu, +nav, +section, +header { + margin: 0; + padding: 0; + border: 0; + display: block; +} + +img { + vertical-align: top; +} + +button { + background: none; + border: 0; + padding: 0; } diff --git a/com.twin.app.shoptime/src/api/apiConfig.js b/com.twin.app.shoptime/src/api/apiConfig.js index e335edf2..ffdcdc6d 100644 --- a/com.twin.app.shoptime/src/api/apiConfig.js +++ b/com.twin.app.shoptime/src/api/apiConfig.js @@ -12,7 +12,12 @@ export const URLS = { //home controller GET_HOME_TERMS: "/lgsp/v1/home/terms.lge", - GET_HOME_MENU: "/lgsp/v1/home/menu.lge", + + //brand-controller + GET_BRAND_LIST: "/lgsp/v1/brand/info.lge", + GET_BRAND_LAYOUT_INFO: "/lgsp/v1/brand/shelf.lge", + GET_BRAND_LIVE_CHANNEL_INFO: "/lgsp/v1/brand/live.lge", + GET_BRAND_TODAYS_DEALS: "/lgsp/v1/brand/tsv.lge", //on-sale controller GET_ON_SALE_INFO: "/lgsp/v1/onsale/onsale.lge", diff --git a/com.twin.app.shoptime/src/api/deviceApi.js b/com.twin.app.shoptime/src/api/deviceApi.js index 9aab3046..64aa4016 100644 --- a/com.twin.app.shoptime/src/api/deviceApi.js +++ b/com.twin.app.shoptime/src/api/deviceApi.js @@ -8,8 +8,16 @@ export async function getAuthenticationCode() { return response.data; } catch (error) { - console.error("Error : ", error); - throw error; + const { response } = error; + + if (response) { + const statusCode = response.status; + const statusText = response.statusText; + + console.error(`${statusCode} Error, ${statusText}`); + } + + throw new Error(error); } } diff --git a/com.twin.app.shoptime/src/api/featuredBrands.js b/com.twin.app.shoptime/src/api/featuredBrands.js new file mode 100644 index 00000000..dfe8ccf7 --- /dev/null +++ b/com.twin.app.shoptime/src/api/featuredBrands.js @@ -0,0 +1,96 @@ +import { URLS } from "./apiConfig"; +import api from "./axiosConfig"; + +// Featured Brands 정보 조회 IF-LGSP-304 +export async function getBrandList() { + try { + const response = await api.get(URLS.GET_BRAND_LIST); + return response.data.data; + } catch (error) { + const { response } = error; + + if (response) { + const statusCode = response.status; + const statusText = response.statusText; + + console.error(`${statusCode} Error, ${statusText}`); + } + + // throw new Error(error); + } +} + +// Featured Brands LAYOUT (shelf) 정보 조회 IF-LGSP-305 +export async function getBrandLayoutInfo(props) { + const { patnrId } = props; + try { + const response = await api.get(URLS.GET_BRAND_LAYOUT_INFO, { + params: { + patnrId, + }, + }); + + return response.data.data; + } catch (error) { + const { response } = error; + + if (response) { + const statusCode = response.status; + const statusText = response.statusText; + + console.error(`${statusCode} Error, ${statusText}`); + } + + // throw new Error(error); + } +} + +// Featured Brands Live 채널 정보 조회 IF-LGSP-306 +export async function getBrandLiveChannelInfo(props) { + const { patnrId } = props; + try { + const response = await api.get(URLS.GET_BRAND_LIVE_CHANNEL_INFO, { + params: { + patnrId, + }, + }); + + return response.data.data; + } catch (error) { + const { response } = error; + + if (response) { + const statusCode = response.status; + const statusText = response.statusText; + + console.error(`${statusCode} Error, ${statusText}`); + } + + // throw new Error(error); + } +} + +// Featured Brands Today's deals 정보 조회 IF-LGSP-307 +export async function getBrandTSVInfo(props) { + const { patnrId } = props; + try { + const response = await api.get(URLS.GET_BRAND_TODAYS_DEALS, { + params: { + patnrId, + }, + }); + + return response.data.data; + } catch (error) { + const { response } = error; + + if (response) { + const statusCode = response.status; + const statusText = response.statusText; + + console.error(`${statusCode} Error, ${statusText}`); + } + + // throw new Error(error); + } +} diff --git a/com.twin.app.shoptime/src/components/TabLayout/TabLayout.jsx b/com.twin.app.shoptime/src/components/TabLayout/TabLayout.jsx index 14c428e4..78dc203f 100644 --- a/com.twin.app.shoptime/src/components/TabLayout/TabLayout.jsx +++ b/com.twin.app.shoptime/src/components/TabLayout/TabLayout.jsx @@ -1,33 +1,41 @@ -import Spotlight from "@enact/spotlight"; -import classNames from "classnames"; import React, { useCallback, useEffect, useMemo, useRef, useState, -} from "react"; -import { useDispatch, useSelector } from "react-redux"; -import TabItem from "./TabItem"; -import css from "./TabLayout.module.less"; -//enact -import Skinnable from "@enact/sandstone/Skinnable"; -import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator"; -import { Cancelable } from "@enact/ui/Cancelable"; +} from 'react'; + +import classNames from 'classnames'; +import { + useDispatch, + useSelector, +} from 'react-redux'; + //아이콘 -import { Job } from "@enact/core/util"; -import CartIcon from "./iconComponents/CartIcon"; -import CategoryIcon from "./iconComponents/CategoryIcon"; -import FeaturedBrandIcon from "./iconComponents/FeaturedBrandIcon"; -import HomeIcon from "./iconComponents/HomeIcon"; -import HotPicksIcon from "./iconComponents/HotPicksIcon"; -import MyPageIcon from "./iconComponents/MyPageIcon"; -import OnSaleIcon from "./iconComponents/OnSaleIcon"; -import SearchIcon from "./iconComponents/SearchIcon"; -import TrendingNowIcon from "./iconComponents/TrendingNowIcon"; +import { Job } from '@enact/core/util'; +//enact +import Skinnable from '@enact/sandstone/Skinnable'; +import Spotlight from '@enact/spotlight'; +import SpotlightContainerDecorator + from '@enact/spotlight/SpotlightContainerDecorator'; +import { Cancelable } from '@enact/ui/Cancelable'; + //이미지 -import shoptimeFullIcon from "../../../assets/icons/ic-lnb-logo-shoptime@3x.png"; -import shopTimeIcon from "../../../assets/icons/ic-lnb-shoptime-symbol@3x.png"; +import shoptimeFullIcon + from '../../../assets/icons/ic-lnb-logo-shoptime@3x.png'; +import shopTimeIcon from '../../../assets/icons/ic-lnb-shoptime-symbol@3x.png'; +import CartIcon from './iconComponents/CartIcon'; +import CategoryIcon from './iconComponents/CategoryIcon'; +import FeaturedBrandIcon from './iconComponents/FeaturedBrandIcon'; +import HomeIcon from './iconComponents/HomeIcon'; +import HotPicksIcon from './iconComponents/HotPicksIcon'; +import MyPageIcon from './iconComponents/MyPageIcon'; +import OnSaleIcon from './iconComponents/OnSaleIcon'; +import SearchIcon from './iconComponents/SearchIcon'; +import TrendingNowIcon from './iconComponents/TrendingNowIcon'; +import TabItem from './TabItem'; +import css from './TabLayout.module.less'; const Container = SpotlightContainerDecorator( { enterTo: "default-element" }, @@ -270,6 +278,7 @@ export default function TabLayout({ topPanelName, onTabActivated }) { const handleNavigation = useCallback( ({ index, target }) => { + console.log(index, target); setMainSelectedIndex(index); if (target) { // dispatch(resetPanels(target)); diff --git a/com.twin.app.shoptime/src/views/FeaturedBrandsPanel/Banner/Banner.jsx b/com.twin.app.shoptime/src/views/FeaturedBrandsPanel/Banner/Banner.jsx new file mode 100644 index 00000000..fee41aa9 --- /dev/null +++ b/com.twin.app.shoptime/src/views/FeaturedBrandsPanel/Banner/Banner.jsx @@ -0,0 +1,18 @@ +import React from "react"; + +import css from "./Banner.module.less"; + +export default function Banner({ selectedBrandInfo, topImgInfo }) { + const { logoImgAlt, logoImgPath, patncNm } = selectedBrandInfo; + const { topImgAlt, topImgNm, topImgPath } = topImgInfo; + + return ( +
+
+ {`${logoImgAlt} +

{patncNm}

+
+ {topImgAlt} +
+ ); +} diff --git a/com.twin.app.shoptime/src/views/FeaturedBrandsPanel/Banner/Banner.module.less b/com.twin.app.shoptime/src/views/FeaturedBrandsPanel/Banner/Banner.module.less new file mode 100644 index 00000000..154cf0e2 --- /dev/null +++ b/com.twin.app.shoptime/src/views/FeaturedBrandsPanel/Banner/Banner.module.less @@ -0,0 +1,33 @@ +@import "../../../style/CommonStyle.module.less"; +@import "../../../style/utils.module.less"; + +.container { + position: relative; + .flex(@justifyCenter: flex-start, @alignCenter: flex-end); + .size(@w: 100%, @h: 108px); + + div { + .flex(); + gap: 12px; + + // @@ Todo, padding-left 전체적으로 적용유무 수정 + padding-left: 60px; + + h2 { + color: #fff; + font-family: @baseFontBold; + font-size: 36px; + } + img { + .size(@w: 60px, @h: 60px); + } + } + + > img { + .position(@position: absolute); + width: 100%; + height: 438px; + object-fit: cover; + z-index: -1; + } +} diff --git a/com.twin.app.shoptime/src/views/FeaturedBrandsPanel/Banner/package.json b/com.twin.app.shoptime/src/views/FeaturedBrandsPanel/Banner/package.json new file mode 100644 index 00000000..4780a2cd --- /dev/null +++ b/com.twin.app.shoptime/src/views/FeaturedBrandsPanel/Banner/package.json @@ -0,0 +1,6 @@ +{ + "main": "Banner.jsx", + "styles": [ + "Banner.module.less" + ] +} diff --git a/com.twin.app.shoptime/src/views/FeaturedBrandsPanel/FeaturedBrandsPanel.jsx b/com.twin.app.shoptime/src/views/FeaturedBrandsPanel/FeaturedBrandsPanel.jsx index da72921e..b8d7bb96 100644 --- a/com.twin.app.shoptime/src/views/FeaturedBrandsPanel/FeaturedBrandsPanel.jsx +++ b/com.twin.app.shoptime/src/views/FeaturedBrandsPanel/FeaturedBrandsPanel.jsx @@ -1,5 +1,107 @@ +import React, { useEffect, useState } from "react"; + +import { + getBrandLayoutInfo, + getBrandList, + getBrandLiveChannelInfo, +} from "../../api/featuredBrands"; import TPanel from "../../components/TPanel/TPanel"; +import Banner from "./Banner/Banner"; +import css from "./FeaturedBrandsPanel.module.less"; +import QuickMenu from "./QuickMenu/QuickMenu"; + +/* + +key, value and dataType of BrandList + +expsOrd: 1 / number +logoImgAlt: "qvc" / string +logoImgNm: "us_qvc.png" / string +logoImgPath: "http://qt2-ngfts.lge.com/fts/gftsDownload.lge?biz_code=LGSHOPPING&func_code=IMAGE&file_path=/lgshopping/image/us_qvc.png" / string +newFlag: "N" / string +patncLogoPath: "http://qt2-ngfts.lge.com/fts/gftsDownload.lge?biz_code=LGSHOPPING&func_code=IMAGE&file_path=/lgshopping/image/small_logo_qvc.png" / string +patncNm: "QVC" / string +patnrId: "1" / string + +*/ export default function FeaturedBrandsPanel() { - return Featured Brands; + // @@ Todo, provided by GNB as props or global state + const [brandList, setBrandList] = useState([]); + + // @@ Todo, provided by GNB as props or global state + const [selectedBrandId, setSelectedBrandId] = useState("1"); + + const [selectedBrandInfo, setSelectedBrandInfo] = useState({}); + const [topImgInfo, setTopImgInfo] = useState({}); + + const handleQuickMenu = (patnrId) => { + const brandInfo = brandList.find((brand) => brand.patnrId === patnrId); + setSelectedBrandInfo(brandInfo); + setSelectedBrandId(patnrId); + }; + + const fetchBrandList = async () => { + try { + const data = await getBrandList(); + console.log("Brand List:", data); + if (data) { + setBrandList(data.brandList); + setSelectedBrandInfo(() => + data.brandList.find(({ patnrId }) => patnrId === selectedBrandId) + ); + } + } catch (error) { + console.error("Error fetching Brand List:", error); + } + }; + + const fetchBrandLayoutInfo = async (patnrId) => { + try { + const data = await getBrandLayoutInfo({ patnrId }); + console.log("Brand Layout Info:", data); + if (data) { + setTopImgInfo(data.topImgInfo); + } + } catch (error) { + console.error("Error fetching Brand Layout Info:", error); + } + }; + + const fetchBrandLiveChannelInfo = async (patnrId) => { + try { + const data = await getBrandLiveChannelInfo({ patnrId }); + console.log("Brand Live Channel Info:", data); + if (data) { + } + } catch (error) { + console.error("Error fetching Brand Live Channel Info:", error); + } + }; + + useEffect(() => { + // @@ Todo, provided by GNB as props or global state + setSelectedBrandId("1"); + fetchBrandList(); + }, []); + + useEffect(() => { + fetchBrandLayoutInfo(selectedBrandId); + fetchBrandLiveChannelInfo(selectedBrandId); + }, [selectedBrandId]); + + return ( + /* scenario page 98 */ + + {brandList && brandList.length > 1 && ( + + )} + + + + ); } diff --git a/com.twin.app.shoptime/src/views/FeaturedBrandsPanel/FeaturedBrandsPanel.module.less b/com.twin.app.shoptime/src/views/FeaturedBrandsPanel/FeaturedBrandsPanel.module.less index e69de29b..a7b63b33 100644 --- a/com.twin.app.shoptime/src/views/FeaturedBrandsPanel/FeaturedBrandsPanel.module.less +++ b/com.twin.app.shoptime/src/views/FeaturedBrandsPanel/FeaturedBrandsPanel.module.less @@ -0,0 +1,3 @@ +.container { + margin-left: 120px; +} diff --git a/com.twin.app.shoptime/src/views/FeaturedBrandsPanel/QuickMenu/QuickMenu.jsx b/com.twin.app.shoptime/src/views/FeaturedBrandsPanel/QuickMenu/QuickMenu.jsx new file mode 100644 index 00000000..e7ef4c99 --- /dev/null +++ b/com.twin.app.shoptime/src/views/FeaturedBrandsPanel/QuickMenu/QuickMenu.jsx @@ -0,0 +1,60 @@ +import React, { useEffect } from 'react'; + +import Spotlight from '@enact/spotlight'; +import SpotlightContainerDecorator + from '@enact/spotlight/SpotlightContainerDecorator'; +import Spottable from '@enact/spotlight/Spottable'; + +import { $L } from '../../../utils/helperMethods'; +import css from './QuickMenu.module.less'; + +const Container = SpotlightContainerDecorator( + { leaveFor: { right: "" }, enterTo: "last-focused" }, + "nav" +); + +const SpottableComponent = Spottable("li"); + +export default function QuickMenu({ + brandList, + selectedBrandId, + onQuickMenuClick, + ...rest +}) { + const handleClick = (patnrId) => { + if (selectedBrandId === patnrId) { + return; + } + + onQuickMenuClick(patnrId); + }; + + useEffect(() => { + const element = document.getElementById(selectedBrandId); + Spotlight.focus(element); + }, []); + + return ( + + + + ); +} diff --git a/com.twin.app.shoptime/src/views/FeaturedBrandsPanel/QuickMenu/QuickMenu.module.less b/com.twin.app.shoptime/src/views/FeaturedBrandsPanel/QuickMenu/QuickMenu.module.less new file mode 100644 index 00000000..08ddf19e --- /dev/null +++ b/com.twin.app.shoptime/src/views/FeaturedBrandsPanel/QuickMenu/QuickMenu.module.less @@ -0,0 +1,67 @@ +@import "../../../style/CommonStyle.module.less"; +@import "../../../style/utils.module.less"; + +.container { + .flex(@justifyCenter:flex start); + .size(@w: 100%, @h: 180px); + padding: 30px 60px; + background-color: #e7e7e7; + + ul { + .flex(@justifyCenter: flex-start); + gap: 18px; + + /* normal */ + li { + position: relative; + + .newBagde { + .position(@position: absolute, @top: 0, @right: 0, @bottom: auto, @left: auto); + z-index: 10; + .size(@w: 60px, @h: 30px); + background-color: #f00; + color: #fff; + border-radius: 6px; + font-family: Arial; + font-weight: 600; + font-size: 18px; + text-align: center; + } + + .outline { + .position(@position: absolute, @top: 0, @right: auto, @bottom: auto, @left: 0); + .size(@w: 138px, @h:138px); + background-position: center; + background-size: cover; + } + + img { + .size(@w: 120px, @h: 120px); + } + + /* focused */ + &:focus { + border-radius: 60px; + .focusDropShadow(); + + .outline { + background-image: url("../../../../assets/images/partners/ic-tab-partners-focus@3x.png"); + } + + img { + .size(@w: 138px, @h: 138px); + } + } + + /* selected */ + &.selected { + img { + .size(@w: 138px, @h: 138px); + } + .outline { + background-image: url("../../../../assets/images/partners/ic-tab-partners-selected@3x.png"); + } + } + } + } +} diff --git a/com.twin.app.shoptime/src/views/FeaturedBrandsPanel/QuickMenu/package.json b/com.twin.app.shoptime/src/views/FeaturedBrandsPanel/QuickMenu/package.json new file mode 100644 index 00000000..125fb9f7 --- /dev/null +++ b/com.twin.app.shoptime/src/views/FeaturedBrandsPanel/QuickMenu/package.json @@ -0,0 +1,6 @@ +{ + "main": "QuickMenu.jsx", + "styles": [ + "QuickMenu.module.less" + ] +} diff --git a/com.twin.app.shoptime/src/views/OnSalePanel/CategoryNav/CategoryNav.jsx b/com.twin.app.shoptime/src/views/OnSalePanel/CategoryNav/CategoryNav.jsx new file mode 100644 index 00000000..84316bdb --- /dev/null +++ b/com.twin.app.shoptime/src/views/OnSalePanel/CategoryNav/CategoryNav.jsx @@ -0,0 +1,55 @@ +import React from "react"; + +import classNames from "classnames"; + +import Scroller from "@enact/sandstone/Scroller"; +import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator"; +import Spottable from "@enact/spotlight/Spottable"; + +import css from "./CategoryNav.module.less"; + +const Container = SpotlightContainerDecorator( + { leaveFor: { right: "" }, enterTo: "last-focused" }, + "nav" +); + +const SpottableComponent = Spottable("li"); + +export default function CategoryNav({ + categoryInfos, + currentLgCatCdIndex, + onCategoryNavClick, + ...rest +}) { + return ( + + + + + + ); +} diff --git a/com.twin.app.shoptime/src/views/OnSalePanel/CategoryNav/CategoryNav.module.less b/com.twin.app.shoptime/src/views/OnSalePanel/CategoryNav/CategoryNav.module.less new file mode 100644 index 00000000..31b3130b --- /dev/null +++ b/com.twin.app.shoptime/src/views/OnSalePanel/CategoryNav/CategoryNav.module.less @@ -0,0 +1,214 @@ +@import "../../../style/CommonStyle.module.less"; +@import "../../../style/utils.module.less"; + +.container { + position: fixed; + top: 0; + left: 120px; + + // @@ Todo, z-index 관련 추후 수정 염두 + z-index: 10; + width: calc(100% - 120px); + background-color: @BG_COLOR_01; + font-family: @baseFontBold; + font-size: 28px; + // @@ font 관련 업데이트 후 수정 + // .font(@fontFamily: @baseFontBold, @fontSize: 28px); + + ul { + display: flex; + gap: 30px; + margin-top: 50px; + } + + .category { + /* normal */ + position: relative; + .flex(@direction: column); + min-width: 210px; + color: #1a1a1a; + text-align: center; + + div { + .flex(); + .size(@w: 94px, @h: 94px); + margin-bottom: 18px; + border-radius: 48px; + background-color: #1a1a1a; + border: 2px solid #1a1a1a; + + span { + .size(@w: 80px, @h:80px); + background-position: center; + background-size: cover; + + &.category-icon-1017 { + // LG Electronics + background-image: url("../../../../assets/category/ic-category-lgelectronics-nor@3x.png"); + } + + // Garden and Outdoors + &.category-icon-1008 { + background-image: url("../../../../assets/category/ic-category-garden-nor@3x.png"); + } + + // Fashion + &.category-icon-1000 { + background-image: url("../../../../assets/category/ic-category-fashion-nor@3x.png"); + } + + // Beauty + &.category-icon-1003 { + background-image: url("../../../../assets/category/ic-category-beauty-nor@3x.png"); + } + + // Jewelry + &.category-icon-1004 { + background-image: url("../../../../assets/category/ic-category-jewelry-nor@3x.png"); + } + + // Home + &.category-icon-1006 { + background-image: url("../../../../assets/category/ic-category-home-nor@3x.png"); + } + + // Kitchen & Food + &.category-icon-1007 { + background-image: url("../../../../assets/category/ic-category-kitchen-nor@3x.png"); + } + + // Accessories + &.category-icon-1014 { + background-image: url("../../../../assets/category/ic-category-accessories-nor@3x.png"); + } + + // Heaclth & Fitness + &.category-icon-1009 { + background-image: url("../../../../assets/category/ic-category-health-nor@3x.png"); + } + + // Crafts & Sewing + &.category-icon-1011 { + background-image: url("../../../../assets/category/ic-category-cw-nor@3x.png"); + } + + // Electronics + &.category-icon-1010 { + background-image: url("../../../../assets/category/ic-category-electronics-nor@3x.png"); + } + + // Clearance + &.category-icon-1013 { + background-image: url("../../../../assets/category/ic-category-clearance-nor@3x.png"); + } + } + } + + strong { + height: 60px; + margin-bottom: 12px; + } + + &::after { + content: ""; + position: absolute; + bottom: 0; + display: block; + .size(@w: 100%, @h:6px); + background-color: transparent; + } + + /* focused */ + &:focus { + // color: #c70850; + + div { + background-color: #fff; + border: solid 2px #c70850; + box-shadow: 0 0 25px 0 rgba(2, 3, 3, 0.8); + + span { + // LG Electronics + &.category-icon-1017 { + background-image: url("../../../../assets/category/ic-category-lgelectronics-foc@3x.png"); + } + + // Garden and Outdoors + &.category-icon-1008 { + background-image: url("../../../../assets/category/ic-category-garden-foc@3x.png"); + } + + // Fashion + &.category-icon-1000 { + background-image: url("../../../../assets/category/ic-category-fashion-foc@3x.png"); + } + + // Beauty + &.category-icon-1003 { + background-image: url("../../../../assets/category/ic-category-beauty-foc@3x.png"); + } + + // Jewelry + &.category-icon-1004 { + background-image: url("../../../../assets/category/ic-category-jewelry-foc@3x.png"); + } + + // Home + &.category-icon-1006 { + background-image: url("../../../../assets/category/ic-category-home-foc@3x.png"); + } + + // Kitchen & Food + &.category-icon-1007 { + background-image: url("../../../../assets/category/ic-category-kitchen-foc@3x.png"); + } + + // Accessories + &.category-icon-1014 { + background-image: url("../../../../assets/category/ic-category-accessories-foc@3x.png"); + } + + // Heaclth & Fitness + &.category-icon-1009 { + background-image: url("../../../../assets/category/ic-category-health-foc@3x.png"); + } + + // Crafts & Sewing + &.category-icon-1011 { + background-image: url("../../../../assets/category/ic-category-cw-foc@3x.png"); + } + + // Electronics + &.category-icon-1010 { + background-image: url("../../../../assets/category/ic-category-electronics-foc@3x.png"); + } + + // Clearance + &.category-icon-1013 { + background-image: url("../../../../assets/category/ic-category-clearance-foc@3x.png"); + } + } + } + + strong { + color: #c70850; + } + + &::after { + background-color: #c70850; + } + } + } + + /* selected */ + .selected { + div { + background-color: #c70850; + border: solid 2px #c70850; + } + + strong { + color: #c70850; + } + } +} diff --git a/com.twin.app.shoptime/src/views/OnSalePanel/CategoryNav/package.json b/com.twin.app.shoptime/src/views/OnSalePanel/CategoryNav/package.json new file mode 100644 index 00000000..cc1575e5 --- /dev/null +++ b/com.twin.app.shoptime/src/views/OnSalePanel/CategoryNav/package.json @@ -0,0 +1,6 @@ +{ + "main": "CategoryNav.jsx", + "styles": [ + "CategoryNav.module.less" + ] +} diff --git a/com.twin.app.shoptime/src/views/OnSalePanel/OnSalePanel.jsx b/com.twin.app.shoptime/src/views/OnSalePanel/OnSalePanel.jsx index 0bf9ca3f..702e7a51 100644 --- a/com.twin.app.shoptime/src/views/OnSalePanel/OnSalePanel.jsx +++ b/com.twin.app.shoptime/src/views/OnSalePanel/OnSalePanel.jsx @@ -1,5 +1,87 @@ +import React, { useEffect, useState } from "react"; + +import { useDispatch } from "react-redux"; + +import { getOnSaleInfo } from "../../api/onSaleApi"; import TPanel from "../../components/TPanel/TPanel"; +import CategoryNav from "./CategoryNav/CategoryNav"; +import css from "./OnSalePanel.module.less"; +import OnSaleProductCard from "./OnSaleProductCard/OnSaleProductCard"; +import OnSaleProductsGrid from "./OnSaleProductsGrid/OnSaleProductsGrid"; export default function OnSalePanel() { - return OnSale; + const dispatch = useDispatch(); + + const [currentLgCatCdIndex, setCurrentLgCatCdIndex] = useState(0); + const [lgCatCd, setLgCatCd] = useState(""); + const [categoryInfos, setCategoryInfos] = useState([]); + const [saleInfos, setSaleInfos] = useState([]); + const [saleProductInfos, setSaleProductInfos] = useState([]); + + const handleCategoryNav = (lgCatCd, index) => { + if (currentLgCatCdIndex === index) { + return; + } + setCurrentLgCatCdIndex(index); + setLgCatCd(lgCatCd); + }; + + const getOnSaleInfoData = async (lgCatCd) => { + try { + const onSaleInfoData = await getOnSaleInfo({ + lgCatCd, + categoryIncFlag: "Y", + }); + console.log("On Sale Data:", onSaleInfoData); + + if (onSaleInfoData) { + if (lgCatCd === "") { + setCategoryInfos(onSaleInfoData.categoryInfos); + } + + setSaleInfos(onSaleInfoData.saleInfos); + + let saleProducts = []; + + for (let index = 0; index < onSaleInfoData.saleInfos.length; index++) { + saleProducts.push(onSaleInfoData.saleInfos[index].saleProductInfos); + } + + setSaleProductInfos(saleProducts); + } + } catch (error) { + console.error("Error fetching on sale:", error); + } + }; + + useEffect(() => { + getOnSaleInfoData(lgCatCd); + }, [lgCatCd, currentLgCatCdIndex]); + + return ( + + + +
+ {saleInfos.map(({ saleNm }, index) => { + return ( + + {saleProductInfos[index].map((saleProduct) => { + return ( + + ); + })} + + ); + })} +
+
+ ); } diff --git a/com.twin.app.shoptime/src/views/OnSalePanel/OnSalePanel.module.less b/com.twin.app.shoptime/src/views/OnSalePanel/OnSalePanel.module.less index e69de29b..2f20d531 100644 --- a/com.twin.app.shoptime/src/views/OnSalePanel/OnSalePanel.module.less +++ b/com.twin.app.shoptime/src/views/OnSalePanel/OnSalePanel.module.less @@ -0,0 +1,10 @@ +@import "../../style/CommonStyle.module.less"; +@import "../../style/utils.module.less"; + +.container { + font-family: @baseFont; + + .onSaleProducts { + margin-top: 236px; + } +} diff --git a/com.twin.app.shoptime/src/views/OnSalePanel/OnSaleProductCard/OnSaleProductCard.jsx b/com.twin.app.shoptime/src/views/OnSalePanel/OnSaleProductCard/OnSaleProductCard.jsx new file mode 100644 index 00000000..80700f57 --- /dev/null +++ b/com.twin.app.shoptime/src/views/OnSalePanel/OnSaleProductCard/OnSaleProductCard.jsx @@ -0,0 +1,35 @@ +import React from "react"; + +import Spottable from "@enact/spotlight/Spottable"; + +import css from "./OnSaleProductCard.module.less"; + +const SpottableComponent = Spottable("li"); + +export default function OnSaleProductCard({ saleProduct, ...rest }) { + const { imgUrl, prdtId, prdtNm, priceInfo } = saleProduct; + + const originalPrice = priceInfo.split("|")[0]; + const salePrice = priceInfo.split("|")[1]; + const salePercentage = priceInfo.split("|").reverse()[0]; + + return ( + +
+ {prdtNm} + {salePercentage && {salePercentage}} +
+
+

{prdtNm}

+

+ {salePrice} + {originalPrice} +

+
+
+ ); +} diff --git a/com.twin.app.shoptime/src/views/OnSalePanel/OnSaleProductCard/OnSaleProductCard.module.less b/com.twin.app.shoptime/src/views/OnSalePanel/OnSaleProductCard/OnSaleProductCard.module.less new file mode 100644 index 00000000..b3df69ae --- /dev/null +++ b/com.twin.app.shoptime/src/views/OnSalePanel/OnSaleProductCard/OnSaleProductCard.module.less @@ -0,0 +1,84 @@ +@import "../../../style/CommonStyle.module.less"; +@import "../../../style/utils.module.less"; + +.container { + position: relative; + .flex(@direction: column); + .size(@w: 260px, @h: 375px); + margin: 0 10px; + border-radius: 10px; + background-color: #e3e7ee; + + div:nth-child(1) { + position: relative; + .size(@w: 260px, @h: 260px); + + img { + .size(@w: 260px, @h: 260px); + object-fit: cover; + border-top-right-radius: 10px; + border-top-left-radius: 10px; + } + + > span { + .position(@position: absolute, @top: auto, @right: auto, @bottom: 12px, @left: 12px); + .size(@w: 44px, @h: 44px); + border-radius: 44px; + background-color: #c70850; + color: #fff; + font-family: @baseFontBold; + font-size: 20px; + line-height: 44px; + text-align: center; + } + } + + div:nth-child(2) { + .flex(@direction: column, @alignCenter: flex-start); + gap: 10px; + padding: 10px 15px; + + h3 { + display: -webkit-box; + font-family: @baseFontBold; + font-size: 24px; + line-height: 26px; + text-overflow: ellipsis; + word-break: break-all; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + } + p { + color: #c70850; + font-family: @baseFontBold; + font-size: 24px; + + span { + margin-left: 5px; + color: #767676; + font-size: 18px; + text-decoration: line-through; + } + } + } + + &::after { + content: ""; + .position(@position: absolute, @top: -12px, @right: auto, @bottom: auto, @left: -12px); + display: block; + .size(@w: 276px, @h: 390px); + border: 4px solid transparent; + border-radius: 10px; + } + + &:focus { + background-color: #fcfcfc; + + &::after { + border: 4px solid #c70850; + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0), + 0px 9px 15px 0 rgba(0, 0, 0, 0.3); + } + } +} diff --git a/com.twin.app.shoptime/src/views/OnSalePanel/OnSaleProductCard/package.json b/com.twin.app.shoptime/src/views/OnSalePanel/OnSaleProductCard/package.json new file mode 100644 index 00000000..a8606fc6 --- /dev/null +++ b/com.twin.app.shoptime/src/views/OnSalePanel/OnSaleProductCard/package.json @@ -0,0 +1,6 @@ +{ + "main": "OnSaleProductCard.jsx", + "styles": [ + "OnSaleProductCard.module.less" + ] +} diff --git a/com.twin.app.shoptime/src/views/OnSalePanel/OnSaleProductsGrid/OnSaleProductsGrid.jsx b/com.twin.app.shoptime/src/views/OnSalePanel/OnSaleProductsGrid/OnSaleProductsGrid.jsx new file mode 100644 index 00000000..a649bf6e --- /dev/null +++ b/com.twin.app.shoptime/src/views/OnSalePanel/OnSaleProductsGrid/OnSaleProductsGrid.jsx @@ -0,0 +1,19 @@ +import React from "react"; + +import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator"; + +import css from "./OnSaleProductsGrid.module.less"; + +const Container = SpotlightContainerDecorator( + { leaveFor: { left: "", right: "" }, enterTo: "last-focused" }, + "li" +); + +export default function OnSaleProductsGrid({ saleNm, children }) { + return ( + +

{saleNm}

+ +
+ ); +} diff --git a/com.twin.app.shoptime/src/views/OnSalePanel/OnSaleProductsGrid/OnSaleProductsGrid.module.less b/com.twin.app.shoptime/src/views/OnSalePanel/OnSaleProductsGrid/OnSaleProductsGrid.module.less new file mode 100644 index 00000000..90282200 --- /dev/null +++ b/com.twin.app.shoptime/src/views/OnSalePanel/OnSaleProductsGrid/OnSaleProductsGrid.module.less @@ -0,0 +1,20 @@ +@import "../../../style/CommonStyle.module.less"; +@import "../../../style/utils.module.less"; + +.container { + .flex(@direction:column, @alignCenter:flex-start); + gap: 40px; + width: 100%; + padding: 40px 0 20px 0; + + h2 { + margin-left: 60px; + font-family: @baseFontBold; + font-size: 38px; + } + + ul { + .flex(); + margin-left: 50px; + } +} diff --git a/com.twin.app.shoptime/src/views/OnSalePanel/OnSaleProductsGrid/package.json b/com.twin.app.shoptime/src/views/OnSalePanel/OnSaleProductsGrid/package.json new file mode 100644 index 00000000..1aba1243 --- /dev/null +++ b/com.twin.app.shoptime/src/views/OnSalePanel/OnSaleProductsGrid/package.json @@ -0,0 +1,6 @@ +{ + "main": "OnSaleProductsGrid.jsx", + "styles": [ + "OnSaleProductsGrid.module.less" + ] +}