From c5ffd7931627ad79e3a576d4b17c12c46d2ca951 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=A7=80=ED=9B=88?= Date: Tue, 9 Jun 2026 13:28:50 +0900 Subject: [PATCH 1/2] Revert "Merge pull request #426 from Nexters/feat/venue-search-integration" This reverts commit 82e0462c37a5d9b602ebf25da1529f1ce7fa781a, reversing changes made to 9b2a2c800ac883d6b9845b498d0afd0269a98708. --- .pnp.cjs | 23 ++ apps/admin/package.json | 1 + .../PlaceSearchInput.styles.ts | 158 ------------- .../src/components/PlaceSearchInput/index.tsx | 217 ------------------ .../ShowBasicInfoFormContent.tsx | 200 ++++++++++++---- apps/admin/src/hooks/useKakaoLocalSearch.ts | 181 --------------- yarn.lock | 10 + 7 files changed, 186 insertions(+), 604 deletions(-) delete mode 100644 apps/admin/src/components/PlaceSearchInput/PlaceSearchInput.styles.ts delete mode 100644 apps/admin/src/components/PlaceSearchInput/index.tsx delete mode 100644 apps/admin/src/hooks/useKakaoLocalSearch.ts diff --git a/.pnp.cjs b/.pnp.cjs index 51a533b1..46af3271 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -9755,6 +9755,7 @@ const RAW_RUNTIME_STATE = ["qrcode.react", "virtual:9845906954fdbefbb879db24fa8772d77a945dca59f459806df47a5b67245d4bc6502880b373cca7201062c81bea9f13f699f52de2004c037e79dbdbd5d97fb3#npm:3.1.0"],\ ["quill", "npm:2.0.3"],\ ["react", "npm:18.2.0"],\ + ["react-daum-postcode", "virtual:9845906954fdbefbb879db24fa8772d77a945dca59f459806df47a5b67245d4bc6502880b373cca7201062c81bea9f13f699f52de2004c037e79dbdbd5d97fb3#npm:3.1.3"],\ ["react-dom", "virtual:de80dc576383b2386358abc0e9fe49c00e3397fe355a0337462b73ab3115c2e557eb85784ee0fe776394cc11dd020b4e84dbbd75acf72ee6d54415d82d21f5c5#npm:18.2.0"],\ ["react-dropzone", "virtual:9845906954fdbefbb879db24fa8772d77a945dca59f459806df47a5b67245d4bc6502880b373cca7201062c81bea9f13f699f52de2004c037e79dbdbd5d97fb3#npm:14.2.3"],\ ["react-error-boundary", "virtual:9845906954fdbefbb879db24fa8772d77a945dca59f459806df47a5b67245d4bc6502880b373cca7201062c81bea9f13f699f52de2004c037e79dbdbd5d97fb3#npm:4.1.2"],\ @@ -18936,6 +18937,28 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["react-daum-postcode", [\ + ["npm:3.1.3", {\ + "packageLocation": "./.yarn/cache/react-daum-postcode-npm-3.1.3-95dbfacabd-72f05078e2.zip/node_modules/react-daum-postcode/",\ + "packageDependencies": [\ + ["react-daum-postcode", "npm:3.1.3"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:9845906954fdbefbb879db24fa8772d77a945dca59f459806df47a5b67245d4bc6502880b373cca7201062c81bea9f13f699f52de2004c037e79dbdbd5d97fb3#npm:3.1.3", {\ + "packageLocation": "./.yarn/__virtual__/react-daum-postcode-virtual-a70527b50a/0/cache/react-daum-postcode-npm-3.1.3-95dbfacabd-72f05078e2.zip/node_modules/react-daum-postcode/",\ + "packageDependencies": [\ + ["react-daum-postcode", "virtual:9845906954fdbefbb879db24fa8772d77a945dca59f459806df47a5b67245d4bc6502880b373cca7201062c81bea9f13f699f52de2004c037e79dbdbd5d97fb3#npm:3.1.3"],\ + ["@types/react", "npm:18.2.48"],\ + ["react", "npm:18.2.0"]\ + ],\ + "packagePeers": [\ + "@types/react",\ + "react"\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["react-dnd-html5-backend", [\ ["npm:16.0.1", {\ "packageLocation": "./.yarn/cache/react-dnd-html5-backend-npm-16.0.1-754940d855-6e4b632a11.zip/node_modules/react-dnd-html5-backend/",\ diff --git a/apps/admin/package.json b/apps/admin/package.json index ffcaf83f..b0ce479e 100644 --- a/apps/admin/package.json +++ b/apps/admin/package.json @@ -39,6 +39,7 @@ "qrcode.react": "^3.1.0", "quill": "2.0.3", "react": "^18.2.0", + "react-daum-postcode": "^3.1.3", "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", "react-error-boundary": "^4.1.2", diff --git a/apps/admin/src/components/PlaceSearchInput/PlaceSearchInput.styles.ts b/apps/admin/src/components/PlaceSearchInput/PlaceSearchInput.styles.ts deleted file mode 100644 index 9cdb2be6..00000000 --- a/apps/admin/src/components/PlaceSearchInput/PlaceSearchInput.styles.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { mq_lg } from '@boolti/ui'; -import styled from '@emotion/styled'; - -const Container = styled.div` - position: relative; - width: 100%; -`; - -const InputWrapper = styled.div` - position: relative; - display: flex; - align-items: center; -`; - -const SearchInput = styled.input<{ hasError?: boolean }>` - width: 100%; - border-radius: 4px; - padding: 12px 40px 12px 13px; - color: ${({ theme }) => theme.palette.grey.g90}; - border: 1px solid - ${({ hasError, theme }) => - hasError ? `${theme.palette.status.error1} !important` : theme.palette.grey.g20}; - background: ${({ theme }) => theme.palette.grey.w}; - ${({ theme }) => theme.typo.b3}; - - &::placeholder { - color: ${({ theme }) => theme.palette.grey.g30}; - } - - &:focus { - border: 1px solid ${({ theme }) => theme.palette.grey.g90}; - } - - &:disabled { - background: ${({ theme }) => theme.palette.grey.g10}; - border: 1px solid ${({ theme }) => theme.palette.grey.g20}; - color: ${({ theme }) => theme.palette.grey.g40}; - } -`; - -const SearchIconWrapper = styled.div` - position: absolute; - right: 12px; - top: 50%; - transform: translateY(-50%); - display: flex; - align-items: center; - pointer-events: none; - - svg { - width: 20px; - height: 20px; - } - - path { - stroke: ${({ theme }) => theme.palette.grey.g40}; - } -`; - -const Dropdown = styled.ul` - position: absolute; - top: calc(100% + 4px); - left: 0; - right: 0; - z-index: 10; - background: ${({ theme }) => theme.palette.grey.w}; - border: 1px solid ${({ theme }) => theme.palette.grey.g20}; - border-radius: 4px; - box-shadow: 0px 8px 14px 0px ${({ theme }) => theme.palette.shadow}; - max-height: 300px; - overflow-y: auto; - list-style: none; - padding: 0; - margin: 0; - - ${mq_lg} { - max-height: 274px; - } -`; - -const DropdownItem = styled.li` - padding: 12px 16px; - cursor: pointer; - display: flex; - flex-direction: column; - gap: 2px; - border-bottom: 1px solid ${({ theme }) => theme.palette.grey.g10}; - - &:last-of-type { - border-bottom: none; - } - - &:hover { - background: ${({ theme }) => theme.palette.grey.g00}; - } -`; - -const PlaceNameRow = styled.div` - display: flex; - align-items: baseline; - gap: 6px; -`; - -const PlaceName = styled.span` - ${({ theme }) => theme.typo.b3}; - color: ${({ theme }) => theme.palette.grey.g90}; -`; - -const Category = styled.span` - ${({ theme }) => theme.typo.b1}; - color: ${({ theme }) => theme.palette.grey.g40}; -`; - -const AddressName = styled.span` - ${({ theme }) => theme.typo.b1}; - color: ${({ theme }) => theme.palette.grey.g50}; -`; - -const SelectedInfo = styled.div` - margin-top: 8px; - display: flex; - align-items: center; - gap: 8px; - - div { - width: auto; - flex: 1; - } -`; - -const ErrorMessage = styled.span` - margin-top: 4px; - ${({ theme }) => theme.typo.b1}; - color: ${({ theme }) => theme.palette.status.error1}; -`; - -const EmptyState = styled.div` - padding: 16px; - text-align: center; - ${({ theme }) => theme.typo.b1}; - color: ${({ theme }) => theme.palette.grey.g50}; -`; - -export default { - Container, - InputWrapper, - SearchInput, - SearchIconWrapper, - Dropdown, - DropdownItem, - PlaceNameRow, - PlaceName, - Category, - AddressName, - SelectedInfo, - ErrorMessage, - EmptyState, -}; diff --git a/apps/admin/src/components/PlaceSearchInput/index.tsx b/apps/admin/src/components/PlaceSearchInput/index.tsx deleted file mode 100644 index 63e1cdad..00000000 --- a/apps/admin/src/components/PlaceSearchInput/index.tsx +++ /dev/null @@ -1,217 +0,0 @@ -import { SearchIcon } from '@boolti/icon'; -import { TextField } from '@boolti/ui'; -import { useEffect, useRef, useState } from 'react'; - -import useKakaoLocalSearch, { - PlaceSearchResult, - PlaceSearchResultType, -} from '~/hooks/useKakaoLocalSearch'; - -import Styled from './PlaceSearchInput.styles'; - -interface PlaceSearchInputProps { - initialPlaceName?: string; - initialAddress?: string; - initialDetailAddress?: string; - disabled?: boolean; - errorMessage?: string; - onSelect: (result: { - type: PlaceSearchResultType; - placeName: string; - streetAddress: string; - detailAddress: string; - latitude: number; - longitude: number; - }) => void; - onClear?: () => void; - onDetailAddressChange?: (value: string) => void; -} - -const PlaceSearchInput = ({ - initialPlaceName, - initialAddress, - initialDetailAddress, - disabled, - errorMessage, - onSelect, - onClear, - onDetailAddressChange, -}: PlaceSearchInputProps) => { - const { query, setQuery, results, isLoading, clearResults } = useKakaoLocalSearch(); - const [isDropdownOpen, setIsDropdownOpen] = useState(false); - const [selectedResult, setSelectedResult] = useState(null); - const [streetAddress, setStreetAddress] = useState(initialAddress ?? ''); - const [detailAddress, setDetailAddress] = useState(initialDetailAddress ?? ''); - const containerRef = useRef(null); - const detailAddressInputRef = useRef(null); - - useEffect(() => { - if (!selectedResult && initialAddress) { - const isPlace = Boolean(initialPlaceName); - setSelectedResult({ - type: isPlace ? 'place' : 'address', - id: 'initial', - placeName: initialPlaceName ?? '', - category: '', - addressName: initialAddress, - roadAddressName: initialAddress, - x: '0', - y: '0', - }); - } - }, [initialPlaceName, initialAddress, selectedResult]); - - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if (containerRef.current && !containerRef.current.contains(event.target as Node)) { - setIsDropdownOpen(false); - } - }; - - document.addEventListener('mousedown', handleClickOutside); - return () => document.removeEventListener('mousedown', handleClickOutside); - }, []); - - const handleInputChange = (e: React.ChangeEvent) => { - const value = e.target.value; - setQuery(value); - setIsDropdownOpen(true); - - if (selectedResult) { - setSelectedResult(null); - setStreetAddress(''); - setDetailAddress(''); - onClear?.(); - } - }; - - const handleSelect = (result: PlaceSearchResult) => { - setSelectedResult(result); - setIsDropdownOpen(false); - clearResults(); - - const address = result.roadAddressName || result.addressName; - setStreetAddress(address); - setDetailAddress(''); - - if (result.type === 'place') { - setQuery(result.placeName); - - onSelect({ - type: 'place', - placeName: result.placeName, - streetAddress: address, - detailAddress: '', - latitude: Number(result.y), - longitude: Number(result.x), - }); - } else { - setQuery(address); - - onSelect({ - type: 'address', - placeName: '', - streetAddress: address, - detailAddress: '', - latitude: Number(result.y), - longitude: Number(result.x), - }); - - setTimeout(() => { - detailAddressInputRef.current?.focus(); - }, 0); - } - }; - - const handleDetailAddressChange = (e: React.ChangeEvent) => { - const value = e.target.value; - setDetailAddress(value); - onDetailAddressChange?.(value); - }; - - const handleInputFocus = () => { - if (!selectedResult && query.trim()) { - setIsDropdownOpen(true); - } - }; - - return ( -
- - - - - - - - - {isDropdownOpen && query.trim() && !selectedResult && ( - - {isLoading && results.length === 0 ? ( - 검색 중... - ) : results.length === 0 ? ( - 검색 결과가 없어요 - ) : ( - results.map((result) => ( - handleSelect(result)}> - - - {result.type === 'place' - ? result.placeName - : result.roadAddressName || result.addressName} - - {result.type === 'place' && result.category && ( - {result.category} - )} - - {result.type === 'place' && ( - - {result.roadAddressName || result.addressName} - - )} - - )) - )} - - )} - - - {selectedResult && ( - - {selectedResult.type === 'place' ? ( - - ) : ( - - )} - - )} - - {errorMessage && !selectedResult && ( - {errorMessage} - )} -
- ); -}; - -export default PlaceSearchInput; diff --git a/apps/admin/src/components/ShowInfoFormContent/ShowBasicInfoFormContent.tsx b/apps/admin/src/components/ShowInfoFormContent/ShowBasicInfoFormContent.tsx index ed0f7476..0782afc3 100644 --- a/apps/admin/src/components/ShowInfoFormContent/ShowBasicInfoFormContent.tsx +++ b/apps/admin/src/components/ShowInfoFormContent/ShowBasicInfoFormContent.tsx @@ -1,16 +1,16 @@ -import { ImageFile } from '@boolti/api'; +import { ImageFile, fetcher } from '@boolti/api'; import { CloseIcon, FileUpIcon } from '@boolti/icon'; -import { TextField, TimePicker } from '@boolti/ui'; +import { Button, TextField, TimePicker, useDialog } from '@boolti/ui'; import { add, format } from 'date-fns'; -import { useEffect } from 'react'; +import { useRef } from 'react'; import { useDropzone } from 'react-dropzone'; import { Controller, UseFormReturn } from 'react-hook-form'; +import { useNavermaps } from 'react-naver-maps'; +import DaumPostcode from 'react-daum-postcode'; -import PlaceSearchInput from '~/components/PlaceSearchInput'; import Styled from './ShowInfoFormContent.styles'; import { ShowBasicInfoFormInputs } from './types'; - -const VENUE_REQUIRED_MESSAGE = '공연장을 선택해 주세요'; +import { useBodyScrollLock } from '~/hooks/useBodyScrollLock'; const MAX_IMAGE_COUNT = 3; const MIN_DATE = format(add(new Date(), { days: 1 }), 'yyyy-MM-dd'); @@ -32,23 +32,18 @@ const ShowBasicInfoFormContent = ({ onDropImage, onDeleteImage, }: ShowBasicInfoFormContentProps) => { + const { open, close, isOpen } = useDialog(); + const naverMaps = useNavermaps(); + const detailAddressInputRef = useRef(null); + const { control, - register, setValue, - watch, formState: { errors }, setError, clearErrors, } = form; - useEffect(() => { - register('placeName', { required: VENUE_REQUIRED_MESSAGE }); - register('placeStreetAddress', { required: VENUE_REQUIRED_MESSAGE }); - register('latitude', { required: VENUE_REQUIRED_MESSAGE }); - register('longitude', { required: VENUE_REQUIRED_MESSAGE }); - }, [register]); - const { getRootProps, getInputProps } = useDropzone({ accept: { 'image/jpeg': [], @@ -58,6 +53,55 @@ const ShowBasicInfoFormContent = ({ onDrop: onDropImage, }); + const openDaumPostCodeWithDialog: React.MouseEventHandler = (e) => { + e.preventDefault(); + open({ + title: '주소 찾기', + content: ( + { + try { + const response = await fetcher.get( + 'web/v1/naver-maps/geocoding', + { + searchParams: { + query: address.address, + filter: `BCODE@${address.bcode};`, + }, + }, + ); + + if ( + response.status === naverMaps.Service.GeocodeStatus.OK && + response.meta.totalCount > 0 + ) { + const foundAddress = response.addresses.find(Boolean); + + if (foundAddress) { + setValue('latitude', Number(foundAddress.y)); + setValue('longitude', Number(foundAddress.x)); + } + } + } catch (e) { + console.warn('[ShowBasicInfoFormContent] geocoding failed:', e); + } + + setValue('placeStreetAddress', address.roadAddress); + + detailAddressInputRef.current?.focus(); + }} + onClose={() => { + close(); + }} + /> + ), + onClose: close, + }); + }; + + useBodyScrollLock(isOpen); + return ( @@ -273,43 +317,103 @@ const ShowBasicInfoFormContent = ({ - 공연장 + 공연장명 - { - setValue('placeName', result.placeName || result.streetAddress, { - shouldValidate: true, - }); - setValue('placeStreetAddress', result.streetAddress, { - shouldValidate: true, - }); - setValue('placeDetailAddress', result.detailAddress); - setValue('latitude', result.latitude, { shouldValidate: true }); - setValue('longitude', result.longitude, { shouldValidate: true }); - clearErrors(['placeName', 'placeStreetAddress', 'placeDetailAddress']); + { - setValue('placeName', ''); - setValue('placeStreetAddress', ''); - setValue('placeDetailAddress', ''); - setValue('latitude', 0); - setValue('longitude', 0); - setError('placeStreetAddress', { - type: 'required', - message: VENUE_REQUIRED_MESSAGE, - }); + render={({ field: { value, onChange, onBlur } }) => ( + { + onChange(event); + clearErrors('placeName'); + }} + onBlur={() => { + onBlur(); + + if (!value) { + setError('placeName', { type: 'required', message: '필수 입력사항입니다.' }); + return; + } + }} + value={value ?? ''} + errorMessage={errors.placeName?.message} + /> + )} + name="placeName" + /> + + + + + + 공연장 주소 + + { - setValue('placeDetailAddress', value); + render={({ field: { value } }) => ( + <> + + + + )} + name="placeStreetAddress" + /> + + + ( + { + onChange(event); + clearErrors('placeDetailAddress'); + }} + onBlur={() => { + onBlur(); + + if (!value) { + setError('placeDetailAddress', { + type: 'required', + message: '필수 입력사항입니다.', + }); + return; + } + }} + value={value ?? ''} + errorMessage={errors.placeDetailAddress?.message} + /> + )} + name="placeDetailAddress" /> diff --git a/apps/admin/src/hooks/useKakaoLocalSearch.ts b/apps/admin/src/hooks/useKakaoLocalSearch.ts deleted file mode 100644 index ec7dfd42..00000000 --- a/apps/admin/src/hooks/useKakaoLocalSearch.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { useCallback, useEffect, useRef, useState } from 'react'; - -const KAKAO_REST_API_KEY = import.meta.env.VITE_KAKAO_REST_API_KEY; -const KAKAO_LOCAL_KEYWORD_URL = 'https://dapi.kakao.com/v2/local/search/keyword.json'; -const KAKAO_LOCAL_ADDRESS_URL = 'https://dapi.kakao.com/v2/local/search/address.json'; - -const DEBOUNCE_MS = 300; - -export interface KakaoKeywordResult { - id: string; - place_name: string; - category_group_code: string; - category_group_name: string; - address_name: string; - road_address_name: string; - x: string; - y: string; - phone: string; -} - -export interface KakaoAddressResult { - address_name: string; - address_type: 'REGION' | 'ROAD' | 'REGION_ADDR' | 'ROAD_ADDR'; - x: string; - y: string; - address: { - address_name: string; - region_1depth_name: string; - region_2depth_name: string; - region_3depth_name: string; - } | null; - road_address: { - address_name: string; - road_name: string; - building_name: string; - } | null; -} - -export type PlaceSearchResultType = 'place' | 'address'; - -export interface PlaceSearchResult { - type: PlaceSearchResultType; - id: string; - placeName: string; - category: string; - addressName: string; - roadAddressName: string; - x: string; - y: string; -} - -const searchKeyword = async (query: string): Promise => { - const url = new URL(KAKAO_LOCAL_KEYWORD_URL); - url.searchParams.set('query', query); - url.searchParams.set('size', '5'); - - const response = await fetch(url.toString(), { - headers: { Authorization: `KakaoAK ${KAKAO_REST_API_KEY}` }, - }); - - if (!response.ok) return []; - - const data = await response.json(); - return (data.documents as KakaoKeywordResult[]).map((doc) => ({ - type: 'place' as const, - id: doc.id, - placeName: doc.place_name, - category: doc.category_group_name, - addressName: doc.address_name, - roadAddressName: doc.road_address_name, - x: doc.x, - y: doc.y, - })); -}; - -const searchAddress = async (query: string): Promise => { - const url = new URL(KAKAO_LOCAL_ADDRESS_URL); - url.searchParams.set('query', query); - url.searchParams.set('size', '5'); - - const response = await fetch(url.toString(), { - headers: { Authorization: `KakaoAK ${KAKAO_REST_API_KEY}` }, - }); - - if (!response.ok) return []; - - const data = await response.json(); - return (data.documents as KakaoAddressResult[]).map((doc, index) => ({ - type: 'address' as const, - id: `addr-${index}-${doc.x}-${doc.y}`, - placeName: '', - category: '', - addressName: doc.address?.address_name ?? doc.address_name, - roadAddressName: doc.road_address?.address_name ?? doc.address_name, - x: doc.x, - y: doc.y, - })); -}; - -const useKakaoLocalSearch = () => { - const [query, setQuery] = useState(''); - const [results, setResults] = useState([]); - const [isLoading, setIsLoading] = useState(false); - const timerRef = useRef | null>(null); - const abortRef = useRef(null); - - const search = useCallback(async (keyword: string) => { - if (!keyword.trim()) { - setResults([]); - return; - } - - setIsLoading(true); - - if (abortRef.current) { - abortRef.current.abort(); - } - abortRef.current = new AbortController(); - - try { - const [keywordResults, addressResults] = await Promise.all([ - searchKeyword(keyword), - searchAddress(keyword), - ]); - - const seenIds = new Set(); - const merged: PlaceSearchResult[] = []; - - for (const result of keywordResults) { - if (!seenIds.has(result.id)) { - seenIds.add(result.id); - merged.push(result); - } - } - - for (const result of addressResults) { - if (!seenIds.has(result.id)) { - seenIds.add(result.id); - merged.push(result); - } - } - - setResults(merged.slice(0, 8)); - } catch { - setResults([]); - } finally { - setIsLoading(false); - } - }, []); - - useEffect(() => { - if (timerRef.current) { - clearTimeout(timerRef.current); - } - - timerRef.current = setTimeout(() => { - search(query); - }, DEBOUNCE_MS); - - return () => { - if (timerRef.current) { - clearTimeout(timerRef.current); - } - }; - }, [query, search]); - - const clearResults = useCallback(() => { - setResults([]); - setQuery(''); - }, []); - - return { - query, - setQuery, - results, - isLoading, - clearResults, - }; -}; - -export default useKakaoLocalSearch; diff --git a/yarn.lock b/yarn.lock index f250e87e..b56913fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6350,6 +6350,7 @@ __metadata: qrcode.react: "npm:^3.1.0" quill: "npm:2.0.3" react: "npm:^18.2.0" + react-daum-postcode: "npm:^3.1.3" react-dom: "npm:^18.2.0" react-dropzone: "npm:^14.2.3" react-error-boundary: "npm:^4.1.2" @@ -13977,6 +13978,15 @@ __metadata: languageName: node linkType: hard +"react-daum-postcode@npm:^3.1.3": + version: 3.1.3 + resolution: "react-daum-postcode@npm:3.1.3" + peerDependencies: + react: ">=16.8.0" + checksum: 10c0/72f05078e25df34d9196d573105c799bb85c7fd36f397af1f9edeec9b2aa1babd8ce90163f79b38fdcc7194d1b73b5df999fd3921fa8cc498fa7a05be755e955 + languageName: node + linkType: hard + "react-dnd-html5-backend@npm:^16.0.1": version: 16.0.1 resolution: "react-dnd-html5-backend@npm:16.0.1" From 41c017e81622b9b815d2e09cc31b54e61958f8ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=A7=80=ED=9B=88?= Date: Tue, 9 Jun 2026 13:28:56 +0900 Subject: [PATCH 2/2] Revert "Merge pull request #425 from Nexters/fix/optimize-preview" This reverts commit 9b2a2c800ac883d6b9845b498d0afd0269a98708, reversing changes made to e9bae8195cd594bd8004b53eb5a266d6adf3a096. --- apps/preview/index.html | 15 ------ apps/preview/src/App.tsx | 41 ++++----------- apps/preview/src/constants/ncp.ts | 1 - apps/preview/src/index.css | 2 + .../src/pages/ShowInfoDetailPage/index.tsx | 2 - .../src/pages/ShowPreviewPage/index.tsx | 21 ++------ .../PreviewMap/PreviewMapWithProvider.tsx | 21 -------- .../components/ShowPreview/ShowInfoDetail.tsx | 32 +++--------- .../ShowPreview/ShowPreview.styles.ts | 7 +-- .../ui/src/components/ShowPreview/index.tsx | 51 +++---------------- 10 files changed, 31 insertions(+), 162 deletions(-) delete mode 100644 apps/preview/src/constants/ncp.ts delete mode 100644 packages/ui/src/components/PreviewMap/PreviewMapWithProvider.tsx diff --git a/apps/preview/index.html b/apps/preview/index.html index 5c8d2a2c..60e8e7d9 100644 --- a/apps/preview/index.html +++ b/apps/preview/index.html @@ -4,21 +4,6 @@ - - - 핫한 공연 예매의 시작, 불티 diff --git a/apps/preview/src/App.tsx b/apps/preview/src/App.tsx index e48f962d..8897dc19 100644 --- a/apps/preview/src/App.tsx +++ b/apps/preview/src/App.tsx @@ -5,36 +5,12 @@ import { ShowCastTeamReadResponse, ShowPreviewResponse } from '@boolti/api'; import { BooltiUIProvider } from '@boolti/ui'; import { createBrowserRouter, RouterProvider } from 'react-router-dom'; import { HelmetProvider } from 'react-helmet-async'; +import { NavermapsProvider } from 'react-naver-maps'; import ShowPreviewPage from './pages/ShowPreviewPage'; import { fetcher } from '@boolti/api/src/fetcher'; import NotFound from './components/NotFound'; import ShowInfoDetailPage from './pages/ShowInfoDetailPage'; -const SHOW_IMAGE_PRELOAD_ID = 'show-preview-lcp-image-preload'; - -const preloadShowImage = (imageUrl?: string) => { - if (!imageUrl || typeof document === 'undefined') { - return; - } - - const existingPreload = document.getElementById(SHOW_IMAGE_PRELOAD_ID) as HTMLLinkElement | null; - - if (existingPreload?.href === imageUrl) { - return; - } - - existingPreload?.remove(); - - const preload = document.createElement('link'); - preload.id = SHOW_IMAGE_PRELOAD_ID; - preload.rel = 'preload'; - preload.as = 'image'; - preload.href = imageUrl; - preload.setAttribute('fetchpriority', 'high'); - - document.head.appendChild(preload); -}; - const showPageLoader = async ({ params }: { params: { showId?: string } }) => { const showId = params.showId; if (showId) { @@ -42,7 +18,6 @@ const showPageLoader = async ({ params }: { params: { showId?: string } }) => { fetcher.get(`web/papi/v1/shows/${showId}`), fetcher.get(`web/papi/v1/shows/${showId}/cast-teams`), ]); - preloadShowImage(response[0].showImg[0]?.path); return response; } }; @@ -77,13 +52,17 @@ const router = createBrowserRouter([ }, ]); +const X_NCP_APIGW_API_KEY_ID = import.meta.env.VITE_X_NCP_APIGW_API_KEY_ID; + const App = () => { return ( - - - - - + + + + + + + ); }; diff --git a/apps/preview/src/constants/ncp.ts b/apps/preview/src/constants/ncp.ts deleted file mode 100644 index 062ebf64..00000000 --- a/apps/preview/src/constants/ncp.ts +++ /dev/null @@ -1 +0,0 @@ -export const X_NCP_APIGW_API_KEY_ID = import.meta.env.VITE_X_NCP_APIGW_API_KEY_ID; diff --git a/apps/preview/src/index.css b/apps/preview/src/index.css index 56c6f661..f650a627 100644 --- a/apps/preview/src/index.css +++ b/apps/preview/src/index.css @@ -1,3 +1,5 @@ +@import url('https://cdnjs.cloudflare.com/ajax/libs/pretendard/1.3.9/static/pretendard-dynamic-subset.min.css'); + * { font-family: 'Pretendard Variable', diff --git a/apps/preview/src/pages/ShowInfoDetailPage/index.tsx b/apps/preview/src/pages/ShowInfoDetailPage/index.tsx index 61061b43..2aac5e85 100644 --- a/apps/preview/src/pages/ShowInfoDetailPage/index.tsx +++ b/apps/preview/src/pages/ShowInfoDetailPage/index.tsx @@ -3,7 +3,6 @@ import { ShowInfoDetail } from '@boolti/ui'; import { format } from 'date-fns'; import { useLoaderData } from 'react-router-dom'; import Styled from './ShowInfoDetailPage.styles'; -import { X_NCP_APIGW_API_KEY_ID } from '../../constants/ncp'; const ShowInfoDetailPage: React.FC = () => { const [show, { count: soldTicketCount }] = useLoaderData() as [ @@ -53,7 +52,6 @@ const ShowInfoDetailPage: React.FC = () => { onClickMessageLink={messageLinkClickHandler} onClickCallLinkMobile={callLinkClickHandler} onClickMessageLinkMobile={messageLinkClickHandler} - naverMapClientId={X_NCP_APIGW_API_KEY_ID} /> ); diff --git a/apps/preview/src/pages/ShowPreviewPage/index.tsx b/apps/preview/src/pages/ShowPreviewPage/index.tsx index c22d000e..c996f0c8 100644 --- a/apps/preview/src/pages/ShowPreviewPage/index.tsx +++ b/apps/preview/src/pages/ShowPreviewPage/index.tsx @@ -2,25 +2,19 @@ import { ShowCastTeamReadResponse, ShowPreviewResponse } from '@boolti/api'; import { Footer, ShowPreview, useDeviceByWidth, useDialog } from '@boolti/ui'; import { format, setDefaultOptions } from 'date-fns'; import { ko } from 'date-fns/locale'; -import { lazy, Suspense, useState } from 'react'; +import { QRCodeSVG } from 'qrcode.react'; import { useLoaderData } from 'react-router-dom'; import Styled from './ShowPreviewPage.styles'; import { Meta } from '../../components/Meta'; import BooltiGrayLogo from '../../components/BooltiGrayLogo'; import useBodyScrollLock from '../../hooks/useBodyScrollLock'; +import { useState } from 'react'; import { EXTERNAL_URL, openStoreLink } from '../../constants/external'; -import { X_NCP_APIGW_API_KEY_ID } from '../../constants/ncp'; import { navigateToAppScheme } from '../../utils/app'; setDefaultOptions({ locale: ko }); -const QRCodeSVG = lazy(() => - import('qrcode.react').then((module) => ({ - default: module.QRCodeSVG, - })), -); - const getAppScheme = (showId: number) => { return `boolti://show/${showId}`; }; @@ -156,9 +150,7 @@ const ShowPreviewPage = () => { - - - + @@ -195,10 +187,7 @@ const ShowPreviewPage = () => { ({ - src: file.path, - thumbnailSrc: file.thumbnailPath, - })), + images: showImg.map((file) => file.path), name: title, date: format(new Date(date), 'yyyy.MM.dd (E)'), startTime: format(new Date(date), 'HH:mm'), @@ -234,8 +223,6 @@ const ShowPreviewPage = () => { onShareShowPreviewLink={shareShowPreviewLink} onShareShowInfo={shareShowInfo} onCloseShareDropdown={shareDropdownCloseHandler} - prioritizeFirstImage - naverMapClientId={X_NCP_APIGW_API_KEY_ID} />