ONSALE, FEATURED BRANDS

This commit is contained in:
younghoon100.park
2024-01-24 17:02:39 +09:00
parent 32c446f1ae
commit 5d51b886db
26 changed files with 1047 additions and 29 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -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;
}

View File

@@ -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",

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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));

View File

@@ -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 (
<div className={css.container}>
<div>
<img src={logoImgPath} alt={`${logoImgAlt} ${topImgNm}`} />
<h2>{patncNm}</h2>
</div>
<img src={topImgPath} alt={topImgAlt} />
</div>
);
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,6 @@
{
"main": "Banner.jsx",
"styles": [
"Banner.module.less"
]
}

View File

@@ -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 <TPanel>Featured Brands</TPanel>;
// @@ 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 */
<TPanel className={css.container}>
{brandList && brandList.length > 1 && (
<QuickMenu
brandList={brandList}
onQuickMenuClick={handleQuickMenu}
selectedBrandId={selectedBrandId}
/>
)}
<Banner selectedBrandInfo={selectedBrandInfo} topImgInfo={topImgInfo} />
</TPanel>
);
}

View File

@@ -0,0 +1,3 @@
.container {
margin-left: 120px;
}

View File

@@ -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 (
<Container {...rest} className={css.container}>
<ul>
{brandList &&
brandList.map(({ logoImgAlt, logoImgPath, newFlag, patnrId }) => {
return (
<SpottableComponent
className={patnrId === selectedBrandId && css.selected}
id={patnrId}
key={patnrId}
onClick={() => handleClick(patnrId)}
>
{newFlag === "Y" && (
<span className={css.newBagde}>{$L("NEW")}</span>
)}
<span className={css.outline} />
<img src={logoImgPath} alt={logoImgAlt} />
</SpottableComponent>
);
})}
</ul>
</Container>
);
}

View File

@@ -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");
}
}
}
}
}

View File

@@ -0,0 +1,6 @@
{
"main": "QuickMenu.jsx",
"styles": [
"QuickMenu.module.less"
]
}

View File

@@ -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 (
<Container {...rest} className={css.container}>
<Scroller
direction="horizontal"
horizontalScrollbar="hidden"
noScrollByWheel={true}
scrollMode="translate"
>
<ul>
{categoryInfos &&
categoryInfos.map(({ lgCatNm, lgCatCd }, index) => {
return (
<SpottableComponent
className={classNames(
css.category,
currentLgCatCdIndex === index && css.selected
)}
key={lgCatCd}
onClick={() => onCategoryNavClick(lgCatCd, index)}
>
<div>
<span className={css[`category-icon-${lgCatCd}`]} />
</div>
<strong>{lgCatNm}</strong>
</SpottableComponent>
);
})}
</ul>
</Scroller>
</Container>
);
}

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,6 @@
{
"main": "CategoryNav.jsx",
"styles": [
"CategoryNav.module.less"
]
}

View File

@@ -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 <TPanel>OnSale</TPanel>;
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 (
<TPanel className={css.container}>
<CategoryNav
categoryInfos={categoryInfos}
currentLgCatCdIndex={currentLgCatCdIndex}
onCategoryNavClick={handleCategoryNav}
/>
<section className={css.onSaleProducts}>
{saleInfos.map(({ saleNm }, index) => {
return (
<OnSaleProductsGrid key={saleNm} saleNm={saleNm}>
{saleProductInfos[index].map((saleProduct) => {
return (
<OnSaleProductCard
key={saleProduct.prdtId}
saleProduct={saleProduct}
/>
);
})}
</OnSaleProductsGrid>
);
})}
</section>
</TPanel>
);
}

View File

@@ -0,0 +1,10 @@
@import "../../style/CommonStyle.module.less";
@import "../../style/utils.module.less";
.container {
font-family: @baseFont;
.onSaleProducts {
margin-top: 236px;
}
}

View File

@@ -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 (
<SpottableComponent
{...rest}
className={css.container}
spotlightId={prdtId}
>
<div>
<img src={imgUrl} alt={prdtNm} />
{salePercentage && <span>{salePercentage}</span>}
</div>
<div className={css.test}>
<h3>{prdtNm}</h3>
<p>
{salePrice}
<span>{originalPrice}</span>
</p>
</div>
</SpottableComponent>
);
}

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,6 @@
{
"main": "OnSaleProductCard.jsx",
"styles": [
"OnSaleProductCard.module.less"
]
}

View File

@@ -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 (
<Container className={css.container}>
<h2>{saleNm}</h2>
<ul>{children}</ul>
</Container>
);
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,6 @@
{
"main": "OnSaleProductsGrid.jsx",
"styles": [
"OnSaleProductsGrid.module.less"
]
}