();\n const [isShow, setIsShow] = useState(false);\n const controls = useAnimation();\n\n useEffect(() => {\n setIsMounted(true);\n }, []);\n\n const updateIsShow = useCallback(() => {\n if (\n mainElement === undefined ||\n contentsElement === undefined ||\n recommendHometownNewsElement === undefined\n )\n return;\n\n const isContentsScrollOveredHalf =\n contentsElement.offsetTop + contentsElement.offsetHeight / 2 <\n mainElement.scrollTop + mainElement.offsetHeight / 2;\n\n const isRecommendHometownNewsListScrollOvered =\n recommendHometownNewsElement.offsetTop <\n mainElement.scrollTop + mainElement.offsetHeight;\n\n setIsShow(\n isContentsScrollOveredHalf && !isRecommendHometownNewsListScrollOvered,\n );\n }, [contentsElement, mainElement, recommendHometownNewsElement]);\n\n useEffect(() => {\n if (!isMounted) return;\n\n const mainElement = document.getElementById(SELECTOR.MAIN);\n if (mainElement) {\n setMainElement(mainElement);\n }\n\n const contentsElement = document.getElementById(contentAreaSelector);\n if (contentsElement) {\n setContentsElement(contentsElement);\n }\n\n const recommendHometownNewsElement = document.getElementById(\n recommendHometownNewsSelector,\n );\n if (recommendHometownNewsElement) {\n setRecommendHometownNewsElement(recommendHometownNewsElement);\n }\n }, [isMounted, contentAreaSelector, recommendHometownNewsSelector]);\n\n useEffect(() => {\n if (mainElement && contentsElement && recommendHometownNewsElement) {\n updateIsShow();\n }\n }, [\n mainElement,\n contentsElement,\n recommendHometownNewsElement,\n updateIsShow,\n ]);\n\n useEffect(() => {\n if (mainElement === undefined) return;\n\n const handleScroll = throttle(() => updateIsShow(), 200);\n\n mainElement.addEventListener('scroll', handleScroll);\n\n return () => mainElement.removeEventListener('scroll', handleScroll);\n }, [mainElement, updateIsShow]);\n\n useEffect(() => {\n controls.start({\n opacity: isShow ? SHOW_BUTTON_OPACITY : INITIALIZE_BUTTON_OPACITY,\n bottom: isShow ? SHOW_BUTTON_BOTTOM : INITIALIZE_BUTTON_BOTTOM,\n transition: { duration: 0.2 },\n });\n }, [isShow, controls]);\n\n const handleClick = () => {\n if (recommendHometownNewsElement === undefined) return;\n\n recommendHometownNewsElement.scrollIntoView({\n behavior: 'smooth',\n block: 'start',\n });\n };\n\n return (\n document.getElementById(SELECTOR.MAIN)}>\n \n \n \n {keyword} 소식 더보기\n
\n \n \n \n \n );\n};\n\nconst INITIALIZE_BUTTON_OPACITY = 0;\nconst INITIALIZE_BUTTON_BOTTOM = -40;\nconst SHOW_BUTTON_OPACITY = 1;\nconst SHOW_BUTTON_BOTTOM = 10;\n","'use client';\n\nimport { CustomLink } from '@/components/atom/CustomLink';\nimport { LazyImage } from '@/components/client';\nimport { ROUTES, STORAGE_KEY } from '@/constants';\n\ninterface RecommendHometownNewsCardProps {\n idIdx: string;\n name: string;\n publishedAt: string;\n thumbnail: string;\n}\n\nexport const RecommendHometownNewsCard = ({\n idIdx,\n name,\n publishedAt,\n thumbnail,\n}: RecommendHometownNewsCardProps) => {\n const routePathname = ROUTES.HOMETOWN_NEWS_DETAIL.pathname({ idIdx: idIdx });\n\n const handleClick = () => {\n sessionStorage.setItem(\n STORAGE_KEY.HOMETOWN_NEWS_DETAIL_BOTTOM_DRAWER_PATH,\n routePathname,\n );\n };\n\n return (\n \n \n \n \n {name}\n
\n \n {publishedAt} 작성\n
\n \n \n );\n};\n","import { cn } from '@common/utils';\n\nimport { CustomLink } from '@/components/atom/CustomLink';\nimport { LazyImage } from '@/components/client';\nimport { ROUTES, STORAGE_KEY } from '@/constants';\n\ninterface RecommendHometownNewsVerticalCardProps {\n className?: string;\n idIdx: string;\n name: string;\n publishedAt: string;\n thumbnail: string;\n}\n\nexport const RecommendHometownNewsVerticalCard = ({\n className,\n idIdx,\n name,\n publishedAt,\n thumbnail,\n}: RecommendHometownNewsVerticalCardProps) => {\n const routePathname = ROUTES.HOMETOWN_NEWS_DETAIL.pathname({ idIdx: idIdx });\n\n const handleClick = () => {\n sessionStorage.setItem(\n STORAGE_KEY.HOMETOWN_NEWS_DETAIL_BOTTOM_DRAWER_PATH,\n routePathname,\n );\n };\n\n return (\n \n \n \n \n {name}\n
\n \n {publishedAt} 작성\n
\n \n \n );\n};\n","'use client';\n\nimport type { ListHometownNewsDto } from '@generatedapi/unauth';\nimport { Mousewheel } from 'swiper/modules';\nimport type { SwiperProps } from 'swiper/react';\nimport { Swiper, SwiperSlide } from 'swiper/react';\n\nimport { RecommendHometownNewsCard } from './RecommendHometownNewsCard';\nimport { RecommendHometownNewsVerticalCard } from './RecommendHometownNewsVerticalCard';\n\ninterface RecommendHometownNewsProps {\n id?: string;\n list: ListHometownNewsDto[];\n}\n\nexport const RecommendHometownNews = ({\n id,\n list,\n}: RecommendHometownNewsProps) => {\n const listSection = {\n list: list.length < 7 ? list : list.slice(0, 3),\n swipeList: list.length < 7 ? [] : list.slice(3),\n };\n\n if (list.length < 1) return null;\n\n return (\n \n \n 이런 이야기는 어때요?\n
\n \n {listSection.list.map((news) => (\n - \n \n
\n ))}\n
\n {listSection.swipeList.length > 0 ? (\n \n {listSection.swipeList.map((news) => (\n \n \n \n ))}\n \n ) : null}\n \n );\n};\n\nconst SWIPER_PROPS = {\n slidesPerView: 'auto',\n spaceBetween: 13,\n mousewheel: true,\n modules: [Mousewheel],\n} satisfies SwiperProps;\n","'use client';\n\nimport { createUuid } from '@common/utils';\nimport { useSuspenseQuery } from '@tanstack/react-query';\n\nimport { COMMENT_TYPE } from '@/api';\nimport { getCommentList } from '@/api/getCommentList';\nimport { CommentListWithField } from '@/components/template/CommentListWithField';\n\nimport { useGetHometownNewsRelatedList as getHometownNewsRelatedList } from '../[id]/_hooks/useGetHometownNewsRelatedList';\nimport { HometownNewsDetailBottomDrawer } from './HometownNewsDetailBottomDrawer';\nimport { ImageFloatingBanner } from './ImageFloatingBanner';\nimport { MoreFloatingButton } from './MoreFloatingButton';\nimport { RecommendHometownNews } from './RecommendHometownNews';\n\ninterface MoreHometownNewsListProps {\n id: number;\n idx: string;\n contentAreaId: string;\n}\n\nexport const MoreHometownNewsList = ({\n id,\n idx,\n contentAreaId,\n}: MoreHometownNewsListProps) => {\n const hometownNewsRelated = useSuspenseQuery({\n queryKey: ['getHometownNewsRelatedList', idx],\n queryFn: async () => {\n const {\n list: hometownNewsRelatedList,\n keyword: hometownNewsRelatedListKeyword,\n } = await getHometownNewsRelatedList({ idIdx: idx });\n\n return { hometownNewsRelatedList, hometownNewsRelatedListKeyword };\n },\n });\n const { hometownNewsRelatedList, hometownNewsRelatedListKeyword } =\n hometownNewsRelated.data;\n\n const isNotRenderRelatedList = hometownNewsRelatedList.length > 0;\n\n const initialComment = useSuspenseQuery({\n queryKey: ['getCommentList', id],\n queryFn: async () =>\n await getCommentList({\n commentType: COMMENT_TYPE.HOMETOWN_NEWS,\n id,\n }),\n });\n\n const initialCommentData = initialComment.data;\n\n const recommendHometownNewsAreaId = createUuid();\n\n return (\n <>\n {isNotRenderRelatedList ? (\n \n ) : null}\n \n {isNotRenderRelatedList ? (\n \n ) : null}\n \n \n >\n );\n};\n","'use client';\n\nimport { Button } from '@common/components';\nimport { SvgDelete } from '@common/icon';\nimport { useState } from 'react';\n\nimport { CustomLink } from '@/components/atom/CustomLink';\nimport { useCheckWelloApp } from '@/hooks/useCheckWelloApp';\nimport { useAuthStore } from '@/stores/AuthStore';\n\ninterface TopBannerProps {\n appFileUrl?: string;\n appLinkUrl?: string;\n fileUrl?: string;\n linkUrl?: string;\n title?: string;\n targetNewYn?: boolean;\n}\n\nexport const TopBanner = ({\n appFileUrl,\n appLinkUrl,\n fileUrl,\n linkUrl,\n title,\n targetNewYn,\n}: TopBannerProps) => {\n const [isClose, setIsClose] = useState(false);\n const { isWelloApp } = useCheckWelloApp();\n const isLogin = useAuthStore((state) => state.isLogin);\n\n const applyFileUrl = isWelloApp ? appFileUrl : fileUrl;\n const applyLinkUrl = isWelloApp ? appLinkUrl : linkUrl;\n\n const isNoRender =\n !!isLogin ||\n isLogin === undefined ||\n isClose ||\n !(applyFileUrl && applyLinkUrl);\n\n if (isNoRender) return null;\n\n return (\n \n
\n \n \n );\n};\n","'use client';\n\nimport { SvgBookmarkFill } from '@common/icon';\nimport { cn } from '@common/utils';\nimport { useState } from 'react';\nimport CountUp from 'react-countup';\nimport { useInView } from 'react-intersection-observer';\n\ninterface BookmarkButtonProps {\n bookmarkCount?: number;\n isBookmarked?: boolean;\n onClick?: (isBookmarked: boolean) => void;\n}\n\nexport const BookmarkButton = ({\n bookmarkCount = 0,\n isBookmarked = false,\n onClick,\n}: BookmarkButtonProps) => {\n const [isDisplayed, setIsDisplayed] = useState(false);\n\n const inView = useInView({\n onChange: (inView) => {\n if (inView) {\n setIsDisplayed(true);\n }\n },\n });\n\n return (\n \n );\n};\n","var _g, _defs;\nfunction _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }\nimport * as React from \"react\";\nvar SvgMailSend = function SvgMailSend(props) {\n return /*#__PURE__*/React.createElement(\"svg\", _extends({\n xmlns: \"http://www.w3.org/2000/svg\",\n width: \"1em\",\n height: \"1em\",\n fill: \"none\",\n viewBox: \"0 0 24 24\"\n }, props), _g || (_g = /*#__PURE__*/React.createElement(\"g\", {\n clipPath: \"url(#mail-send_svg__a)\"\n }, /*#__PURE__*/React.createElement(\"path\", {\n fill: \"#000\",\n fillRule: \"evenodd\",\n d: \"M22.092.795a2 2 0 0 0-.288.076c-.393.14-20.294 7.802-20.43 7.865-.17.079-.362.248-.446.393a.93.93 0 0 0-.132.564c.02.399.225.69.62.882.112.054 2.008.917 4.212 1.917s4.02 1.831 4.034 1.846c.015.014.845 1.83 1.846 4.034 1 2.204 1.863 4.1 1.918 4.21.126.26.29.432.51.537.154.072.18.077.428.077.251 0 .272-.004.422-.08q.314-.156.466-.47c.078-.16 7.735-20.045 7.889-20.486a.85.85 0 0 0 .062-.42.8.8 0 0 0-.085-.379 1.01 1.01 0 0 0-1.026-.566m-9.654 5.824A1322 1322 0 0 0 4.416 9.72c0 .009 1.302.607 2.894 1.33l2.894 1.316 2.176-2.171c2.033-2.029 2.186-2.176 2.326-2.237.408-.178.847-.105 1.143.192.297.296.37.735.192 1.143-.061.14-.208.293-2.237 2.326l-2.171 2.177 1.315 2.893a186 186 0 0 0 1.33 2.894c.01 0 1.392-3.577 3.073-7.95 1.682-4.372 3.072-7.985 3.09-8.028a.3.3 0 0 0 .025-.076c-.003 0-3.616 1.39-8.028 3.089\",\n clipRule: \"evenodd\"\n }))), _defs || (_defs = /*#__PURE__*/React.createElement(\"defs\", null, /*#__PURE__*/React.createElement(\"clipPath\", {\n id: \"mail-send_svg__a\"\n }, /*#__PURE__*/React.createElement(\"path\", {\n fill: \"#fff\",\n d: \"M0 0h24v24H0z\"\n })))));\n};\nexport default SvgMailSend;","import { createSvgComponent } from \"../../../icon/createSvgComponent\";\nimport MailSend from \"../figma/mail-send.svg\";\nexport default createSvgComponent(MailSend);","'use client';\n\nimport { type Editor, Spinner } from '@common/components';\nimport { Button, CustomEditor } from '@common/components';\nimport { SvgMailSend } from '@common/icon';\nimport { cn,CustomError } from '@common/utils';\nimport type { SaveWelloCommentResponse } from '@generatedapi/auth';\nimport { useMutation, useSuspenseQuery } from '@tanstack/react-query';\nimport { Fragment, Suspense, useEffect, useRef, useState } from 'react';\n\nimport type { COMMENT_TYPE } from '@/api';\nimport { AuthApi, AuthQueryKey, UnauthQueryOptions } from '@/api/v2';\nimport { ROUTES } from '@/constants';\nimport { useCustomRouter } from '@/hooks/useCustomRouter';\nimport { useAuthStore } from '@/stores/AuthStore';\n\nimport { BottomNavPortal } from '../client';\n\ninterface CommentFieldContentProps {\n initOnFocus?: boolean;\n replyTo?: string;\n contentId: number;\n commentType: COMMENT_TYPE;\n rootCommentId?: number;\n parentCommentId?: number;\n isLogin: boolean;\n onSuccess?: (response?: SaveWelloCommentResponse) => void;\n topDivider?: boolean;\n className?: string;\n}\n\nconst CommentFieldContent = ({\n initOnFocus,\n replyTo,\n contentId,\n commentType,\n rootCommentId,\n parentCommentId,\n isLogin,\n onSuccess,\n topDivider = true,\n className,\n}: CommentFieldContentProps) => {\n const [isFocused, setIsFocused] = useState(false);\n\n const startText = replyTo ? `@${replyTo}` : '';\n\n const protectedPrefix = startText\n ? `${startText} `\n : null;\n\n const minHtmlLength = replyTo ? protectedPrefix!.length : 0;\n\n const tempContentForProtectedPrefix = useRef('');\n const editorRef = useRef(null);\n\n const [isSubmitDisabled, setIsSubmitDisabled] = useState(false);\n\n const handleSubmitDisabled = (editor: Editor) => {\n let text = editor.getText({\n blockSeparator: '\\n',\n });\n\n if (startText && text.startsWith(startText)) {\n text = text.slice(startText.length + 1);\n }\n\n const isDisabled = !text.length;\n\n setIsSubmitDisabled(isDisabled);\n };\n\n const initContent = (editor: Editor) => {\n if (protectedPrefix) {\n tempContentForProtectedPrefix.current = protectedPrefix;\n\n editor.commands.setContent(protectedPrefix);\n } else {\n editor.commands.setContent('');\n }\n };\n\n useEffect(() => {\n if (!protectedPrefix || !editorRef.current) return;\n editorRef.current.commands.setContent(protectedPrefix);\n }, [protectedPrefix]);\n\n const postComment = useMutation({\n mutationFn: (contents: string) =>\n AuthApi['post:/wello/v2/auth/wello-comment']({\n body: {\n comment_type_cd: commentType,\n content_id: contentId,\n contents,\n parent_wello_comment_id: parentCommentId,\n root_wello_comment_id: rootCommentId,\n },\n }),\n meta: {\n reset: [\n AuthQueryKey['get:/wello/v2/auth/wello-comment'](),\n AuthQueryKey['get:/wello/v2/auth/policy/feedback/{policy_id}'](),\n AuthQueryKey[\n 'get:/wello/v2/auth/contents/feedback/{best_contents_id}'\n ](),\n AuthQueryKey['get:/wello/v2/auth/wello-comment/my'](),\n AuthQueryKey['get:/wello/v2/auth/community/my'](),\n AuthQueryKey[\n 'get:/wello/v2/auth/hometown-news/feedback/{hometown_news_id}'\n ](),\n AuthQueryKey[\n 'get:/wello/v2/auth/community/free-talk/feedback/{community_id_idx}'\n ](),\n AuthQueryKey[\n 'get:/wello/v2/auth/community/policy-talk/feedback/{community_id_idx}'\n ](),\n AuthQueryKey[\n 'get:/wello/v2/auth/community/wello-news/feedback/{community_id_idx}'\n ](),\n ],\n },\n\n onSuccess: async ({ data }) => {\n const context = data?.context;\n\n const { current: editor } = editorRef;\n\n if (!editor) throw new CustomError();\n\n initContent(editor);\n\n handleSubmitDisabled(editor);\n\n onSuccess?.(context);\n\n setTimeout(() => {\n editor.commands.focus();\n }, 100);\n },\n });\n\n const router = useCustomRouter();\n\n return (\n \n setIsFocused(false)}\n onCreate={({ editor }) => {\n initContent(editor);\n\n if (initOnFocus) {\n editor.commands.focus();\n }\n\n editorRef.current = editor;\n\n handleSubmitDisabled(editor);\n }}\n onFocus={() => {\n if (isLogin) {\n setIsFocused(true);\n } else {\n const url = new URL(window.location.href);\n\n router.push(\n ROUTES.LOGIN.withSearchParams({\n searchParams: {\n redirect: url.pathname + url.search,\n },\n }),\n );\n }\n }}\n onUpdate={({ editor }) => {\n if (protectedPrefix) {\n const content = editor.getHTML();\n\n if (!content.startsWith(protectedPrefix)) {\n if (content.length <= minHtmlLength) {\n editor.commands.setContent(protectedPrefix);\n } else {\n return editor.commands.setContent(\n tempContentForProtectedPrefix.current,\n );\n }\n }\n\n tempContentForProtectedPrefix.current = content;\n }\n\n handleSubmitDisabled(editor);\n }}\n >\n \n \n
\n );\n};\n\nconst ReplyCommentField = (\n props: Omit & {\n parentCommentId: number;\n },\n) => {\n const { data: nickname } = useSuspenseQuery({\n ...UnauthQueryOptions[\n 'get:/wello/v2/allows/wello-comment/{welloCommentId}'\n ]({\n path: {\n welloCommentId: props.parentCommentId,\n },\n }),\n select: (data) => data.context?.nick_name,\n });\n\n return ;\n};\n\nconst FixedArea = () => ;\n\ninterface CommentFieldProps\n extends Omit {\n withPortal?: boolean;\n}\n\nexport const CommentField = ({\n parentCommentId,\n withPortal = true,\n ...restProps\n}: CommentFieldProps) => {\n const isLogin = useAuthStore((state) => state.isLogin);\n\n const Container = withPortal ? BottomNavPortal : Fragment;\n\n return (\n \n {isLogin !== undefined ? (\n }>\n {parentCommentId ? (\n \n ) : (\n \n )}\n \n ) : (\n \n )}\n \n );\n};\n","'use client';\n\nimport { CustomError, isEnumValue } from '@common/utils';\nimport { useSearchParams } from 'next/navigation';\nimport { useState } from 'react';\nimport { scroller } from 'react-scroll';\n\nimport { COMMENT_SORT_TYPE } from '@/api';\nimport { ROUTES, SEARCH_PARAMS, SELECTOR } from '@/constants';\nimport { useCustomRouter } from '@/hooks/useCustomRouter';\nimport { useToastPoint } from '@/hooks/useToastPoint';\nimport { useAuthStore } from '@/stores/AuthStore';\n\nimport { CommentField } from '../molecule/CommentField';\nimport type { CommentListProps } from '../organism/CommentList';\nimport { CommentList } from '../organism/CommentList';\n\nexport interface CommentListWithFieldProps\n extends Pick<\n CommentListProps,\n 'commentType' | 'contentId' | 'initialData' | 'rootCommentId' | 'className'\n > {\n initOnFocus?: boolean;\n policyTalkContentIdx?: string;\n hasCommentField?: boolean;\n commentFieldTopDivider?: boolean;\n commentFieldClassName?: string;\n withPortal?: boolean;\n}\n\nexport const CommentListWithField = ({\n initOnFocus,\n commentType,\n contentId,\n initialData,\n rootCommentId,\n className,\n policyTalkContentIdx,\n hasCommentField = true,\n commentFieldTopDivider,\n commentFieldClassName,\n withPortal,\n}: CommentListWithFieldProps) => {\n const searchParams = useSearchParams();\n\n const sortType =\n searchParams?.get(SEARCH_PARAMS.SORT_TYPE) || COMMENT_SORT_TYPE.LATEST;\n\n const parentCommentId = ((params) => {\n if (!params) return undefined;\n\n const parentCommentId = Number(params);\n\n if (Number.isNaN(parentCommentId)) return undefined;\n\n return parentCommentId;\n })(searchParams?.get(SEARCH_PARAMS.PARENT_COMMENT_ID));\n\n if (!isEnumValue(COMMENT_SORT_TYPE, sortType))\n throw new CustomError({\n return_message: '잘못된 sortType입니다.',\n });\n\n const isLogin = useAuthStore((state) => state.isLogin);\n\n const router = useCustomRouter();\n\n const [placeholder, setPlaceholder] = useState(initialData);\n\n const isCommentPage = !!parentCommentId;\n\n const { toastPoint } = useToastPoint();\n\n return (\n <>\n {\n if (isRoot && policyTalkContentIdx) {\n router.push(\n ROUTES.POLICY_TALK_COMMENT_EDIT.pathname({\n commentId,\n contentIdx: policyTalkContentIdx,\n }),\n );\n } else {\n router.push(\n ROUTES.COMMENT_EDIT.withSearchParams({\n dynamicPath: { commentId },\n }),\n );\n }\n }}\n onClickReplyButton={(commentId, parentCommentId) => {\n const targetRoute = ROUTES.COMMENT_LIST.withSearchParams({\n dynamicPath: { commentType, contentId, commentId },\n searchParams: {\n sortType,\n parentCommentId,\n },\n });\n\n if (!isLogin) {\n return router.push(\n ROUTES.LOGIN.withSearchParams({\n searchParams: {\n redirect: targetRoute,\n },\n }),\n );\n }\n\n return isCommentPage\n ? router.replace(targetRoute)\n : router.push(targetRoute);\n }}\n />\n {hasCommentField && isLogin ? (\n {\n if (!res) throw new CustomError();\n\n if (sortType !== COMMENT_SORT_TYPE.LATEST) {\n const url = new URL(window.location.href);\n\n url.searchParams.set(\n SEARCH_PARAMS.SORT_TYPE,\n COMMENT_SORT_TYPE.LATEST,\n );\n\n history.replaceState(null, '', url.search);\n }\n\n scroller.scrollTo(SELECTOR.COMMENT_LIST_TOP, {\n duration: 300,\n smooth: 'easeOutQuad',\n containerId: SELECTOR.MAIN,\n offset: -100,\n });\n\n const point = res?.point_activity?.result_yn\n ? res.point_activity.add_points\n : null;\n\n if (point) {\n toastPoint(point);\n }\n\n setTimeout(() => {\n scroller.scrollTo(\n SELECTOR.COMMENT_PREFIX + res.wello_comment_id,\n {\n duration: 300,\n smooth: 'easeOutQuad',\n containerId: SELECTOR.MAIN,\n offset: -100,\n },\n );\n }, 300);\n }}\n />\n ) : null}\n >\n );\n};\n","'use client';\n\nimport { isSameEndpointQuery } from '@common/api/isSameEndpointQuery';\nimport { hasObjectKey } from '@common/utils';\nimport { useMutation, useQueryClient } from '@tanstack/react-query';\nimport { cloneDeep, debounce } from 'es-toolkit';\nimport { useMemo } from 'react';\n\nimport { AuthApi, AuthQueryKey } from '@/api/v2';\nimport { ROUTES } from '@/constants';\nimport { MESSAGE_KEYWORD, useHandleApp } from '@/modules/wello-app';\nimport { useAuthStore } from '@/stores/AuthStore';\n\nimport { useCustomRouter } from './useCustomRouter';\n\ninterface BookmarkParams {\n /**\n * 쿼리키 조회용\n */\n idx?: string;\n contentId: number;\n isBookmarked: boolean;\n bookmarkType: string;\n}\n\n// 북마크 가능한 항목의 인터페이스\ninterface BookmarkableItem {\n id: number;\n wishlist_yn: boolean;\n [key: string]: unknown;\n}\n\n// 컨텐츠 배열을 포함하는 응답 데이터 인터페이스\ninterface ContentsResponse {\n context: {\n contents: BookmarkableItem[];\n [key: string]: unknown;\n };\n [key: string]: unknown;\n}\n\nconst OPTIMISTIC_QUERY_KEYS = [\n AuthQueryKey['get:/wello/v2/auth/member/neighbor/policy'],\n AuthQueryKey['get:/wello/v2/auth/member/recommend/policy'],\n AuthQueryKey['get:/wello/v2/auth/policy-apply/history'],\n AuthQueryKey['get:/wello/v2/auth/community/wello-news'],\n AuthQueryKey['get:/wello/v2/auth/member/recent/policy'],\n AuthQueryKey['get:/wello/v2/auth/community/policy-talk'],\n AuthQueryKey['get:/wello/v2/auth/hometown-news'],\n AuthQueryKey['get:/wello/v2/auth/member/interest/policy'],\n AuthQueryKey['get:/wello/v2/auth/policy/policy/{meta_policy_id_idx}/related'],\n AuthQueryKey['get:/wello/v2/auth/member/wishlist/voucher-provider'],\n AuthQueryKey['get:/wello/v2/auth/contents/pick/{wello_pick_id}'],\n AuthQueryKey['get:/wello/v2/auth/policy'],\n AuthQueryKey['get:/wello/v2/auth/member/wishlist/policy'],\n AuthQueryKey['get:/wello/v2/auth/member/wishlist/community/policy-talk'],\n AuthQueryKey['get:/wello/v2/auth/member/wishlist/best-contents'],\n AuthQueryKey['get:/wello/v2/auth/wello-comment'],\n AuthQueryKey['get:/wello/v2/auth/member/wishlist/emergency-room'],\n AuthQueryKey['get:/wello/v2/auth/emergency-room/{emergencyRoomId}'],\n AuthQueryKey['get:/wello/v2/auth/voucher/provider'],\n AuthQueryKey['get:/wello/v2/auth/voucher/provider/feedback/{id}'],\n AuthQueryKey['get:/wello/v2/auth/policy/feedback/{policy_id}'],\n AuthQueryKey['get:/wello/v2/auth/contents/feedback/{best_contents_id}'],\n AuthQueryKey['get:/wello/v2/auth/wello-comment/my'],\n AuthQueryKey['get:/wello/v2/auth/community/my'],\n AuthQueryKey['get:/wello/v2/auth/hometown-news/feedback/{hometown_news_id}'],\n AuthQueryKey[\n 'get:/wello/v2/auth/community/free-talk/feedback/{community_id_idx}'\n ],\n AuthQueryKey[\n 'get:/wello/v2/auth/community/policy-talk/feedback/{community_id_idx}'\n ],\n AuthQueryKey[\n 'get:/wello/v2/auth/community/wello-news/feedback/{community_id_idx}'\n ],\n AuthQueryKey['get:/wello/v2/auth/member/peer-group/policy'],\n AuthQueryKey['get:/wello/v2/auth/contents/best'],\n AuthQueryKey['get:/wello/v2/auth/member/wishlist/hometown-news'],\n AuthQueryKey['get:/wello/v2/auth/member/wishlist/community/wello-news'],\n];\n\ntype QueryObject = {\n query: {\n page: number;\n };\n};\n\nconst isQueryObject = (obj: object): obj is QueryObject => {\n return 'query' in obj && typeof (obj as any).query === 'object';\n};\n\nexport const useBookmark = () => {\n const { mutate } = useMutation({\n mutationFn: ({ contentId, isBookmarked, bookmarkType }: BookmarkParams) =>\n AuthApi[\n isBookmarked\n ? 'delete:/wello/v2/auth/member/wishlist'\n : 'post:/wello/v2/auth/member/wishlist'\n ]({\n body: {\n content_id: contentId,\n content_type_cd: bookmarkType,\n },\n }),\n meta: {\n reset: [AuthQueryKey['get:/wello/v2/auth/member/action-count']()],\n },\n });\n\n const queryClient = useQueryClient();\n const router = useCustomRouter();\n\n const { request: requestHaptic } = useHandleApp({\n type: MESSAGE_KEYWORD.HAPTIC,\n });\n\n const bookmark = useMemo(() => {\n const debouncedMutate = debounce((params, options) => {\n return mutate(params, options);\n }, 500);\n\n return (params: BookmarkParams) => {\n requestHaptic();\n const { contentId, isBookmarked, idx } = params;\n\n const { isLogin } = useAuthStore.getState();\n\n if (!isLogin)\n return router.push(\n ROUTES.LOGIN.withSearchParams({\n searchParams: {\n redirect: location.href,\n },\n }),\n );\n\n const bookmarkQueries = queryClient.getQueriesData({\n predicate: (query) =>\n OPTIMISTIC_QUERY_KEYS.some((queryKey) =>\n isSameEndpointQuery(query, queryKey()),\n ),\n });\n\n bookmarkQueries.forEach(([key, data]) => {\n const isBaseKeyHasId = (() => {\n const [firstKey] = key;\n\n const isV2ApiQueryKey = typeof firstKey === 'object';\n\n if (isV2ApiQueryKey) {\n const StringKey = JSON.stringify(firstKey);\n\n const id = String(contentId);\n\n if (StringKey.includes(id) || (idx && StringKey.includes(idx)))\n return true;\n }\n\n return false;\n })();\n\n if (\n (key.includes(contentId) ||\n isBaseKeyHasId ||\n (idx && key.includes(idx))) &&\n hasObjectKey(data, 'context') &&\n hasObjectKey(data.context, 'wishlist_yn')\n ) {\n const newData = cloneDeep(data);\n\n if (\n hasObjectKey(newData.context, 'wishlist_yn') &&\n typeof newData.context.wishlist_yn === 'boolean'\n ) {\n newData.context.wishlist_yn = !isBookmarked;\n\n if (\n 'wishlist_count' in newData.context &&\n typeof newData.context.wishlist_count === 'number'\n ) {\n newData.context.wishlist_count = isBookmarked\n ? newData.context.wishlist_count - 1\n : newData.context.wishlist_count + 1;\n }\n\n queryClient.setQueryData(key, newData);\n }\n }\n //* 일반 쿼리 (not Infinite Query)\n else if (\n data &&\n typeof data === 'object' &&\n hasObjectKey(data, 'context') &&\n data.context &&\n typeof data.context === 'object' &&\n hasObjectKey(data.context, 'contents') &&\n Array.isArray(data.context.contents)\n ) {\n const newData = cloneDeep(data);\n\n const updateContents = (contents: unknown[]): unknown[] =>\n contents.map((item) => {\n if (\n item &&\n typeof item === 'object' &&\n 'id' in item &&\n typeof item.id === 'number' &&\n item.id === contentId &&\n 'wishlist_yn' in item &&\n typeof item.wishlist_yn === 'boolean'\n ) {\n return { ...item, wishlist_yn: !isBookmarked };\n }\n\n return item;\n });\n\n const hasContents = (obj: unknown): obj is ContentsResponse =>\n obj !== null &&\n typeof obj === 'object' &&\n 'context' in obj &&\n obj.context !== null &&\n typeof obj.context === 'object' &&\n 'contents' in obj.context &&\n Array.isArray(obj.context.contents);\n\n if (hasContents(newData)) {\n const updatedContents = updateContents(newData.context.contents);\n const hasChanges = updatedContents.some(\n (item, index) => item !== newData.context.contents[index],\n );\n\n if (hasChanges) {\n newData.context.contents = updatedContents as BookmarkableItem[];\n queryClient.setQueryData(key, newData);\n }\n }\n } else\n for (const idKey of [\n 'community_id',\n 'best_contents_id',\n 'hometown_news_id',\n 'wello_comment_id',\n 'policy_talk_id',\n 'policy_id',\n 'id',\n ] as const) {\n const newData = cloneDeep(data);\n\n const targetObjectList = ((data) => {\n const result: unknown[] = [];\n\n const findTargetObject = (obj: unknown) => {\n if (obj && typeof obj === 'object') {\n if (hasObjectKey(obj, idKey) && obj[idKey] === contentId) {\n switch (idKey) {\n case 'id': {\n const hasId =\n 'meta_policy_id_idx' in obj || 'uuid' in obj;\n\n if (hasId) result.push(obj);\n\n break;\n }\n\n default:\n result.push(obj);\n break;\n }\n }\n\n for (const key in obj) {\n if (hasObjectKey(obj, key)) {\n findTargetObject(obj[key]);\n }\n }\n }\n };\n\n findTargetObject(data);\n\n return result;\n })(newData);\n\n const changeBookmarkCount = (obj: unknown) => {\n if (obj && typeof obj === 'object') {\n if (\n hasObjectKey(obj, 'wishlist_count') &&\n typeof obj.wishlist_count === 'number'\n ) {\n obj['wishlist_count'] = isBookmarked\n ? obj['wishlist_count'] - 1\n : obj['wishlist_count'] + 1;\n }\n for (const key in obj) {\n if (hasObjectKey(obj, key)) {\n changeBookmarkCount(obj[key]);\n }\n }\n }\n };\n\n const changeBookmarkYn = (obj: unknown) => {\n if (obj && typeof obj === 'object') {\n if (\n hasObjectKey(obj, 'wishlist_yn') &&\n typeof obj.wishlist_yn === 'boolean'\n ) {\n obj['wishlist_yn'] = !isBookmarked;\n }\n for (const key in obj) {\n if (hasObjectKey(obj, key)) {\n changeBookmarkYn(obj[key]);\n }\n }\n }\n };\n\n targetObjectList.forEach((obj) => {\n changeBookmarkCount(obj);\n changeBookmarkYn(obj);\n\n const [firstKey] = key;\n\n const isV2InfiniteQuery =\n firstKey && isQueryObject(firstKey) && '_infinite';\n\n if (isV2InfiniteQuery) {\n firstKey.query.page = 1;\n }\n\n queryClient.setQueryData(key, newData);\n });\n }\n });\n\n debouncedMutate(params, {\n onError: () => {\n bookmarkQueries.forEach(([key, data]) => {\n queryClient.setQueryData(key, data);\n });\n },\n });\n };\n }, [mutate, queryClient, requestHaptic, router]);\n\n return { bookmark };\n};\n","import type { ServerResponse } from '@common/utils';\nimport { useQueryClient } from '@tanstack/react-query';\nimport Image from 'next/image';\nimport { toast } from 'react-toastify';\n\nimport { pointOptionsKeys } from '@/query-factory/point';\n\nexport const useToastPoint = () => {\n const queryClient = useQueryClient();\n\n const toastPoint = (point: number) => {\n toast(\n // 가운데 정렬을 위한 콘테이너\n \n {/* 토스트 콘테이너 */}\n
\n
\n
\n \n {Math.abs(point).toLocaleString()}P\n \n {point >= 0 ? '가 적립되었어요!' : '가 차감되었어요!'}\n
\n
\n
,\n {\n className: '!bg-transparent !shadow-none pointer-events-none',\n },\n );\n\n //* 포인트 정보 갱신\n QUERY_KEYS_TO_INVALIDATE.forEach(async (key) => {\n await queryClient.invalidateQueries({\n queryKey: [key],\n });\n });\n };\n\n const toastPointError = ({ error }: { error: unknown }) => {\n const { return_code: errorCode, return_message: errorMessage } =\n error as ServerResponse;\n\n console.error(error);\n\n const toastErrorMessage = (message: string) =>\n toast(\n // 가운데 정렬을 위한 콘테이너\n ,\n {\n className: '!bg-transparent !shadow-none pointer-events-none',\n },\n );\n\n if (!errorCode || !errorMessage) {\n return toastErrorMessage('서버로 부터 정상적인 응답을 받지 못했습니다.');\n }\n\n if (30000 <= errorCode) {\n return toastErrorMessage(errorMessage);\n }\n\n return toastErrorMessage(\n `일시적인 오류가 발생했습니다. 잠시 후 다시 시도해주세요. (${errorCode})`,\n );\n };\n\n return {\n toastPoint,\n toastPointError,\n };\n};\n\nconst QUERY_KEYS_TO_INVALIDATE = [\n pointOptionsKeys.pointAttendance,\n pointOptionsKeys.pointHistoryInfiniteList,\n //* 필요에 따라 다른 queryKey를 추가\n];\n","import { createUuid,CustomError } from '@common/utils';\nimport { queryOptions } from '@tanstack/react-query';\nimport ky from 'ky';\n\nimport { LOGIN_TYPE } from '@/api';\nimport { API_ROUTES, ENV } from '@/constants';\n\nexport const authQueryOptionsKeys = {\n socialLogin: createUuid(),\n};\n\ninterface SocialLoginProps {\n state?: string | null;\n code?: string | null;\n}\n\nexport interface SocialLoginResult {\n socialLoginToken: string;\n loginType: LOGIN_TYPE;\n}\n\ninterface LoginResponse {\n access_token: string;\n}\n\nexport const authQueryOptions = {\n socialLogin: ({ code, state }: SocialLoginProps) => {\n const enabled = !!(code && state);\n\n return queryOptions({\n queryKey: [authQueryOptionsKeys.socialLogin, enabled, state],\n staleTime: 0,\n enabled,\n queryFn: async (): Promise => {\n if (!enabled)\n throw new CustomError({\n return_message: 'socialLoginProps 가 없습니다.',\n });\n\n const decodedState = window.atob(state);\n\n const { type, redirectPath } = JSON.parse(decodedState);\n\n switch (type) {\n //! ⚠️ Kakao는 client secret이 없어서 front에서 직접 요청 가능\n case 'kakao': {\n const { access_token } = await ky\n .post('https://kauth.kakao.com/oauth/token', {\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n code,\n state,\n redirect_uri: `${ENV.NEXT_PUBLIC_DOMAIN}${redirectPath}`,\n grant_type: 'authorization_code',\n client_id: ENV.NEXT_PUBLIC_KAKAO_CLIENT_ID,\n }),\n })\n .json();\n\n return {\n loginType: LOGIN_TYPE.KAKAO,\n socialLoginToken: access_token,\n };\n }\n\n case 'naver': {\n const { access_token } = await ky\n .post(API_ROUTES.NAVER_AUTH.pathname, {\n json: {\n code,\n state,\n },\n })\n .json();\n\n return {\n loginType: LOGIN_TYPE.NAVER,\n socialLoginToken: access_token,\n };\n }\n\n case 'google': {\n const { access_token } = await ky\n .post(API_ROUTES.GOOGLE_AUTH.pathname, {\n json: {\n code,\n state,\n },\n })\n .json();\n\n return {\n loginType: LOGIN_TYPE.GOOGLE,\n socialLoginToken: access_token,\n };\n }\n\n case 'apple': {\n const { id_token } = await ky\n .post(API_ROUTES.APPLE_TOKEN.pathname, {\n json: {\n code,\n state,\n },\n })\n .json<{ id_token: string }>();\n\n return {\n loginType: LOGIN_TYPE.APPLE,\n socialLoginToken: id_token,\n };\n }\n\n default:\n throw new Error('잘못된 로그인 타입입니다.');\n }\n },\n });\n },\n};\n","import { assert, createUuid } from '@common/utils';\nimport { queryOptions } from '@tanstack/react-query';\nimport ky from 'ky';\n\nimport { AUTH_API,DPG_API } from '@/api';\n\ninterface DeptReportDetailProps {\n custCi: string;\n rprYr: string;\n dbtrRprClssCd: string;\n dbtrRprDt: string;\n}\n\nexport const dpgQueryOptionsKeys = {\n checkEligibility: createUuid(),\n agreement: createUuid(),\n notice: createUuid(),\n loanInterest: createUuid(),\n deptReportList: createUuid(),\n deptReportDetail: createUuid(),\n getCIInfo: createUuid(),\n address: createUuid(),\n};\n\nexport const dpgQueryOptions = {\n checkEligibility: (ciToken: string) =>\n queryOptions({\n queryKey: [dpgQueryOptionsKeys.checkEligibility, ciToken],\n queryFn: () =>\n DPG_API.postEligibilityCheck({\n requestBody: {\n custCi: ciToken,\n },\n }),\n retry: false,\n enabled: ciToken !== '',\n select: ({ context }) => {\n return {\n ciToken: context?.custCi,\n isTarget: context?.canRprYn === 'Y',\n isPeriodicReport: context?.preRgrRprtYn === 'Y',\n };\n },\n }),\n agreement: () =>\n queryOptions({\n queryKey: [dpgQueryOptionsKeys.agreement],\n queryFn: DPG_API.getAgreement,\n select: ({ context }) => {\n assert(context, 'context is not valid');\n\n return {\n financialInfo: context.finInfoAgmtCten,\n personalInfo: context.indvInfoAgmtCten,\n };\n },\n }),\n notice: (year = 2024) =>\n queryOptions({\n queryKey: [dpgQueryOptionsKeys.notice, year, DPG_API],\n queryFn: () =>\n ky\n .get('https://stage.dpgapi.wello.im/wello/v1/dept-reporting/notice', {\n searchParams: { baseYear: year.toString() },\n })\n .json>>(),\n select: (data) => {\n assert(data.context, 'context is not valid');\n\n return {\n content: data.context.noticeCten,\n };\n },\n }),\n loanInterest: (custCi: string) =>\n queryOptions({\n queryKey: [dpgQueryOptionsKeys.loanInterest, custCi],\n queryFn: () =>\n DPG_API.getLoanInterestList({\n requestBody: {\n custCi,\n baseYear: new Date().getFullYear().toString(),\n },\n }),\n enabled: !!custCi,\n select: ({ context }) => {\n assert(context, 'context is not valid');\n\n return {\n custCi: context.custCi,\n basDt: context.basDt,\n loanAmtSum: context.loanAmtSum,\n loanBlmtSum: context.loanBlmtSum,\n loanIntAmtSum: context.loanIntAmtSum,\n nyRcpDutyApmtAmt: context.nyRcpDutyApmtAmt,\n nyRcpOvdAmt: context.nyRcpOvdAmt,\n nyRcpOvdAdAmt: context.nyRcpOvdAdAmt,\n nyRcpAmtSum: context.nyRcpAmtSum,\n loanList: context.loanList,\n };\n },\n }),\n deptReportList: (custCi: string) =>\n queryOptions({\n queryKey: [dpgQueryOptionsKeys.deptReportList, custCi],\n queryFn: () =>\n DPG_API.getReportHistoryList({\n requestBody: {\n custCi,\n },\n }),\n enabled: !!custCi,\n select: ({ context }) => {\n assert(context, 'context is not valid');\n\n return {\n contents: context.contents,\n total: context.totalCount,\n };\n },\n }),\n deptReportDetail: ({\n custCi,\n rprYr,\n dbtrRprClssCd,\n dbtrRprDt,\n }: DeptReportDetailProps) =>\n queryOptions({\n queryKey: [\n dpgQueryOptionsKeys.deptReportDetail,\n custCi,\n dbtrRprClssCd,\n dbtrRprDt,\n rprYr,\n ],\n queryFn: () =>\n DPG_API.getReportHistory({\n requestBody: {\n custCi,\n rprYr,\n dbtrRprClssCd,\n dbtrRprDt,\n },\n }),\n enabled: !!custCi,\n select: ({ context }) => {\n assert(context, 'context is not valid');\n\n return {\n custCi: context.custCi,\n rprFltNm: context.rprFltNm,\n dbtrRprClssCd: context.dbtrRprClssCd,\n rprDutyAbtnYr: context.rprDutyAbtnYr,\n dbtrRprDt: context.dbtrRprDt,\n univGrdtYn: context.univGrdtYn,\n grdtDt: context.grdtDt,\n grdtUnivCd: context.grdtUnivCd,\n grdtUnivNm: context.grdtUnivNm,\n ovseRsdYn: context.ovseRsdYn,\n rsdcPno: context.rsdcPno,\n rsdcPnoAddr: context.rsdcPnoAddr,\n rsdcDtalAddr: context.rsdcDtalAddr,\n rsdcStsgLtnoDivCd: context.rsdcStsgLtnoDivCd,\n ctyCd: context.ctyCd,\n ctyNm: context.ctyNm,\n etcMttCten: context.etcMttCten,\n mblTno: context.mblTno,\n emailAddr: context.emailAddr,\n wkplEan: context.wkplEan,\n spoEan: context.spoEan,\n spoFrnrYn: context.spoFrnrYn,\n spoNm: context.spoNm,\n spoOvseRsdYn: context.spoOvseRsdYn,\n spoRsdcPno: context.spoRsdcPno,\n spoRsdcPnoAddr: context.spoRsdcPnoAddr,\n spoRsdcDtalAddr: context.spoRsdcDtalAddr,\n spoRsdcStsgLtnoDivCd: context.spoRsdcStsgLtnoDivCd,\n spoCtyCd: context.spoCtyCd,\n spoCtyNm: context.spoCtyNm,\n spoWkplEan: context.spoWkplEan,\n indvInfoOferAgmtYn: context.indvInfoOferAgmtYn,\n finInfoOferAgmtYn: context.finInfoOferAgmtYn,\n dbtrNrgrRprClssCd: context.dbtrNrgrRprClssCd,\n careerList: context.careerList,\n spoCareerList: context.spoCareerList,\n };\n },\n }),\n getCIInfo: (accessToken: string | null) =>\n queryOptions({\n queryKey: [dpgQueryOptionsKeys.getCIInfo],\n queryFn: AUTH_API.getMemberVerification,\n enabled: !!accessToken,\n select: ({ context }) => {\n assert(context, 'context is not valid');\n\n return {\n ci: context.ci,\n };\n },\n }),\n address: (custCi: string) =>\n queryOptions({\n queryKey: [dpgQueryOptionsKeys.address, custCi],\n queryFn: () => DPG_API.getAddress({ requestBody: { custCi } }),\n select: ({ context }) => {\n assert(context, 'context is not valid');\n\n return {\n address: context.pnoAddr,\n addressDetail: context.dtalAddr,\n zonecode: context.pno,\n addressType: context.stsgLtnoDivCd,\n };\n },\n }),\n};\n","import { queryOptions } from '@tanstack/react-query';\n\nimport { AUTH_API } from '@/api';\nimport { queryKeys } from '@/constants/query-keys';\n\ninterface FamilyListOptionsProps {\n isLogin: boolean;\n}\n\nexport const familyQueryOptions = {\n list: ({ isLogin }: FamilyListOptionsProps) =>\n queryOptions({\n queryKey: [queryKeys.familyList],\n queryFn: AUTH_API.getFamilyList,\n select: ({ context }) => {\n const { wello_member_family_list, total_policy_count } = context ?? {};\n\n return {\n familyList: wello_member_family_list,\n totalPolicyCount: total_policy_count,\n };\n },\n enabled: isLogin,\n }),\n};\n","import { infiniteQueryOptions, queryOptions } from '@tanstack/react-query';\n\nimport {\n AuthInfiniteQueryOptions,\n AuthQueryOptions,\n UnauthInfiniteQueryOptions,\n UnauthQueryOptions,\n} from '@/api/v2';\n\ninterface HometownNewsFeedbackOptionsProps {\n id: number | undefined;\n isLogin?: boolean;\n}\n\ninterface HometownNewsListOptionsProps {\n keyword?: string;\n codeRegion?: string;\n codeSubRegion?: string;\n isLogin: boolean | undefined;\n hometownNewsSort?: 'LATEST' | 'POPULARITY';\n}\n\nexport const hometownNewsQueryOptions = {\n list: ({\n keyword,\n isLogin,\n codeRegion,\n codeSubRegion,\n hometownNewsSort = 'LATEST',\n }: HometownNewsListOptionsProps) => {\n const INITIAL_PAGE = 1;\n const PAGE_SIZE = 20;\n\n return infiniteQueryOptions({\n ...(isLogin\n ? AuthInfiniteQueryOptions['get:/wello/v2/auth/hometown-news']\n : UnauthInfiniteQueryOptions['get:/wello/v2/allows/hometown-news'])({\n query: {\n page: INITIAL_PAGE,\n size: PAGE_SIZE,\n code_region: codeRegion,\n code_sub_region: codeSubRegion,\n hometown_news_sort: hometownNewsSort,\n search_keyword: keyword,\n },\n }),\n enabled: isLogin !== undefined,\n select: (data) => ({\n pages: data.pages.map(({ context }) => ({\n contents: context?.contents?.map((news) => {\n return {\n id: news.hometown_news_id,\n idIdx: news.hometown_news_id_idx,\n title: news.hometown_news_name,\n daysAgo: news.days_ago,\n thumbnail: news.thumbnail,\n commentCount: news.comment_count,\n isBookmarked: news.wishlist_yn,\n bookmarkCount: news.wishlist_count,\n dateText: news.published_at,\n summary: news.summary,\n thumbUpCount: news.like_count,\n isThumbUp: news.like_yn,\n };\n }),\n totalCount: context?.total_count,\n })),\n pageParams: data.pageParams,\n }),\n getNextPageParam: (lastPage) => {\n if (lastPage.context?.total_count && lastPage.context.page) {\n const nextPage = lastPage.context.page + 1;\n const maxPage = Math.ceil(lastPage.context.total_count / PAGE_SIZE);\n\n return nextPage <= maxPage ? nextPage : undefined;\n }\n },\n initialPageParam: INITIAL_PAGE,\n });\n },\n feedback: ({ id, isLogin }: HometownNewsFeedbackOptionsProps) =>\n queryOptions({\n ...(isLogin\n ? AuthQueryOptions[\n 'get:/wello/v2/auth/hometown-news/feedback/{hometown_news_id}'\n ]\n : UnauthQueryOptions[\n 'get:/wello/v2/allows/hometown-news/feedback/{hometown_news_id}'\n ])({\n path: {\n hometown_news_id: id!,\n },\n }),\n enabled: isLogin !== undefined && !!id,\n }),\n};\n","import { assert, createUuid } from '@common/utils';\nimport { queryOptions } from '@tanstack/react-query';\nimport ky from 'ky';\n\nimport { UNAUTH_API } from '@/api';\n\nexport const metaCodeQueryOptionsKeys = {\n regionList: createUuid(),\n subRegionList: createUuid(),\n geolocation: createUuid(),\n geoCodingByRegionCode: createUuid(),\n};\n\nexport const metaCodeQueryOptions = {\n regionList: queryOptions({\n queryKey: [metaCodeQueryOptionsKeys.regionList],\n queryFn: () => UNAUTH_API.getMetaCode({ metaCode: 'C01' }),\n select: (data) =>\n data.context?.meta_code_all_list?.[0].meta_sub_code_list?.map((item) => ({\n label: item.value,\n value: item.code,\n })),\n }),\n\n subRegionList: (regionCode?: string) =>\n queryOptions({\n queryKey: [metaCodeQueryOptionsKeys.subRegionList, regionCode],\n queryFn: () => {\n assert(regionCode, '지역코드가 없습니다.');\n\n return UNAUTH_API.getMetaCode({ metaCode: regionCode });\n },\n enabled: !!regionCode,\n select: ({ context }) => {\n const [{ meta_sub_code_list }] = context?.meta_code_all_list ?? [];\n\n const allItem = meta_sub_code_list?.find((item) =>\n item.value.includes('전체'),\n );\n\n const options = meta_sub_code_list?.filter(\n (item) => !item.value.includes('전체'),\n );\n\n if (allItem && options) {\n options.unshift(allItem);\n }\n\n return options?.map(({ value, code }) => ({\n label: value,\n value: code,\n }));\n },\n }),\n\n geolocation: () =>\n queryOptions({\n queryKey: [metaCodeQueryOptionsKeys.geolocation],\n queryFn: async () => {\n const ip = await ky.get('https://api.ip.pe.kr').text();\n\n return UNAUTH_API.getGeolocation({ ip });\n },\n select: ({ context }) => context,\n }),\n\n geocodingByCode: ({\n regionCode,\n subRegionCode,\n }: Partial[number]>) =>\n queryOptions({\n queryKey: [\n metaCodeQueryOptionsKeys.geoCodingByRegionCode,\n regionCode,\n subRegionCode,\n ],\n enabled: !!regionCode && !!subRegionCode,\n queryFn: () => {\n assert(regionCode, '지역코드가 없습니다.');\n assert(subRegionCode, '지역코드가 없습니다.');\n\n return UNAUTH_API.getGeocodingByRegionCode({\n regionCode,\n subRegionCode,\n });\n },\n }),\n};\n","import { redirect } from 'next/navigation';\n\ninterface RedirectDetailPageProps {\n returnCode?: number;\n pathname: string;\n}\n\nexport const redirectDetailPage = ({\n returnCode,\n pathname,\n}: RedirectDetailPageProps) => {\n switch (returnCode) {\n case 24040:\n return redirect(`${pathname}?toast=존재하지 않는 페이지입니다.`);\n case 20019:\n case 24041:\n return redirect(`${pathname}?toast=숨김 처리된 페이지입니다.`);\n }\n};\n","import { assert } from '@common/utils';\nimport { withTimeout } from 'es-toolkit';\n\nimport { ROUTES } from '@/constants';\nimport { redirectDetailPage } from '@/utils/redirectDetailPage';\n\nimport { GPT_PROMPT_TYPE, UNAUTH_API } from '.';\nimport { UnauthApi } from './v2';\n\nexport const getPolicyDetail = async (metaPolicyIdIdx: string) => {\n const { data } = await UnauthApi[\n 'get:/wello/v2/allows/policy/{meta_policy_id_idx}'\n ]({\n path: {\n meta_policy_id_idx: metaPolicyIdIdx,\n },\n });\n\n redirectDetailPage({\n pathname: ROUTES.HOME.pathname,\n returnCode: data?.return_code,\n });\n\n const context = data?.context;\n\n assert(context);\n\n let relatedInfo = '';\n if (context.apply_documents)\n relatedInfo += `○ 구비서류\\n - ${context.apply_documents\n ?.replaceAll('\\n', '\\n - ')\n .replaceAll('||', '\\n - ')}`;\n\n if (context.base_rule)\n relatedInfo += `${relatedInfo ? '\\n' : ''}○ 관계법령\\n - ${context.base_rule\n ?.replaceAll('\\n', '\\n - ')\n .replaceAll('||', '\\n - ')}`;\n\n if (!relatedInfo) relatedInfo = '- 관련 정보 없음';\n\n const [summaryString, keywordString] = await withTimeout(async () => {\n let summaryString = context.ai_summary;\n let keywordString = context.ai_seo_keyword;\n\n [summaryString, keywordString] = await Promise.all([\n summaryString\n ? summaryString\n : UNAUTH_API.updatePolicyAiInfo({\n metaPolicyIdIdx,\n requestBody: {\n gpt_prompt_type: GPT_PROMPT_TYPE.SUMMARY,\n },\n }).then((res) => res.context?.result),\n keywordString\n ? keywordString\n : UNAUTH_API.updatePolicyAiInfo({\n metaPolicyIdIdx,\n requestBody: {\n gpt_prompt_type: GPT_PROMPT_TYPE.KEYWORD,\n },\n }).then((res) => res.context?.result),\n ]);\n\n return [summaryString, keywordString];\n }, 3_000).catch(() => []);\n\n const aiSummaryList = summaryString\n ?.split('***')\n .map((s) => s.trim())\n .slice(1);\n\n const aiKeywordList = keywordString\n ?.split(' | ')\n .map((s) => s.trim())\n //! 한자 제외\n .filter((keyword) => !/[\\u4E00-\\u9FFF]/.test(keyword));\n\n return {\n id: context.id,\n idx: context.meta_policy_id_idx,\n policyName: context.policy_name,\n descAgeList: context.desc_age_list,\n dday: context.dday,\n policyApplyId: context.wello_policy_apply_id,\n provisionTypeList: context.provision_type_list,\n applyTypeList: context.apply_type_list,\n agencyLogo: context.agency_logo,\n agency: context.agency,\n dueDateBetween: context.due_date_between,\n supportBenefit: context.support_benefit,\n descTarget: context.desc_target,\n descSupport: context.desc_support,\n descProvision: context.desc_provision,\n relatedInfo,\n policyBannerTypeList: context.policy_banner_type_list,\n descApply: context.desc_apply,\n agencyTel: context.agency_tel,\n infoUrl: context.info_url,\n aiSummaryList,\n aiKeywordList,\n policyApplicationTypeCd: context.policy_application_type_cd,\n detailPolicyApplicationTypeCd: context.detail_policy_application_type_cd,\n };\n};\n\nexport type PolicyDetail = Awaited>;\n","import { createUuid } from '@common/utils';\nimport {\n infiniteQueryOptions,\n keepPreviousData,\n queryOptions,\n} from '@tanstack/react-query';\n\nimport { getPolicyDetail } from '@/api/getPolicyDetail';\nimport {\n AuthInfiniteQueryOptions,\n AuthQueryOptions,\n UnauthInfiniteQueryOptions,\n UnauthQueryOptions,\n} from '@/api/v2';\n\nexport const policyQueryOptionsKeys = {\n policyDetail: createUuid(),\n};\n\ninterface PolicyOptionsProps {\n id: number;\n isLogin: boolean | undefined;\n}\n\ninterface SearchOptionsProps {\n codeRegion?: string;\n codeSubRegion?: string;\n keyword?: string;\n isLogin?: boolean | undefined;\n descAgeList?: string[];\n codeProvisionTypeList?: string[];\n policySort?: 'LATEST' | 'POPULARITY';\n}\n\nexport interface PolicySearchItem {\n name: string;\n id: number;\n}\n\nexport const policyQueryOptions = {\n feedback: ({ id, isLogin }: PolicyOptionsProps) => {\n const options = isLogin\n ? AuthQueryOptions['get:/wello/v2/auth/policy/feedback/{policy_id}']\n : UnauthQueryOptions['get:/wello/v2/allows/policy/feedback/{policy_id}'];\n\n return queryOptions({\n ...options({\n path: {\n policy_id: id,\n },\n }),\n enabled: isLogin !== undefined,\n placeholderData: keepPreviousData,\n select: ({ context }) => ({\n readingCount: context?.reading_count ?? 0,\n hasNewComment: context?.new_comment_yn ?? false,\n isThumbUp: context?.like_yn ?? false,\n thumbUpCount: context?.like_count ?? 0,\n isBookmarked: context?.wishlist_yn,\n bookmarkCount: context?.wishlist_count ?? 0,\n commentCount: context?.comment_count ?? 0,\n likeCount: context?.like_count ?? 0,\n }),\n });\n },\n policyDetail: (idx: string) =>\n queryOptions({\n queryKey: [policyQueryOptionsKeys.policyDetail, idx],\n queryFn: () => getPolicyDetail(idx),\n }),\n search: ({\n isLogin,\n keyword,\n codeRegion,\n codeSubRegion,\n descAgeList,\n codeProvisionTypeList,\n policySort = 'LATEST',\n }: SearchOptionsProps) => {\n const INITIAL_PAGE = 1;\n const PAGE_SIZE = 20;\n\n const options = isLogin\n ? AuthInfiniteQueryOptions['get:/wello/v2/auth/policy']\n : UnauthInfiniteQueryOptions['get:/wello/v2/allows/policy'];\n\n return infiniteQueryOptions({\n ...options({\n query: {\n page: INITIAL_PAGE,\n size: PAGE_SIZE,\n codeProvisionTypeList,\n codeRegion,\n codeSubRegion,\n descAgeList,\n policySort,\n searchKeyword: keyword,\n },\n }),\n enabled: isLogin !== undefined,\n getNextPageParam: (lastPage) => {\n const { page: currentPage, total_count: totalCount } =\n lastPage.context ?? {};\n\n if (!totalCount || !currentPage) return;\n\n const nextPage = currentPage + 1;\n const totalPageCount = Math.ceil(totalCount / PAGE_SIZE);\n\n if (nextPage <= totalPageCount) return nextPage;\n },\n initialPageParam: INITIAL_PAGE,\n placeholderData: keepPreviousData,\n });\n },\n};\n","import { infiniteQueryOptions } from '@tanstack/react-query';\n\nimport { AuthInfiniteQueryOptions, UnauthInfiniteQueryOptions } from '@/api/v2';\n\ninterface PolicyInfoListOptionsProps {\n isLogin: boolean | undefined;\n keyword?: string;\n policySort?: 'LATEST' | 'POPULARITY';\n codeProvisionType?: string;\n}\n\nexport const policyInfoQueryOptions = {\n list: ({\n isLogin,\n policySort = 'LATEST',\n keyword,\n codeProvisionType,\n }: PolicyInfoListOptionsProps) => {\n const INITIAL_PAGE = 1;\n const PAGE_SIZE = 20;\n\n return infiniteQueryOptions({\n ...(isLogin\n ? AuthInfiniteQueryOptions['get:/wello/v2/auth/contents/best']\n : UnauthInfiniteQueryOptions['get:/wello/v2/allows/contents/best'])({\n query: {\n size: PAGE_SIZE,\n search_keyword: keyword,\n best_contents_sort: policySort,\n code_provision_type: codeProvisionType,\n page: INITIAL_PAGE,\n },\n }),\n getNextPageParam: ({ context }) => {\n const { total_count, page } = context ?? {};\n\n if (!total_count || !page) return;\n\n const nextPage = page + 1;\n const totalPageCount = Math.ceil(total_count / PAGE_SIZE);\n\n if (nextPage <= totalPageCount) return nextPage;\n },\n select: ({ pages }) =>\n pages.map(({ context }) => ({\n policyInfoList: context?.contents?.map(\n ({ provision_type, ...content }) => {\n const provisionTypeList: string[] = [];\n\n for (const key in provision_type) {\n const value = provision_type[key];\n if (value) provisionTypeList.push(value);\n }\n\n return { ...content, provisionTypeList };\n },\n ),\n totalCount: context?.total_count,\n })),\n initialPageParam: INITIAL_PAGE,\n });\n },\n};\n","import { createUuid,CustomError } from '@common/utils';\nimport { infiniteQueryOptions, queryOptions } from '@tanstack/react-query';\n\nimport { AUTH_API, UNAUTH_API } from '@/api';\n\nimport { EXCEPTED_HIDDEN_CODE } from './filter';\n\nexport const situationQueryKeys = {\n hospitalList: createUuid(),\n hospitalDetailLive: createUuid(),\n medicalPolicyInfinityList: createUuid(),\n emergencyRoomCountOfRegion: createUuid(),\n};\n\nexport const situationQueryOptions = {\n hospitalInfinityList: ({\n page,\n size,\n time,\n ...restParams\n }: Parameters[number]) =>\n infiniteQueryOptions({\n queryKey: [situationQueryKeys.hospitalList, page, size, time, restParams],\n queryFn: ({ pageParam }) =>\n UNAUTH_API.getEmergencyRoomList({\n page: pageParam,\n size,\n time: time === 24 ? 0 : time,\n ...restParams,\n }),\n getNextPageParam: (lastPage) => {\n const { page: currentPage, has_next } = lastPage.context ?? {};\n\n if (!has_next || !currentPage) return;\n\n return currentPage + 1;\n },\n initialPageParam: page,\n }),\n\n HospitalList: ({\n time,\n ...restParams\n }: Omit<\n Parameters[number],\n 'page' | 'size'\n >) => {\n return queryOptions({\n queryKey: [situationQueryKeys.hospitalList, restParams, time],\n queryFn: () =>\n UNAUTH_API.getEmergencyRoomList({\n ...restParams,\n time: time === 24 ? 0 : time,\n page: 1,\n size: 999,\n }),\n });\n },\n\n hospitalDetailLive: (emergencyRoomId: number) =>\n queryOptions({\n refetchInterval: 60_000,\n queryKey: [situationQueryKeys.hospitalDetailLive, emergencyRoomId],\n queryFn: () =>\n UNAUTH_API.getEmergencyRoomLive({\n emergencyRoomId,\n }),\n select: ({ context }) => ({\n roomCount: context?.hv36,\n babyRoomCount: context?.hv37,\n ambulanceUsable: context?.hvamyn,\n ctUsable: context?.hvctayn,\n mriUsable: context?.hvmriayn,\n doctorName: context?.hvdnm,\n }),\n }),\n\n medicalPolicyInfinityList: ({\n page,\n size,\n isLogin,\n ...restParams\n }: Parameters[number] & {\n isLogin?: boolean;\n }) =>\n infiniteQueryOptions({\n queryKey: [\n situationQueryKeys.medicalPolicyInfinityList,\n page,\n size,\n restParams,\n isLogin,\n ],\n queryFn: ({ pageParam }) => {\n const params = {\n page: pageParam,\n size,\n ...restParams,\n };\n\n return isLogin\n ? AUTH_API.getMedicalPolicyList(params)\n : UNAUTH_API.getMedicalPolicyList(params);\n },\n getNextPageParam: (lastPage) => {\n const { page: currentPage, total_count } = lastPage.context ?? {};\n\n if (!total_count || !currentPage)\n throw new CustomError({\n return_message: '페이지 수 데이터가 없습니다.',\n });\n\n const hasNextPage = currentPage < total_count / size;\n\n if (!hasNextPage) return;\n\n return currentPage + 1;\n },\n initialPageParam: page,\n enabled: isLogin !== undefined,\n }),\n emergencyRoomCountOfRegion: ({\n time,\n ...restParams\n }: Parameters[number]) =>\n queryOptions({\n queryFn: () =>\n UNAUTH_API.getEmergencyRoomCountOfRegion({\n ...restParams,\n time: time === 24 ? 0 : time,\n }),\n queryKey: [\n situationQueryKeys.emergencyRoomCountOfRegion,\n time,\n restParams,\n ],\n select: ({ context }) => {\n const sejongIndex = context?.contents.find(\n ({ code_sub_region }) =>\n code_sub_region === EXCEPTED_HIDDEN_CODE.REGION_SEJONG_ALL,\n );\n\n if (sejongIndex) {\n sejongIndex.sub_region = '세종시';\n }\n\n return context?.contents;\n },\n }),\n};\n","export * from './auth';\nexport * from './dpg';\nexport * from './family';\nexport * from './hometown-news';\nexport * from './meta-code';\nexport * from './policy';\nexport * from './policy-info';\nexport * from './situation';\n","import { createUuid } from '@common/utils';\nimport {\n infiniteQueryOptions,\n keepPreviousData,\n queryOptions,\n} from '@tanstack/react-query';\n\nimport { AUTH_API, POINT_ACTIVITY, UNAUTH_API } from '@/api';\n\nexport const pointOptionsKeys = {\n /** 본인 인증된 사용자 정보 */\n memberAccount: createUuid(),\n /** 포인트 정보 */\n point: createUuid(),\n /** 출석 내역 조회 */\n pointAttendance: createUuid(),\n /** 초대 수 */\n invitedMemberCount: createUuid(),\n /** 포인트 입출금 내역 리스트 */\n pointHistoryInfiniteList: createUuid(),\n /** 은행 리스트 */\n bankList: createUuid(),\n /** 포인트 예산 조회 */\n pointBudget: createUuid(),\n /** 포인트 시세 조회 */\n pointPrice: createUuid(),\n /** 내 포인트 조회 */\n myPointBudget: createUuid(),\n};\n\nexport const pointOptions = {\n //* 본인 인증된 사용자 정보\n memberAccount: () =>\n queryOptions({\n queryFn: () => AUTH_API.getMemberAccount(),\n queryKey: [pointOptionsKeys.memberAccount],\n placeholderData: keepPreviousData,\n select: ({ context }) => {\n return {\n accountNumber: context?.account_number,\n accountOwner: context?.account_owner,\n bankId: context?.bank_id,\n birthDate: context?.birth_date,\n memberAccountId: context?.member_account_id,\n };\n },\n }),\n //* point 정보, 특히 totalPoints\n point: () =>\n queryOptions({\n queryFn: () => AUTH_API.getDetailPointBalance(),\n queryKey: [pointOptionsKeys.pointAttendance],\n placeholderData: keepPreviousData,\n select: ({ context }) => {\n return {\n totalPoints: context?.total_points,\n debitablePoints: context?.debitable_points,\n memberOauthId: context?.member_oauth_id,\n };\n },\n //* 매번 요청 최신화\n staleTime: 0,\n gcTime: 0,\n }),\n //* 은행 리스트\n bankList: () =>\n queryOptions({\n queryFn: () => UNAUTH_API.getBankList(),\n queryKey: [pointOptionsKeys.bankList],\n placeholderData: keepPreviousData,\n select: ({ context }) => {\n return {\n contents: context?.contents,\n };\n },\n }),\n // * 출석 내역 조회\n pointAttendance: () =>\n queryOptions({\n queryFn: AUTH_API.getListPointAttendance,\n queryKey: [pointOptionsKeys.pointAttendance],\n placeholderData: keepPreviousData,\n staleTime: 0,\n select: ({ context }) => {\n return {\n attendanceList: context?.attendance_list.map((item) => ({\n checked: item.attendance_yn,\n date: item.date,\n })),\n count: context?.attendance_count,\n month: context?.month,\n point: context?.total_points,\n };\n },\n }),\n //* 초대 수\n invitedMemberCount: () =>\n queryOptions({\n queryFn: () => AUTH_API.getInvitedMemberCount(),\n queryKey: [pointOptionsKeys.invitedMemberCount],\n placeholderData: keepPreviousData,\n select: ({ context }) => {\n return {\n invitedMemberCount: context?.invited_member_count,\n };\n },\n }),\n //* 포인트 입출금 내역 리스트\n pointHistoryInfiniteList: (\n params: Pick<\n Parameters[number],\n 'activityType'\n >,\n ) =>\n infiniteQueryOptions({\n queryKey: [\n pointOptionsKeys.pointHistoryInfiniteList,\n params.activityType,\n ],\n queryFn: ({ pageParam }) =>\n AUTH_API.getPointHistoryList({\n activityType: params.activityType,\n page: pageParam,\n size: 50,\n }),\n getNextPageParam: (lastPage, allPages) => {\n // list length가 0이 아니면 다음 page 요청\n if (lastPage.context && lastPage.context.contents.length > 0) {\n return allPages.length + 1;\n }\n\n return null; // 나머지 조건에는 종료\n },\n initialPageParam: 1,\n staleTime: 0,\n gcTime: 0,\n }),\n //* 포인트 예산 조회\n pointBudget: () =>\n queryOptions({\n queryFn: () => UNAUTH_API.getPointBudget(),\n queryKey: [pointOptionsKeys.pointBudget],\n placeholderData: keepPreviousData,\n staleTime: 0,\n select: ({ context }) => {\n const weeklyBudget = context?.budget_point ?? 0;\n const weeklyAccumulatedPoint = context?.accumulated_point ?? 0;\n const benefitCount = context?.benefit_count ?? 0;\n const weeklyAccumulatedPointPercentage =\n (weeklyAccumulatedPoint / weeklyBudget) * 100;\n const weeklyRemainingPoint = context?.remain_point ?? 0;\n\n let weeklyAccumulatedPointText = '';\n\n if (10_000 <= weeklyAccumulatedPoint) {\n weeklyAccumulatedPointText = `${Math.floor(weeklyAccumulatedPoint / 10_000).toLocaleString()}만`;\n } else {\n weeklyAccumulatedPointText = `${weeklyAccumulatedPoint.toLocaleString()}`;\n }\n\n let weeklyBudgetText = '';\n\n if (10_000 <= weeklyBudget) {\n weeklyBudgetText = `${(weeklyBudget / 10_000).toLocaleString()}만`;\n } else {\n weeklyBudgetText = `${weeklyBudget.toLocaleString()}`;\n }\n\n return {\n weeklyBudget,\n weeklyAccumulatedPointPercentage,\n weeklyRemainingPoint,\n weeklyAccumulatedPointText,\n weeklyBudgetText,\n benefitCount,\n };\n },\n }),\n //* 포인트 시세 조회\n pointPrice: () =>\n queryOptions({\n queryFn: () => UNAUTH_API.getPointPrice(),\n queryKey: [pointOptionsKeys.pointPrice],\n placeholderData: keepPreviousData,\n staleTime: Infinity,\n gcTime: Infinity,\n select: ({ context }) => {\n const pointEarning = context?.point_earning;\n\n if (!pointEarning) return;\n\n const activity = Object.values(POINT_ACTIVITY).reduce(\n (acc, cur) => {\n acc[cur] = pointEarning[cur];\n\n return acc;\n },\n {} as Record,\n );\n\n return activity;\n },\n }),\n myPointBudget: () =>\n queryOptions({\n queryFn: () => AUTH_API.getPointBudget(),\n queryKey: [pointOptionsKeys.myPointBudget],\n select: ({ context }) => {\n const pointMission = {\n isSaveFilter: context?.save_filter_yn ?? false,\n isAttendance: context?.today_attendance_yn ?? false,\n isInvite: context?.today_invite_yn ?? false,\n isWrite: context?.today_write_yn ?? false,\n };\n\n const completedMission: (keyof typeof pointMission)[] = [];\n const leftMission: (keyof typeof pointMission)[] = [];\n\n Object.entries(pointMission).forEach(([key, value]) => {\n const missionKey = key as keyof typeof pointMission;\n if (value) {\n completedMission.push(missionKey);\n } else {\n leftMission.push(missionKey);\n }\n });\n\n return {\n eventCombo: context?.event_count ?? 0,\n todayAccumulatedPoint: context?.today_accumulated_point ?? 0,\n completedMission,\n leftMission,\n pointMission,\n totalWithdrawalCount: context?.total_withdrawal_count ?? 0,\n };\n },\n staleTime: 0,\n placeholderData: keepPreviousData,\n }),\n};\n","// extracted by mini-css-extract-plugin\nmodule.exports = {\"contents\":\"page_contents__ASCLL\"};","// extracted by mini-css-extract-plugin\nmodule.exports = {\"thumbnail\":\"HometownNewsDetailBottomDrawer_thumbnail__UaaIk\"};","class AbortError extends Error {\n constructor(message = 'The operation was aborted') {\n super(message);\n this.name = 'AbortError';\n }\n}\n\nexport { AbortError };\n","import { AbortError } from '../error/AbortError.mjs';\n\nfunction delay(ms, { signal } = {}) {\n return new Promise((resolve, reject) => {\n const abortError = () => {\n reject(new AbortError());\n };\n const abortHandler = () => {\n clearTimeout(timeoutId);\n abortError();\n };\n if (signal?.aborted) {\n return abortError();\n }\n const timeoutId = setTimeout(() => {\n signal?.removeEventListener('abort', abortHandler);\n resolve();\n }, ms);\n signal?.addEventListener('abort', abortHandler, { once: true });\n });\n}\n\nexport { delay };\n","class TimeoutError extends Error {\n constructor(message = 'The operation was timed out') {\n super(message);\n this.name = 'TimeoutError';\n }\n}\n\nexport { TimeoutError };\n","import { delay } from './delay.mjs';\nimport { TimeoutError } from '../error/TimeoutError.mjs';\n\nasync function timeout(ms) {\n await delay(ms);\n throw new TimeoutError();\n}\n\nexport { timeout };\n","import { timeout } from './timeout.mjs';\n\nasync function withTimeout(run, ms) {\n return Promise.race([run(), timeout(ms)]);\n}\n\nexport { withTimeout };\n","import { invariant } from '../../utils/errors.mjs';\nimport { setValues } from '../../render/utils/setters.mjs';\nimport { animateVisualElement } from '../interfaces/visual-element.mjs';\n\nfunction stopAnimation(visualElement) {\n visualElement.values.forEach((value) => value.stop());\n}\n/**\n * @public\n */\nfunction animationControls() {\n /**\n * Track whether the host component has mounted.\n */\n let hasMounted = false;\n /**\n * A collection of linked component animation controls.\n */\n const subscribers = new Set();\n const controls = {\n subscribe(visualElement) {\n subscribers.add(visualElement);\n return () => void subscribers.delete(visualElement);\n },\n start(definition, transitionOverride) {\n invariant(hasMounted, \"controls.start() should only be called after a component has mounted. Consider calling within a useEffect hook.\");\n const animations = [];\n subscribers.forEach((visualElement) => {\n animations.push(animateVisualElement(visualElement, definition, {\n transitionOverride,\n }));\n });\n return Promise.all(animations);\n },\n set(definition) {\n invariant(hasMounted, \"controls.set() should only be called after a component has mounted. Consider calling within a useEffect hook.\");\n return subscribers.forEach((visualElement) => {\n setValues(visualElement, definition);\n });\n },\n stop() {\n subscribers.forEach((visualElement) => {\n stopAnimation(visualElement);\n });\n },\n mount() {\n hasMounted = true;\n return () => {\n hasMounted = false;\n controls.stop();\n };\n },\n };\n return controls;\n}\n\nexport { animationControls };\n","import { animationControls } from './animation-controls.mjs';\nimport { useConstant } from '../../utils/use-constant.mjs';\nimport { useIsomorphicLayoutEffect } from '../../utils/use-isomorphic-effect.mjs';\n\n/**\n * Creates `AnimationControls`, which can be used to manually start, stop\n * and sequence animations on one or more components.\n *\n * The returned `AnimationControls` should be passed to the `animate` property\n * of the components you want to animate.\n *\n * These components can then be animated with the `start` method.\n *\n * ```jsx\n * import * as React from 'react'\n * import { motion, useAnimation } from 'framer-motion'\n *\n * export function MyComponent(props) {\n * const controls = useAnimation()\n *\n * controls.start({\n * x: 100,\n * transition: { duration: 0.5 },\n * })\n *\n * return \n * }\n * ```\n *\n * @returns Animation controller with `start` and `stop` methods\n *\n * @public\n */\nfunction useAnimationControls() {\n const controls = useConstant(animationControls);\n useIsomorphicLayoutEffect(controls.mount, []);\n return controls;\n}\nconst useAnimation = useAnimationControls;\n\nexport { useAnimation, useAnimationControls };\n"],"names":["Promise","resolve","then","__webpack_require__","bind","t","_path","_extends","Object","assign","n","e","arguments","length","r","hasOwnProperty","call","apply","components_SvgTextfieldclose","createSvgComponent","props","react","createElement","xmlns","width","height","fill","viewBox","fillRule","d","clipRule","DownloadAppBanner","webviewType","osType","useDeviceInfoStore","useShallow","state","isVisible","setIsVisible","useState","isMobileWeb","includes","useEffect","lastClosed","localStorage","getItem","now","dayjs","isSame","react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__","jsx","Portal","renderTo","document","getElementById","SELECTOR","PAGE_NAV_WRAPPER","jsxs","aside","className","div","Image","alt","quality","src","Button","asChild","dimensions","a","href","DEFAULT_META","appLinks","ios","url","android","rel","target","button","type","onClick","setItem","toISOString","SvgDelete","name","HometownNewsBookmarkButton","idIdx","param","isLogin","useAuthStore","data","id","useQuery","UnauthQueryOptions","path","hometown_news_id_idx","select","context","detail_hometown_news","hometown_news_id","feedbackQuery","hometownNewsQueryOptions","feedback","isBookmarked","wishlist_yn","bookmarkCount","wishlist_count","bookmark","useBookmark","BookmarkButton","assert","bookmarkType","BOOKMARK_TYPE","HOMETOWN_NEWS","contentId","idx","HometownNewsDaysAgo","fallbackDate","daysAgo","clientDaysAgo","days_ago","p","useGetHometownNewsRelatedList","UNAUTH_API","getRelatedHometownNewsList","hometownNewsIdIdx","Error","list","contents","keyword","search_keyword","isNormalizedResponse","return_code","ERROR_CODE","HIDDEN","redirect","ROUTES","ERROR","pathname","code","HometownNewsDetailBottomDrawer","isShow","setIsShow","isWelloApp","useCheckWelloApp","isLoading","query","contents_type_cd","BANNER_CONTENTS_TYPE","HOMETOWN_BOTTOM_SHEET","banner","applyFileUrl","app_file_url","file_url","applyLinkUrl","app_link_url","link_url","window","sessionStorage","bottomDrawerShowPathname","STORAGE_KEY","HOMETOWN_NEWS_DETAIL_BOTTOM_DRAWER_PATH","setTimeout","location","removeItem","jsx_runtime","BottomDrawer","opened","MAIN_WRAPPER","onClose","HandleWrapper","Handle","Contents","section","title","subtitle","LazyImage","styles","theme","button_text","ImageFloatingBanner","isClose","setIsClose","isMounted","setIsMounted","HOMETOWN_FLOATING","mainElement","setMainElement","MAIN","createPortal","CustomLink","target_new_yn","preventDefault","SvgTextfieldclose","Fragment","MoreFloatingButton","contentAreaSelector","recommendHometownNewsSelector","contentsElement","setContentsElement","recommendHometownNewsElement","setRecommendHometownNewsElement","controls","useAnimation","updateIsShow","useCallback","undefined","isContentsScrollOveredHalf","offsetTop","offsetHeight","scrollTop","isRecommendHometownNewsListScrollOvered","handleScroll","throttle","addEventListener","removeEventListener","start","opacity","SHOW_BUTTON_OPACITY","INITIALIZE_BUTTON_OPACITY","bottom","SHOW_BUTTON_BOTTOM","INITIALIZE_BUTTON_BOTTOM","transition","duration","motion","animate","disabled","initial","scrollIntoView","behavior","block","SvgChevrondown","RecommendHometownNewsCard","publishedAt","thumbnail","routePathname","HOMETOWN_NEWS_DETAIL","h4","RecommendHometownNewsVerticalCard","cn","RecommendHometownNews","listSection","slice","swipeList","article","ul","map","li","news","hometown_news_name","published_at","Swiper","SWIPER_PROPS","SwiperSlide","slidesPerView","spaceBetween","mousewheel","modules","Mousewheel","MoreHometownNewsList","contentAreaId","hometownNewsRelatedList","hometownNewsRelatedListKeyword","hometownNewsRelated","useSuspenseQuery","queryKey","queryFn","getHometownNewsRelatedList","isNotRenderRelatedList","initialCommentData","initialComment","getCommentList","commentType","COMMENT_TYPE","recommendHometownNewsAreaId","createUuid","CommentListWithField","initialData","TopBanner","appFileUrl","appLinkUrl","fileUrl","linkUrl","targetNewYn","img","isDisplayed","setIsDisplayed","inView","useInView","onChange","ref","SvgBookmarkFill","CountUp","preserveValue","startOnMount","end","_g","_defs","components_SvgMailSend","node_modules_react","clipPath","CommentFieldContent","initOnFocus","replyTo","rootCommentId","parentCommentId","onSuccess","topDivider","isFocused","setIsFocused","startText","concat","protectedPrefix","minHtmlLength","tempContentForProtectedPrefix","useRef","editorRef","isSubmitDisabled","setIsSubmitDisabled","handleSubmitDisabled","text","editor","getText","blockSeparator","startsWith","initContent","current","commands","setContent","postComment","useMutation","mutationFn","AuthApi","body","comment_type_cd","content_id","parent_wello_comment_id","root_wello_comment_id","meta","reset","AuthQueryKey","CustomError","focus","router","useCustomRouter","CustomEditor","Provider","placeholder","onBlur","onCreate","onFocus","URL","push","LOGIN","withSearchParams","searchParams","search","onUpdate","content","getHTML","form","onSubmit","replace","trim","mutate","isPending","Spinner","size","SvgMailSend","ReplyCommentField","nickname","welloCommentId","nick_name","FixedArea","CommentField","withPortal","restProps","Container","BottomNavPortal","Suspense","fallback","policyTalkContentIdx","hasCommentField","commentFieldTopDivider","commentFieldClassName","useSearchParams","sortType","get","SEARCH_PARAMS","SORT_TYPE","COMMENT_SORT_TYPE","LATEST","params","Number","isNaN","PARENT_COMMENT_ID","isEnumValue","return_message","setPlaceholder","isCommentPage","toastPoint","useToastPoint","CommentList","onChangeCommentData","onClickEditButton","commentId","isRoot","POLICY_TALK_COMMENT_EDIT","contentIdx","COMMENT_EDIT","dynamicPath","onClickReplyButton","targetRoute","COMMENT_LIST","res","set","history","replaceState","scroller","scrollTo","COMMENT_LIST_TOP","smooth","containerId","offset","point","point_activity","result_yn","add_points","COMMENT_PREFIX","wello_comment_id","OPTIMISTIC_QUERY_KEYS","isQueryObject","obj","content_type_cd","queryClient","useQueryClient","request","requestHaptic","useHandleApp","MESSAGE_KEYWORD","HAPTIC","useMemo","debouncedMutate","debounce","options","getState","bookmarkQueries","getQueriesData","predicate","some","isSameEndpointQuery","forEach","key","isBaseKeyHasId","firstKey","StringKey","JSON","stringify","String","hasObjectKey","newData","cloneDeep","setQueryData","Array","isArray","updatedContents","item","index","idKey","targetObjectList","result","findTargetObject","changeBookmarkCount","changeBookmarkYn","page","onError","toast","span","Math","abs","toLocaleString","QUERY_KEYS_TO_INVALIDATE","invalidateQueries","toastPointError","message","error","errorCode","errorMessage","console","pointOptionsKeys","pointAttendance","pointHistoryInfiniteList","authQueryOptionsKeys","socialLogin","authQueryOptions","enabled","queryOptions","staleTime","redirectPath","parse","atob","access_token","ky","post","headers","URLSearchParams","redirect_uri","ENV","NEXT_PUBLIC_DOMAIN","grant_type","client_id","NEXT_PUBLIC_KAKAO_CLIENT_ID","json","loginType","LOGIN_TYPE","KAKAO","socialLoginToken","API_ROUTES","NAVER_AUTH","NAVER","GOOGLE_AUTH","GOOGLE","id_token","APPLE_TOKEN","APPLE","dpgQueryOptionsKeys","checkEligibility","agreement","notice","loanInterest","deptReportList","deptReportDetail","getCIInfo","address","dpgQueryOptions","ciToken","DPG_API","postEligibilityCheck","requestBody","custCi","retry","isTarget","canRprYn","isPeriodicReport","preRgrRprtYn","getAgreement","financialInfo","finInfoAgmtCten","personalInfo","indvInfoAgmtCten","year","baseYear","toString","noticeCten","getLoanInterestList","Date","getFullYear","basDt","loanAmtSum","loanBlmtSum","loanIntAmtSum","nyRcpDutyApmtAmt","nyRcpOvdAmt","nyRcpOvdAdAmt","nyRcpAmtSum","loanList","getReportHistoryList","total","totalCount","rprYr","dbtrRprClssCd","dbtrRprDt","getReportHistory","rprFltNm","rprDutyAbtnYr","univGrdtYn","grdtDt","grdtUnivCd","grdtUnivNm","ovseRsdYn","rsdcPno","rsdcPnoAddr","rsdcDtalAddr","rsdcStsgLtnoDivCd","ctyCd","ctyNm","etcMttCten","mblTno","emailAddr","wkplEan","spoEan","spoFrnrYn","spoNm","spoOvseRsdYn","spoRsdcPno","spoRsdcPnoAddr","spoRsdcDtalAddr","spoRsdcStsgLtnoDivCd","spoCtyCd","spoCtyNm","spoWkplEan","indvInfoOferAgmtYn","finInfoOferAgmtYn","dbtrNrgrRprClssCd","careerList","spoCareerList","AUTH_API","getMemberVerification","accessToken","ci","getAddress","pnoAddr","addressDetail","dtalAddr","zonecode","pno","addressType","stsgLtnoDivCd","familyQueryOptions","queryKeys","familyList","getFamilyList","wello_member_family_list","total_policy_count","totalPolicyCount","codeRegion","codeSubRegion","hometownNewsSort","infiniteQueryOptions","AuthInfiniteQueryOptions","UnauthInfiniteQueryOptions","code_region","code_sub_region","hometown_news_sort","pages","commentCount","comment_count","dateText","summary","thumbUpCount","like_count","isThumbUp","like_yn","total_count","pageParams","getNextPageParam","lastPage","nextPage","ceil","initialPageParam","AuthQueryOptions","metaCodeQueryOptionsKeys","regionList","subRegionList","geolocation","geoCodingByRegionCode","getMetaCode","metaCode","meta_code_all_list","meta_sub_code_list","label","value","regionCode","subRegionCode","getGeocodingByRegionCode","redirectDetailPage","returnCode","getPolicyDetail","metaPolicyIdIdx","UnauthApi","meta_policy_id_idx","HOME","relatedInfo","apply_documents","replaceAll","base_rule","summaryString","keywordString","withTimeout","ai_summary","ai_seo_keyword","all","updatePolicyAiInfo","gpt_prompt_type","GPT_PROMPT_TYPE","SUMMARY","KEYWORD","catch","aiSummaryList","split","s","aiKeywordList","filter","test","policyName","policy_name","descAgeList","desc_age_list","dday","policyApplyId","wello_policy_apply_id","provisionTypeList","provision_type_list","applyTypeList","apply_type_list","agencyLogo","agency_logo","agency","dueDateBetween","due_date_between","supportBenefit","support_benefit","descTarget","desc_target","descSupport","desc_support","descProvision","desc_provision","policyBannerTypeList","policy_banner_type_list","descApply","desc_apply","agencyTel","agency_tel","infoUrl","info_url","policyApplicationTypeCd","policy_application_type_cd","detailPolicyApplicationTypeCd","detail_policy_application_type_cd","policyQueryOptionsKeys","policyDetail","policyQueryOptions","policy_id","placeholderData","keepPreviousData","readingCount","reading_count","hasNewComment","new_comment_yn","likeCount","codeProvisionTypeList","policySort","searchKeyword","currentPage","policyInfoQueryOptions","codeProvisionType","best_contents_sort","code_provision_type","policyInfoList","provision_type","situationQueryKeys","hospitalList","hospitalDetailLive","medicalPolicyInfinityList","emergencyRoomCountOfRegion","situationQueryOptions","hospitalInfinityList","time","restParams","pageParam","getEmergencyRoomList","has_next","HospitalList","refetchInterval","emergencyRoomId","getEmergencyRoomLive","roomCount","hv36","babyRoomCount","hv37","ambulanceUsable","hvamyn","ctUsable","hvctayn","mriUsable","hvmriayn","doctorName","hvdnm","getMedicalPolicyList","getEmergencyRoomCountOfRegion","sejongIndex","find","EXCEPTED_HIDDEN_CODE","REGION_SEJONG_ALL","sub_region","memberAccount","invitedMemberCount","bankList","pointBudget","pointPrice","myPointBudget","pointOptions","getMemberAccount","accountNumber","account_number","accountOwner","account_owner","bankId","bank_id","birthDate","birth_date","memberAccountId","member_account_id","getDetailPointBalance","totalPoints","total_points","debitablePoints","debitable_points","memberOauthId","member_oauth_id","gcTime","getBankList","getListPointAttendance","attendanceList","attendance_list","checked","attendance_yn","date","count","attendance_count","month","getInvitedMemberCount","invited_member_count","activityType","getPointHistoryList","allPages","getPointBudget","weeklyBudget","budget_point","weeklyAccumulatedPoint","accumulated_point","benefitCount","benefit_count","weeklyRemainingPoint","remain_point","weeklyAccumulatedPointText","floor","weeklyBudgetText","weeklyAccumulatedPointPercentage","getPointPrice","Infinity","pointEarning","point_earning","values","POINT_ACTIVITY","reduce","acc","cur","pointMission","isSaveFilter","save_filter_yn","isAttendance","today_attendance_yn","isInvite","today_invite_yn","isWrite","today_write_yn","completedMission","leftMission","entries","eventCombo","event_count","todayAccumulatedPoint","today_accumulated_point","totalWithdrawalCount","total_withdrawal_count","module","exports","AbortError","constructor","delay","ms","signal","reject","abortError","abortHandler","clearTimeout","timeoutId","aborted","once","TimeoutError","timeout","g","run","race","animationControls","hasMounted","subscribers","Set","subscribe","add","visualElement","delete","definition","transitionOverride","errors","k","animations","visual_element","setters","gg","stop","stopAnimation","mount","use_constant","h","use_isomorphic_effect","L"],"sourceRoot":""}