[250924] feat: views - TabLayout.jsx, homeReducer.js, CartPanel.jsx 등 ...

🕐 커밋 시간: 2025. 09. 24. 10:33:07

📊 변경 통계:
  • 총 파일: 6개
  • 추가: +140줄
  • 삭제: -120줄

📁 추가된 파일:
  + com.twin.app.shoptime/src/views/CartPanel/CartSidebar.jsx
  + com.twin.app.shoptime/src/views/CartPanel/CartSidebar.module.less

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/components/TabLayout/TabLayout.jsx
  ~ com.twin.app.shoptime/src/reducers/homeReducer.js
  ~ com.twin.app.shoptime/src/views/CartPanel/CartPanel.jsx
  ~ com.twin.app.shoptime/src/views/CartPanel/CartPanel.module.less

🔧 함수 변경 내용:
  📄 com.twin.app.shoptime/src/components/TabLayout/TabLayout.jsx (javascript):
     Added: deActivateTabJobFunc()
     Deleted: deActivateTabJabFunc()
  📄 com.twin.app.shoptime/src/views/CartPanel/CartSidebar.jsx (javascript):
     Added: CartSidebar(), formatPrice()

🔧 주요 변경 내용:
  • UI 컴포넌트 아키텍처 개선
  • 핵심 비즈니스 로직 개선
This commit is contained in:
djaco
2025-09-24 10:33:09 +09:00
parent adb32b4536
commit cd7438248e
6 changed files with 336 additions and 128 deletions

View File

@@ -1,36 +1,22 @@
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import classNames from 'classnames';
import {
useDispatch,
useSelector,
} from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
// import { useFocusHistory } from '../../hooks/useFocusHistory/useFocusHistory';
//아이콘
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 SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
import { Cancelable } from '@enact/ui/Cancelable';
import shoptimeFullIconRuc
from '../../../assets/images/icons/ic-lnb-logo-shoptime-ruc-white.png';
import shoptimeFullIconRuc from '../../../assets/images/icons/ic-lnb-logo-shoptime-ruc-white.png';
//이미지
import shoptimeFullIcon
from '../../../assets/images/icons/ic-lnb-logo-shoptime@3x.png';
import shoptimeFullIcon from '../../../assets/images/icons/ic-lnb-logo-shoptime@3x.png';
import { gnbOpened } from '../../actions/commonActions';
import {
checkEnterThroughGNB,
resetHomeInfo,
} from '../../actions/homeActions';
import { checkEnterThroughGNB, resetHomeInfo } from '../../actions/homeActions';
import { resetPanels } from '../../actions/panelActions';
import usePrevious from '../../hooks/usePrevious';
import useScrollTo from '../../hooks/useScrollTo';
@@ -45,36 +31,19 @@ import MyPageIcon from './iconComponents/MyPageIcon';
import OnSaleIcon from './iconComponents/OnSaleIcon';
import SearchIcon from './iconComponents/SearchIcon';
import TrendingNowIcon from './iconComponents/TrendingNowIcon';
import CartIcon from './iconComponents/CartIcon';
import TabItem from './TabItem';
import TabItemSub from './TabItemSub';
import css from './TabLayout.module.less';
const Container = SpotlightContainerDecorator(
{ enterTo: "default-element" },
"div"
);
const Container = SpotlightContainerDecorator({ enterTo: 'default-element' }, 'div');
const MainContainer = SpotlightContainerDecorator(
{ enterTo: "last-focused" },
"div"
);
const MainContainer = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
const CancelableDiv = Cancelable(
{ modal: true, onCancel: "handleCancel" },
Skinnable(Container)
);
const CancelableDiv = Cancelable({ modal: true, onCancel: 'handleCancel' }, Skinnable(Container));
class TabMenuItem {
constructor(
icons = "",
title = "",
spotlightId,
path,
patncNm,
target,
id,
children = []
) {
constructor(icons = '', title = '', spotlightId, path, patncNm, target, id, children = []) {
this.icons = icons;
this.title = title;
this.spotlightId = spotlightId;
@@ -105,10 +74,10 @@ class TabMenuItem {
};
}
const deActivateTabJabFunc = (func) => {
const deActivateTabJobFunc = (func) => {
func();
};
let deActivateTabJob = new Job(deActivateTabJabFunc, 2000);
let deActivateTabJob = new Job(deActivateTabJobFunc, 2000);
const clearPanelSwitching = (ref) => {
if (ref) {
@@ -131,40 +100,42 @@ const PANELS_HAS_TAB = [
panel_names.ON_SALE_PANEL,
panel_names.SEARCH_PANEL,
panel_names.TRENDING_NOW_PANEL,
panel_names.JUST_FOR_YOU_PANEL,
];
export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
const { getScrollTo, scrollTop } = useScrollTo();
const dispatch = useDispatch();
// 🔽 GNB 포커스 추적 - 전역 상태 사용 (HomeBanner, HomePanel과 동일한 인스턴스)
// const focusHistory = useFocusHistory({
// enableLogging: true,
// useGlobalState: true,
// logPrefix: '[FocusHistory]',
// });
const [mainExpanded, setMainExpanded] = useState(false);
const [mainSelectedIndex, setMainSelectedIndex] = useState(-1);
const [secondDepthReduce, setSecondDepthReduce] = useState(false);
const [lastFocusId, setLastFocusId] = useState(null);
const [selectedTitle, setSelectedTitle] = useState("");
const [selectedTitle, setSelectedTitle] = useState('');
const [selectedSubItemId, setSelectedSubItemId] = useState(null);
const [selectedSubIndex, setSelectedSubIndex] = useState(-1);
const [subTabLastFocusId, setSubTabLastFocusId] = useState(null);
const [tabs, setTabs] = useState([]);
const [tabFocused, setTabFocused] = useState([false, false, false]); //COLLABSED_MAIN, ACTIVATED_MAIN, ACTIVATED_SUB
const panelSwitching = useRef(null);
const cursorVisible = useSelector(
(state) => state.common.appStatus.cursorVisible
);
const cursorVisible = useSelector((state) => state.common.appStatus.cursorVisible);
const cursorVisibleRef = usePrevious(cursorVisible);
const data = useSelector((state) => state.home.menuData?.data);
const panels = useSelector((state) => state.panels.panels);
const { loginUserData } = useSelector((state) => state.common.appStatus);
const menuItems = useSelector((state) => state.home.menuItems);
const webOSVersion = useSelector(
(state) => state.common.appStatus.webOSVersion
);
const webOSVersion = useSelector((state) => state.common.appStatus.webOSVersion);
const httpHeader = useSelector((state) => state.common.httpHeader);
const broadcast = useSelector(
(state) => state.common.broadcast,
(newState) => newState?.type !== "deActivateTab" // 'deActivateTab'일 때만 리렌더링 허용
(newState) => newState?.type !== 'deActivateTab' // 'deActivateTab'일 때만 리렌더링 허용
);
const deviceCountryCode = httpHeader["X-Device-Country"];
const deviceCountryCode = httpHeader['X-Device-Country'];
const mouseNavOpen = useRef(new Job((func) => func(), 1000));
const mouseMainEntered = useRef(false);
const scrollTopJobRef = useRef(new Job((func) => func(), 0));
@@ -173,7 +144,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
let result = [];
switch (type) {
case "GNB":
case 'GNB':
result =
data?.gnb &&
data.gnb.map((item) => ({
@@ -189,7 +160,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
icons: CategoryIcon,
id: item.lgCatCd,
title: item.lgCatNm,
spotlightId: "spotlight_category",
spotlightId: 'spotlight_category',
target: [
{
name: panel_names.CATEGORY_PANEL,
@@ -216,11 +187,11 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
id: item.patnrId,
path: item.patncLogoPath,
patncNm: item.patncNm,
spotlightId: "spotlight_featuredbrand",
spotlightId: 'spotlight_featuredbrand',
target: [
{
name: panel_names.FEATURED_BRANDS_PANEL,
panelInfo: { from: "gnb", patnrId: item.patnrId },
panelInfo: { from: 'gnb', patnrId: item.patnrId },
},
],
}));
@@ -232,7 +203,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
icons: MyPageIcon,
id: item.menuId,
title: item.menuNm,
spotlightId: "spotlight_mypage",
spotlightId: 'spotlight_mypage',
target: [
{
name: panel_names.MY_PAGE_PANEL,
@@ -245,13 +216,10 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
],
}))
.filter((item) => {
if (!loginUserData.userNumber && item.title === "My Orders") {
if (!loginUserData.userNumber && item.title === 'My Orders') {
return false;
}
if (
webOSVersion < "6.0" &&
(item.title === "My Orders" || item.title === "My Info")
) {
if (webOSVersion < '6.0' && (item.title === 'My Orders' || item.title === 'My Info')) {
return false;
}
return true;
@@ -262,7 +230,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
result = [
{
icons: SearchIcon,
spotlightId: "spotlight_search",
spotlightId: 'spotlight_search',
target: [{ name: panel_names.SEARCH_PANEL }],
},
];
@@ -272,7 +240,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
result = [
{
icons: HomeIcon,
spotlightId: "spotlight_home",
spotlightId: 'spotlight_home',
target: [{ name: panel_names.HOME_PANEL }],
},
];
@@ -282,7 +250,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
result = [
{
icons: OnSaleIcon,
spotlightId: "spotlight_onsale",
spotlightId: 'spotlight_onsale',
target: [{ name: panel_names.ON_SALE_PANEL }],
},
];
@@ -292,7 +260,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
result = [
{
icons: TrendingNowIcon,
spotlightId: "spotlight_trendingnow",
spotlightId: 'spotlight_trendingnow',
target: [{ name: panel_names.TRENDING_NOW_PANEL }],
},
];
@@ -302,11 +270,21 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
result = [
{
icons: HotPicksIcon,
spotlightId: "spotlight_hotpicks",
spotlightId: 'spotlight_hotpicks',
target: [{ name: panel_names.HOT_PICKS_PANEL }],
},
];
break;
case 10800:
result = [
{
icons: CartIcon,
spotlightId: 'spotlight_cart',
target: [{ name: panel_names.CART_PANEL }],
},
];
break;
}
return result;
@@ -316,7 +294,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
if (data) {
for (let i = 0; i < menuItems.length; i++) {
const currentKey = menuItems[i].menuId;
const menuInfo = getMenuData(currentKey || "GNB");
const menuInfo = getMenuData(currentKey || 'GNB');
for (let j = 0; j < menuInfo.length; j++) {
if (![10600, 10500, 10300].includes(currentKey)) {
@@ -375,6 +353,11 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
const onTabHasFocus = useCallback(
(type) => (event) => {
// 🔽 GNB 영역으로 포커스가 들어올 때 추적
// if (type === ACTIVATED_MAIN || type === ACTIVATED_SUB) {
// focusHistory.enqueue('gnb');
// }
switch (type) {
case COLLABSED_MAIN: {
if (cursorVisibleRef.current) {
@@ -567,7 +550,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
}, [mainExpanded, mainSelectedIndex]);
const logoImg = useMemo(() => {
if (deviceCountryCode === "RU") {
if (deviceCountryCode === 'RU') {
return shoptimeFullIconRuc;
} else return shoptimeFullIcon;
}, [deviceCountryCode]);
@@ -580,11 +563,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
}, [topPanelName]);
const showSubTab = useMemo(() => {
if (
tabActivated &&
tabs[mainSelectedIndex] &&
tabs[mainSelectedIndex].hasChildren()
) {
if (tabActivated && tabs[mainSelectedIndex] && tabs[mainSelectedIndex].hasChildren()) {
return true; // 서브 탭이 있는 경우
}
return false; // 서브 탭이 없는 경우
@@ -612,7 +591,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
dispatch(gnbOpened(true));
if (panels.length === 0) {
Spotlight.focus("spotlight_home");
Spotlight.focus('spotlight_home');
return;
}
@@ -622,7 +601,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
//
else {
if (!subTabLastFocusId) {
Spotlight.focus("spotlight_home");
Spotlight.focus('spotlight_home');
}
}
}
@@ -636,7 +615,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
useEffect(() => {
if (!panelInfo) {
setMainSelectedIndex(-1);
setLastFocusId("spotlight_home");
setLastFocusId('spotlight_home');
setSubTabLastFocusId(null);
return;
}
@@ -653,17 +632,11 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
subTarget = panelInfo.lgCatCd;
}
// case: Featured Brands 2depth
else if (
topPanelName === panel_names.FEATURED_BRANDS_PANEL &&
panelInfo?.patnrId
) {
else if (topPanelName === panel_names.FEATURED_BRANDS_PANEL && panelInfo?.patnrId) {
subTarget = panelInfo.patnrId;
}
// case: My Info 2depth
else if (
topPanelName === panel_names.MY_PAGE_PANEL &&
panelInfo?.menuId
) {
else if (topPanelName === panel_names.MY_PAGE_PANEL && panelInfo?.menuId) {
subTarget = panelInfo.menuId;
}
}
@@ -688,8 +661,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
}, [tabActivated, subTabLastFocusId, mainSelectedIndex]);
useEffect(() => {
const hasFeaturedBrands =
tabs[mainSelectedIndex]?.children[0]?.path !== undefined;
const hasFeaturedBrands = tabs[mainSelectedIndex]?.children[0]?.path !== undefined;
const SCROLL_OFFSET_INDEX = hasFeaturedBrands ? 8 : 9;
@@ -707,9 +679,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
tabs[mainSelectedIndex]?.children.length - 1 >= selectedSubIndex
) {
const targetScrollIndex = selectedSubIndex - SCROLL_OFFSET_INDEX;
scrollTopJobRef.current.start(() =>
scrollTop({ y: y * targetScrollIndex })
);
scrollTopJobRef.current.start(() => scrollTop({ y: y * targetScrollIndex }));
return () => scrollTopJobRef.current.stop();
}
@@ -742,8 +712,8 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
}, [cursorVisible]);
useEffect(() => {
if (broadcast?.type === "deActivateTab") {
console.log("TabLayout deactivateTab by broadcast");
if (broadcast?.type === 'deActivateTab') {
console.log('TabLayout deactivateTab by broadcast');
deActivateTab();
}
}, [broadcast]);
@@ -775,7 +745,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
const moveFocusToMainTab = useCallback(
(e) => {
if (e.key === "ArrowLeft" && showSubTab && lastFocusId) {
if (e.key === 'ArrowLeft' && showSubTab && lastFocusId) {
Spotlight.focus(lastFocusId);
}
},
@@ -818,16 +788,14 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
<img
src={logoImg}
alt=""
className={classNames(
deviceCountryCode === "RU" && css.rucLogo
)}
className={classNames(deviceCountryCode === 'RU' && css.rucLogo)}
/>
</h1>
{tabs.map((item, index) => (
<TabItem
{...item}
key={"tabitemExpanded" + index}
key={'tabitemExpanded' + index}
onFocus={onFocus}
spotlightId={item.spotlightId}
setLastFocusId={setLastFocusId}
@@ -837,11 +805,10 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
icons={item.icons}
expanded={mainExpanded}
mainSelected={
(panels.length === 0 &&
item.spotlightId === "spotlight_home") ||
(panels.length === 0 && item.spotlightId === 'spotlight_home') ||
(panels[0]?.name === panel_names.PLAYER_PANEL &&
panels.length === 1 &&
item.spotlightId === "spotlight_home") ||
item.spotlightId === 'spotlight_home') ||
(Array.isArray(item.target) &&
item.target[0]?.name &&
panels[0]?.name === item.target[0]?.name)
@@ -878,19 +845,14 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
onMouseLeave={onTabBlur(ACTIVATED_SUB)}
onKeyDown={moveFocusToMainTab}
>
<TScroller
cbScrollTo={getScrollTo}
className={css.scrollWrap}
>
<TScroller cbScrollTo={getScrollTo} className={css.scrollWrap}>
{showSubTab &&
tabs[mainSelectedIndex]?.children.map((item, index) => {
return (
<TabItemSub
{...item}
mainMenuTitle={
tabs && tabs[mainSelectedIndex]?.title
}
key={"tabitemSubmenu" + index}
mainMenuTitle={tabs && tabs[mainSelectedIndex]?.title}
key={'tabitemSubmenu' + index}
spotlightId={item.spotlightId}
setLastFocusId={setSubTabLastFocusId}
onClick={onClickSubItem}
@@ -898,7 +860,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
index={index}
isSubItem={true}
deActivateTab={deActivateTab}
title={item.title + "-sub"}
title={item.title + '-sub'}
itemId={item.id}
path={item.path}
patncNm={item.patncNm}
@@ -912,10 +874,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
panels[0]?.panelInfo === item.target[0]?.panelInfo
}
label={
index * 1 +
1 +
" of " +
tabs[mainSelectedIndex]?.children.length
index * 1 + 1 + ' of ' + tabs[mainSelectedIndex]?.children.length
}
/>
);

View File

@@ -11,7 +11,7 @@ const initialState = {
homeTopDisplayInfo: {},
bannerData: {
data: {},
status: "idle",
status: 'idle',
},
defaultFocus: null,
enterThroughGNB: false,
@@ -19,8 +19,8 @@ const initialState = {
bannerIndices: {},
themeMenuShelfInfoData: null,
homeInfo: null,
curationId: "",
curationTitle: "",
curationId: '',
curationTitle: '',
playerControl: {
ownerId: null,
videoInfo: null,
@@ -71,6 +71,13 @@ export const homeReducer = (state = initialState, action) => {
});
}
}
// Cart 메뉴 임시 추가
menuItems.push({
title: 'Cart',
menuId: 10800,
});
return {
...state,
menuData: action.payload,
@@ -88,15 +95,13 @@ export const homeReducer = (state = initialState, action) => {
const homeTopDisplayInfo = action.payload?.homeTopDisplayInfos?.[0] ?? {};
const bannerData = action.payload?.homeMainContentsBannerInfos?.[0] ?? {};
const curationTitle =
action.payload?.homeMainContentsBannerInfos?.[0].curtNm;
const curationId =
action.payload?.homeMainContentsBannerInfos?.[0].curationId;
const curationTitle = action.payload?.homeMainContentsBannerInfos?.[0].curtNm;
const curationId = action.payload?.homeMainContentsBannerInfos?.[0].curationId;
if (bannerData.bannerInfos) {
for (let i = 0; i < bannerData.bannerInfos.length; i++) {
const info = bannerData.bannerInfos[i];
if (info.shptmDspyTpNm === "Random") {
if (info.shptmDspyTpNm === 'Random') {
const dataLength = info.bannerDetailInfos.length;
info.randomIndex = Math.floor(Math.random() * dataLength);
}

View File

@@ -1,7 +1,32 @@
import React from "react";
import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import TPanel from "../../components/TPanel/TPanel";
import TPanel from '../../components/TPanel/TPanel';
import TBody from '../../components/TBody/TBody';
import THeader from '../../components/THeader/THeader';
import { popPanel } from '../../actions/panelActions';
import CartSidebar from './CartSidebar';
export default function CartPanel({spotlightId}) {
return <TPanel spotlightId={spotlightId}>Cart</TPanel>;
import css from './CartPanel.module.less';
export default function CartPanel({ spotlightId }) {
const dispatch = useDispatch();
const onBackClick = useCallback(() => {
dispatch(popPanel());
}, [dispatch]);
return (
<TPanel spotlightId={spotlightId} isTabActivated={false}>
<TBody className={css.tbody}>
<THeader className={css.theader} title="CART" onBackButton onClick={onBackClick} />
<div className={css.Wrap}>
<div className={css.leftSection}>
<CartSidebar />
</div>
<div className={css.rightSection}>{/* 오른쪽 상품 영역 */}</div>
</div>
</TBody>
</TPanel>
);
}

View File

@@ -0,0 +1,29 @@
@import "../../style/CommonStyle.module.less";
@import "../../style/utils.module.less";
.tbody {
.size(@w: 1920px, @h: 100%);
.theader {
position: sticky;
left: 0;
top: 0;
padding-left: 60px;
z-index: 10;
}
.Wrap {
display: flex;
}
}
.leftSection {
.size(@w: 600px, @h: inherit);
background-color: #2c343f;
position: sticky;
top: 90px;
height: calc(100vh - 90px);
}
.rightSection {
flex: 1;
background-color: #ffffff;
}

View File

@@ -0,0 +1,71 @@
import React from 'react';
import TButton from '../../components/TButton/TButton';
import css from './CartSidebar.module.less';
const CartSidebar = () => {
const mockData = {
itemCount: 3,
subtotal: 199.97,
optionTotal: 29.99,
shippingHandling: 19.99,
orderTotalBeforeTax: 249.95,
};
const formatPrice = (price) => {
return `$${price.toFixed(2)}`;
};
return (
<div className={css.sidebar}>
<div className={css.summarySection}>
<div className={css.header}>
<div className={css.title}>Subtotal</div>
<span className={css.itemCount}>{mockData.itemCount} Items</span>
</div>
<div className={css.priceList}>
<div className={css.priceItem}>
<span className={css.label}>Subtotal</span>
<span className={css.value}>{formatPrice(mockData.subtotal)}</span>
</div>
<div className={css.priceItem}>
<span className={css.label}>Option</span>
<span className={css.value}>{formatPrice(mockData.optionTotal)}</span>
</div>
<div className={css.priceItem}>
<span className={css.label}>S&H</span>
<span className={css.value}>{formatPrice(mockData.shippingHandling)}</span>
</div>
</div>
<div className={css.totalRow}>
<span className={css.totalLabel}>Order Total (Before Tax)</span>
<span className={css.totalValue}>{formatPrice(mockData.orderTotalBeforeTax)}</span>
</div>
</div>
<div className={css.notesSection}>
<p className={css.note}>
Final costs will be available on your Order Review before you place order
</p>
<p className={css.note}>
By proceeding, you agree to ShopTime's General Terms of Use and acknowledge the Privacy
Statement
</p>
</div>
<div className={css.buttonSection}>
<TButton
className={css.checkoutButton}
spotlightId="cart-checkout-button"
onClick={() => console.log('Checkout clicked')}
>
Checkout
</TButton>
</div>
</div>
);
};
export default CartSidebar;

View File

@@ -0,0 +1,119 @@
@import "../../style/CommonStyle.module.less";
.sidebar {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
padding: 60px;
color: #ffffff;
}
.summarySection {
margin-bottom: 40px;
}
.header {
width: 480px;
height: 40px;
margin: 0 60px 20px;
padding: 9px 0;
// background-color: #000;
}
.title {
width: 148px;
height: 22px;
margin: 0 218px 0 0;
font-family: LGSmartUI;
font-size: 30px;
font-weight: bold;
font-stretch: normal;
font-style: normal;
line-height: normal;
letter-spacing: normal;
text-align: left;
color: #fff;
}
.itemCount {
font-size: 14px;
color: #cccccc;
}
.priceList {
margin-bottom: 20px;
}
.priceItem {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.priceItem:last-child {
margin-bottom: 0;
}
.label {
font-size: 14px;
color: #cccccc;
}
.value {
font-size: 14px;
color: #ffffff;
font-weight: 500;
}
.totalRow {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 20px;
border-top: 1px solid #4a5568;
}
.totalLabel {
font-size: 16px;
font-weight: 600;
color: #ffffff;
}
.totalValue {
font-size: 18px;
font-weight: 700;
color: #ffffff;
}
.notesSection {
margin-bottom: 40px;
}
.note {
font-size: 12px;
color: #999999;
line-height: 1.4;
margin: 0;
margin-bottom: 12px;
}
.note:last-child {
margin-bottom: 0;
}
.buttonSection {
margin-top: auto;
}
.checkoutButton {
width: 100%;
height: 50px;
background-color: #007bff;
color: #ffffff;
border: none;
border-radius: 6px;
font-size: 16px;
font-weight: 600;
}