diff --git a/com.twin.app.shoptime/src/actions/playActions.js b/com.twin.app.shoptime/src/actions/playActions.js index 2db5ac53..b432e9a9 100644 --- a/com.twin.app.shoptime/src/actions/playActions.js +++ b/com.twin.app.shoptime/src/actions/playActions.js @@ -1147,3 +1147,113 @@ export const setDisplayFullscreen = () => ({ lastUpdate: Date.now(), }, }); + +/** + * 배너 비디오를 시작합니다. + * @param {Object} videoInfo - 비디오 정보 + * @param {string} videoInfo.bannerId - 배너 ID + * @param {string} videoInfo.videoId - 비디오 ID + * @param {string} videoInfo.showUrl - 비디오 URL + * @param {boolean} videoInfo.modal - 모달 여부 + * @param {string} videoInfo.modalContainerId - 모달 컨테이너 ID + * @param {string} videoInfo.modalClassName - 모달 클래스 이름 + */ +export const startBannerVideo = (videoInfo) => (dispatch, getState) => { + dlog('[startBannerVideo] ✅ START - videoInfo:', videoInfo); + + const { + bannerId, + videoId, + showUrl, + modal = true, + modalContainerId, + modalClassName, + ...rest + } = videoInfo; + + // 비디오 식별자 생성 + const videoIdentifier = videoId || showUrl || bannerId; + if (videoIdentifier) { + const displayMode = modal ? DISPLAY_STATUS.VISIBLE : DISPLAY_STATUS.FULLSCREEN; + dlog( + '[startBannerVideo] 📌 Setting playback loading - identifier:', + videoIdentifier, + ', displayMode:', + displayMode + ); + dispatch(setPlaybackLoading(videoIdentifier, displayMode)); + } + + const panels = getState().panels.panels; + const existingPlayerPanel = panels.find((p) => p.name === panel_names.PLAYER_PANEL); + + // 기존 PlayerPanel이 있으면 초기화 + if (existingPlayerPanel) { + dlog('[startBannerVideo] 🔄 Resetting existing PLAYER_PANEL before start'); + clearAllVideoTimers(); + dispatch(popPanel(panel_names.PLAYER_PANEL)); + } + + // 새로운 PlayerPanel push + dispatch( + pushPanel( + { + name: panel_names.PLAYER_PANEL, + panelInfo: { + modal, + modalContainerId, + modalClassName, + playerState: { + currentBannerId: bannerId, + }, + videoId, + showUrl, + bannerId, + ...rest, + }, + }, + true + ) + ); + + dlog('[startBannerVideo] ✨ Panel action dispatched'); +}; + +/** + * 비디오를 중지하고 화면에서 숨깁니다. + * 패널을 닫지 않고 비디오 재생만 중지합니다. + */ +export const stopAndHideVideo = () => (dispatch, getState) => { + const panels = getState().panels.panels; + + // 모든 PlayerPanel 찾기 + const playerPanels = panels.filter((panel) => panel.name === panel_names.PLAYER_PANEL); + + if (playerPanels.length > 0) { + dlog('[stopAndHideVideo] Stopping all video playback and hiding'); + + // 타이머 정리 + if (startVideoFocusTimer) { + clearTimeout(startVideoFocusTimer); + startVideoFocusTimer = null; + } + + // 모든 PlayerPanel을 중지 및 숨김 상태로 업데이트 + playerPanels.forEach((playerPanel) => { + dispatch( + updatePanel({ + name: panel_names.PLAYER_PANEL, + panelInfo: { + ...playerPanel.panelInfo, + shouldStop: true, + isPaused: true, + isHidden: true, + }, + }) + ); + }); + + // Redux 상태도 중지로 업데이트 + dispatch(setVideoStopped()); + } +}; diff --git a/com.twin.app.shoptime/src/components/MediaItem/MediaItem.js b/com.twin.app.shoptime/src/components/MediaItem/MediaItem.js index 9f4dda2a..50e2a33b 100644 --- a/com.twin.app.shoptime/src/components/MediaItem/MediaItem.js +++ b/com.twin.app.shoptime/src/components/MediaItem/MediaItem.js @@ -20,7 +20,7 @@ import * as Config from "../../utils/Config"; import * as ContentType from "../../utils/Config"; import * as Utils from "../../utils/helperMethods"; import { $L } from "../../utils/helperMethods"; -import SpotlightIds from "../../utils/SpotlightIds"; +import { SpotlightIds } from "../../utils/SpotlightIds"; import css from "./MediaItem.module.less"; /** diff --git a/com.twin.app.shoptime/src/components/VideoPlayer/MediaTitle.js b/com.twin.app.shoptime/src/components/VideoPlayer/MediaTitle.js index 2f3c3035..b859e816 100644 --- a/com.twin.app.shoptime/src/components/VideoPlayer/MediaTitle.js +++ b/com.twin.app.shoptime/src/components/VideoPlayer/MediaTitle.js @@ -10,7 +10,7 @@ import { SpotlightContainerDecorator } from "@enact/spotlight/SpotlightContainer import ForwardRef from "@enact/ui/ForwardRef"; import { $L } from "../../utils/helperMethods"; -import SpotlightIds from "../../utils/SpotlightIds"; +import { SpotlightIds } from "../../utils/SpotlightIds"; import TButton from "../TButton/TButton"; import css from "./MediaTitle.module.less"; diff --git a/com.twin.app.shoptime/src/components/VideoPlayer/TReactPlayer.jsx b/com.twin.app.shoptime/src/components/VideoPlayer/TReactPlayer.jsx index 5b730908..c35e839c 100644 --- a/com.twin.app.shoptime/src/components/VideoPlayer/TReactPlayer.jsx +++ b/com.twin.app.shoptime/src/components/VideoPlayer/TReactPlayer.jsx @@ -158,7 +158,7 @@ export default function TReactPlayer({ // 🔽 [최적화] URL 변경 또는 언마운트 시 이전 비디오 정리 (메모리 누수 방지) useEffect(() => { return () => { - console.log('[TReactPlayer] cleanup - start', { url }); + // console.log('[TReactPlayer] cleanup - start', { url }); const videoNode = playerRef.current?.getInternalPlayer(); if (videoNode) { try { @@ -179,7 +179,7 @@ export default function TReactPlayer({ console.warn('[TReactPlayer] cleanup warning:', err); } } - console.log('[TReactPlayer] cleanup - done', { url }); + // console.log('[TReactPlayer] cleanup - done', { url }); }; }, [url]); // ✅ URL 변경 시에도 정리 로직 실행 diff --git a/com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.js b/com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.js index 35ab208c..7d0eacd1 100644 --- a/com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.js +++ b/com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.js @@ -1020,7 +1020,7 @@ const VideoPlayerBase = class extends React.Component { } componentWillUnmount() { - console.log('[VideoPlayer] componentWillUnmount - start cleanup', { src: this.props?.src }); + // console.log('[VideoPlayer] componentWillUnmount - start cleanup', { src: this.props?.src }); off('mousemove', this.activityDetected); if (platform.touch) { off('touchmove', this.activityDetected); @@ -1089,7 +1089,7 @@ const VideoPlayerBase = class extends React.Component { // 정리 중 에러는 무시하고 언마운트 진행 // console.warn('[VideoPlayer] cleanup error', err); } - console.log('[VideoPlayer] componentWillUnmount - cleanup done', { src: this.props?.src }); + // console.log('[VideoPlayer] componentWillUnmount - cleanup done', { src: this.props?.src }); if (this.floatingLayerController) { this.floatingLayerController.unregister(); } diff --git a/com.twin.app.shoptime/src/hooks/useReviews/useReviews.js b/com.twin.app.shoptime/src/hooks/useReviews/useReviews.js index a93c2a99..415831f4 100644 --- a/com.twin.app.shoptime/src/hooks/useReviews/useReviews.js +++ b/com.twin.app.shoptime/src/hooks/useReviews/useReviews.js @@ -1,6 +1,6 @@ import { useState, useMemo, useEffect, useCallback } from 'react'; import { useSelector, useDispatch } from 'react-redux'; -import { getUserReviews, getUserReviewList, getReviewFilters, clearReviewFilter } from '../../actions/productActions'; +import { getUserReviewList, getReviewFilters, clearReviewFilter } from '../../actions/productActions'; import fp from '../../utils/fp'; const DISPLAY_SIZE = 3; // 화면에 표시할 리뷰 개수 @@ -175,28 +175,22 @@ const useReviews = (prdtId, patnrId, _deprecatedReviewVersion) => { // }); try { - if (reviewVersion === 1) { - // 기존 API 호출 - // console.log('[useReviews] 🔄 getUserReviews 호출 중... (v1)'); - await dispatch(getUserReviews({ prdtId, patnrId })); - } else { - // 신 API 호출 (v2) - // console.log('[useReviews] 🔄 getUserReviewList 호출 중... (v2)'); - await dispatch(getUserReviewList({ - prdtId, - patnrId, - filterTpCd: 'ALL', - pageSize: 100, - pageNo: 1 - })); + // 신 API 호출 (v2) + // console.log('[useReviews] 🔄 getUserReviewList 호출 중... (v2)'); + await dispatch(getUserReviewList({ + prdtId, + patnrId, + filterTpCd: 'ALL', + pageSize: 100, + pageNo: 1 + })); - // IF-LGSP-100 필터 데이터 조회 - // console.log('[useReviews] 🔄 getReviewFilters 호출 중... (IF-LGSP-100)'); - await dispatch(getReviewFilters({ - prdtId, - patnrId - })); - } + // IF-LGSP-100 필터 데이터 조회 + // console.log('[useReviews] 🔄 getReviewFilters 호출 중... (IF-LGSP-100)'); + await dispatch(getReviewFilters({ + prdtId, + patnrId + })); setHasLoadedData(true); } catch (error) { console.error('[useReviews] loadReviews 실패:', error); diff --git a/com.twin.app.shoptime/src/utils/helperMethods.js b/com.twin.app.shoptime/src/utils/helperMethods.js index c6996563..3b487997 100644 --- a/com.twin.app.shoptime/src/utils/helperMethods.js +++ b/com.twin.app.shoptime/src/utils/helperMethods.js @@ -572,3 +572,10 @@ export const getReservationErrorMessage = (error) => { return $L("Failed to set reminder. Please try again."); }; + +export const convertNewlinesToBr = (text) => { + if (typeof text !== 'string') { + return text; + } + return text.replace(/\n/g, '
'); +}; diff --git a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx index fe07a672..4635a566 100644 --- a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx +++ b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx @@ -2222,13 +2222,13 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props useEffect(() => { return () => { - console.log('[PlayerPanel] unmount cleanup start'); + // console.log('[PlayerPanel] unmount cleanup start'); cleanupPlayerOnUnmount(); stopExternalPlayer(); dispatch(clearShopNowInfo()); dispatch(CLEAR_PLAYER_INFO()); setShopNowInfo([]); - console.log('[PlayerPanel] unmount cleanup done'); + // console.log('[PlayerPanel] unmount cleanup done'); }; }, [cleanupPlayerOnUnmount, stopExternalPlayer, dispatch]);