diff --git a/com.twin.app.shoptime/src/api/TAxios.js b/com.twin.app.shoptime/src/api/TAxios.js index acaa9f7a..f179f1e8 100644 --- a/com.twin.app.shoptime/src/api/TAxios.js +++ b/com.twin.app.shoptime/src/api/TAxios.js @@ -99,6 +99,23 @@ export const TAxios = async ( AUTHORIZATION.headers["sdk_ver"] = "1.0.0"; AUTHORIZATION.headers["publish_flag"] = "Y"; + // Device Header 임시 + AUTHORIZATION.headers["X-Authentication"] = "MkOLvUocrJ69RH/iV1ZABJhjR2g="; + AUTHORIZATION.headers["X-Device-ID"] = + "OemUY5qbPITZv96QKlxrtcqT6ypeX6us2qANLng3/0QCUhv2mecK1UDTMYb/hjpjey9dC/kFycc/5R8u+oK56JIWyYC4V278z64YDPKbDXIsd+eECvyf+Rdm8BneIUPM"; + AUTHORIZATION.headers["X-Device-Product"] = "webOSTV 5.0"; + AUTHORIZATION.headers["X-Device-Platform"] = "W20P"; + AUTHORIZATION.headers["X-Device-Model"] = "HE_DTV_W20P_AFADATAA"; + AUTHORIZATION.headers["X-Device-Eco-Info"] = "1"; + AUTHORIZATION.headers["X-Device-Country"] = "US"; + AUTHORIZATION.headers["X-Device-Language"] = "en-US"; + AUTHORIZATION.headers["X-Device-Netcast-Platform-Version"] = "5.0.0"; + AUTHORIZATION.headers["X-Device-Publish-Flag"] = "N"; + AUTHORIZATION.headers["X-Device-Fck"] = "253"; + AUTHORIZATION.headers["X-Device-Eula"] = + "additionalDataAllowed,takeOnAllowed,networkAllowed,generalTermsAllowed,chpAllowed,customAdAllowed,acrOnAllowed,voice2Allowed,voiceAllowed,acrAdAllowed"; + AUTHORIZATION.headers["X-Device-Personalization"] = "Y"; + if ( baseUrl !== URLS.GET_AUTHENTICATION_CODE && !accessToken && diff --git a/com.twin.app.shoptime/src/api/apiConfig.js b/com.twin.app.shoptime/src/api/apiConfig.js index 213f6a80..c8a83d32 100644 --- a/com.twin.app.shoptime/src/api/apiConfig.js +++ b/com.twin.app.shoptime/src/api/apiConfig.js @@ -34,6 +34,9 @@ export const URLS = { //my-page controller GET_MY_RECOMMANDED_KEYWORD: "/lgsp/v1/mypage/reckeyword.lge", + //search controller + GET_SEARCH: "/lgsp/v1/search/list.lge", + //main controller GET_SUB_CATEGORY: "/lgsp/v1/main/subcategory.lge", }; diff --git a/com.twin.app.shoptime/src/components/TInput/TInput.jsx b/com.twin.app.shoptime/src/components/TInput/TInput.jsx index 02a9db84..de66c7d1 100644 --- a/com.twin.app.shoptime/src/components/TInput/TInput.jsx +++ b/com.twin.app.shoptime/src/components/TInput/TInput.jsx @@ -4,11 +4,10 @@ import { InputField } from "@enact/sandstone/Input"; import Spottable from "@enact/spotlight/Spottable"; import classNames from "classnames"; import { SpotlightContainerDecorator } from "@enact/spotlight/SpotlightContainerDecorator"; +import TIconButton from "../TIconButton/TIconButton"; const Container = SpotlightContainerDecorator("div"); -const SpottableInputIcon = Spottable("div"); - const KINDS = { withIcon: "withIcon" }; const ICONS = { search: "search" }; const BORDER = { none: "none" }; @@ -21,10 +20,10 @@ export default function TInput({ color, className, spotlightDisabled, + disabled, + onIconClick, ...rest }) { - const handleIconClick = useCallback(() => {}, []); - return ( {kind === "withIcon" && ( - )} diff --git a/com.twin.app.shoptime/src/features/search/searchSlice.js b/com.twin.app.shoptime/src/features/search/searchSlice.js new file mode 100644 index 00000000..f5663ef8 --- /dev/null +++ b/com.twin.app.shoptime/src/features/search/searchSlice.js @@ -0,0 +1,51 @@ +import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; +import { URLS } from "../../api/apiConfig"; +import { TAxios } from "../../api/TAxios"; + +// Search 통합검색 (IBS) 데이터 조회 IF-LGSP-090 +export const getSearch = createAsyncThunk( + "search/getSearch", + + async (params, thunkAPI) => { + const { service, query, startIndex, maxResults, domain } = params; + + const onSuccess = (response) => { + console.log("getSearch onSuccess: ", response.data); + + thunkAPI.dispatch(updateSearchData(response.data)); + }; + + const onFail = (error) => { + console.error("getSearch onFail: ", error); + }; + + TAxios( + thunkAPI.dispatch, + thunkAPI.getState, + "post", + URLS.GET_SEARCH, + {}, + { service, query, startIndex, maxResults, domain }, + onSuccess, + onFail + ); + } +); + +const initialState = { + searchDatas: {}, +}; + +export const searchSlice = createSlice({ + name: "search", + initialState, + reducers: { + updateSearchData: (state, action) => { + state.searchDatas = action.payload; + }, + }, +}); + +export const { updateSearchData } = searchSlice.actions; + +export default searchSlice.reducer; diff --git a/com.twin.app.shoptime/src/store/store.js b/com.twin.app.shoptime/src/store/store.js index 8d2e7fc8..043d8fed 100644 --- a/com.twin.app.shoptime/src/store/store.js +++ b/com.twin.app.shoptime/src/store/store.js @@ -7,8 +7,9 @@ import deviceReducer from "../features/device/deviceSlice"; import homeReducer from "../features/home/homeSlice"; import myPageReducer from "../features/mypage/myPageSlice"; import onSaleReducer from "../features/onSale/onSaleSlice"; -import productReducer from "../features/product/productSlice"; import panelsReducer from "../features/panels/panelsSlice"; +import searchReducer from "../features/search/searchSlice"; +import productReducer from "../features/product/productSlice"; import mainReducer from "../features/main/mainSlice"; export const store = configureStore({ @@ -21,6 +22,7 @@ export const store = configureStore({ onSale: onSaleReducer, brand: brandReducer, myPage: myPageReducer, + search: searchReducer, product: productReducer, main: mainReducer, }, diff --git a/com.twin.app.shoptime/src/views/LoadingPanel/LoadingPanel.jsx b/com.twin.app.shoptime/src/views/LoadingPanel/LoadingPanel.jsx index b48da7da..82212a1f 100644 --- a/com.twin.app.shoptime/src/views/LoadingPanel/LoadingPanel.jsx +++ b/com.twin.app.shoptime/src/views/LoadingPanel/LoadingPanel.jsx @@ -149,6 +149,14 @@ export default function LoadingPanel({ showLoadingPanel, ...rest }) { src={loadingImage} > ); + + case "wait": + return ( +
+

LOADING...

+
+ ); + default: return null; } diff --git a/com.twin.app.shoptime/src/views/LoadingPanel/LoadingPanel.module.less b/com.twin.app.shoptime/src/views/LoadingPanel/LoadingPanel.module.less index 67b32683..56445ef4 100644 --- a/com.twin.app.shoptime/src/views/LoadingPanel/LoadingPanel.module.less +++ b/com.twin.app.shoptime/src/views/LoadingPanel/LoadingPanel.module.less @@ -27,3 +27,14 @@ width: auto; height: auto; } + +.wait { + background-color: rgba(0, 0, 0, 0.7); + width: 100%; + height: 100%; + .flex(); + + > p { + color: #fff; + } +} diff --git a/com.twin.app.shoptime/src/views/SearchPanel/SearchPanel.jsx b/com.twin.app.shoptime/src/views/SearchPanel/SearchPanel.jsx index 35f91383..61028e5d 100644 --- a/com.twin.app.shoptime/src/views/SearchPanel/SearchPanel.jsx +++ b/com.twin.app.shoptime/src/views/SearchPanel/SearchPanel.jsx @@ -12,6 +12,10 @@ import TIconButton, { import classNames from "classnames"; import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator"; +import Spotlight from "@enact/spotlight"; + +import { changeAppStatus } from "../../features/common/commonSlice"; +import { getSearch } from "../../features/search/searchSlice"; const Container = SpotlightContainerDecorator( { enterTo: "last-focused" }, @@ -21,12 +25,22 @@ const Container = SpotlightContainerDecorator( const ITEMS_PER_PAGE = 9; export default function SearchPanel() { + const dispatch = useDispatch(); + + const { showLoadingPanel } = useSelector((state) => state.common.appStatus); const recommandedKeywords = useSelector( (state) => state.myPage.recommandedKeywordData.data?.keywords ); + const searchDatas = useSelector( + (state) => state.search.searchDatas.data?.result.results + ); const [currentPage, setCurrentPage] = useState(1); const [paginatedKeywords, setPaginatedKeywords] = useState([]); + const [pageChanged, setPageChanged] = useState(false); + + const [searchQuery, setSearchQuery] = useState(""); + const [searchPerformed, setSearchPerformed] = useState(false); useEffect(() => { if (recommandedKeywords) { @@ -37,18 +51,101 @@ export default function SearchPanel() { } }, [recommandedKeywords, currentPage]); + useEffect(() => { + if (pageChanged && paginatedKeywords.length > 0) { + const firstButtonSpotlightId = "first-keyword-button"; + + Spotlight.focus(firstButtonSpotlightId); + + setPageChanged(false); + } + }, [pageChanged, paginatedKeywords]); + + const handleSearchChange = useCallback((e) => { + const query = e.value; + + if (query.length <= 255) { + setSearchQuery(query); + } + }, []); + + const handleSearchSubmit = useCallback(async () => { + setSearchPerformed(true); + + if (searchQuery.trim() === "") { + setSearchPerformed(false); + return; + } + + try { + await dispatch( + getSearch({ + service: "com.lgshop.app", + query: searchQuery, + startIndex: 1, + maxResults: 10, + domain: "theme,show,item", + }) + ).unwrap(); + } catch (error) { + console.error("Search request failed: ", error); + } + }, [searchQuery, dispatch]); + const handleNext = useCallback(() => { setCurrentPage((prev) => prev + 1); + setPageChanged(true); }, [currentPage]); const handlePrev = useCallback(() => { setCurrentPage((prev) => (prev > 1 ? prev - 1 : prev)); + setPageChanged(true); }, [currentPage]); const hasPrevPage = currentPage > 1; const hasNextPage = currentPage * ITEMS_PER_PAGE < recommandedKeywords?.length; + const renderContents = () => { + if (searchPerformed) { + if (searchDatas && searchDatas.length > 0) { + return
data!!
; + } else { + return
SORRY
; + } + } else { + return ( + <> + {hasPrevPage && ( + + )} + + {paginatedKeywords.map((keyword, index) => ( + + {keyword.keywd} + + ))} + + {hasNextPage && ( + + )} + + ); + } + }; + return ( - {hasPrevPage && ( - - )} - - {paginatedKeywords.map((keyword, index) => ( - - {keyword.keywd} - - ))} - - {hasNextPage && ( - - )} + {renderContents()} ); }