import {useEffect, useState, type ReactNode} from 'react'; import {PlaceCategory, selectPublishable} from '@o2o/shared'; import {useSite} from '@site/lib/site-context'; import { bookingActionLabel, bookingLinks, channelLabel, isSectionEnabled, publicLinks, stayBookingView, } from '@site/lib/derive'; import {isoDate, naverDirectionsUrl} from '@site/lib/format'; import {SongPlayer} from '@site/sections/SongPlayer'; import {BookingForm} from '../paper/BookingForm'; import {BOOK_EVENT} from '../paper/Rooms'; import {Sheet} from '../paper/Sheet'; import {MapPinIcon, PhoneIcon, TalkIcon} from './icons'; import '../kit/kit.css'; import './coral.css'; const HOUSE_WORD: Record = { [PlaceCategory.LODGING]: '펜션', [PlaceCategory.CAFE]: '카페', [PlaceCategory.RESTAURANT]: '가게', [PlaceCategory.CLINIC]: '병원', }; type PageId = 'home' | 'area' | 'stay' | 'story'; const PAGE_HEAD: Record, {kicker: string; title: string; note: string}> = { area: { kicker: 'Travel', title: '지역 소개', note: '대문에서 걸어 닿는 자리와, 이 도시가 남긴 이야기입니다.', }, stay: { kicker: 'Reservation', title: '이용안내 · 예약', note: '상세 안내와 요금, 예약하는 방법입니다.', }, story: { kicker: 'Story', title: '이야기', note: '사장님이 남기는 글과 다녀가신 분들의 후기, 그리고 엽서 한 장입니다.', }, }; function pageOf(hash: string): PageId { const id = hash.replace(/^#\/?/, ''); return id === 'area' || id === 'stay' || id === 'story' ? id : 'home'; } function usePage(): PageId { const [page, setPage] = useState('home'); useEffect(() => { const sync = () => { const {hash} = window.location; if (hash && !hash.startsWith('#/')) return; setPage(pageOf(hash)); window.scrollTo({top: 0, behavior: 'instant'}); }; sync(); window.addEventListener('hashchange', sync); const again = (event: MouseEvent) => { const link = event.target instanceof Element ? event.target.closest('a[href^="#/"]') : null; const href = link?.getAttribute('href'); if (href && pageOf(href) === pageOf(window.location.hash)) window.scrollTo({top: 0, behavior: 'instant'}); }; document.addEventListener('click', again); return () => { window.removeEventListener('hashchange', sync); document.removeEventListener('click', again); }; }, []); return page; } export function Frame({children}: {children: ReactNode}) { const payload = useSite(); const {place, site} = payload; const page = usePage(); const [bookingOpen, setBookingOpen] = useState(false); useEffect(() => { const open = () => setBookingOpen(true); window.addEventListener(BOOK_EVENT, open); return () => window.removeEventListener(BOOK_EVENT, open); }, []); const tabs: {label: string; page: PageId}[] = [ {label: `${HOUSE_WORD[place.category] ?? '가게'} 소개`, page: 'home'}, ]; if (isSectionEnabled(payload, 'local')) tabs.push({label: '지역 소개', page: 'area'}); tabs.push({label: '이용안내 · 예약', page: 'stay'}); tabs.push({label: '이야기', page: 'story'}); const booking = bookingLinks(payload)[0]; const stayBooking = place.category === PlaceCategory.LODGING ? stayBookingView(payload) : null; const facts = selectPublishable(payload.facts); const fact = (key: string) => facts.find((f) => f.key === key)?.value ?? undefined; const hours = [ fact('check_in_time') && `체크인 ${fact('check_in_time')}`, fact('check_out_time') && `체크아웃 ${fact('check_out_time')}`, fact('smoking') === 'false' && '전 구역 금연', ].filter(Boolean); const links = publicLinks(payload); const instagram = links.find((link) => /instagram\.com/i.test(link.url)); const kakao = payload.links.find((link) => link.confirmed && /(pf|open)\.kakao\.com/i.test(link.url)); const address = place.roadAddress ?? place.address; const english = place.englishName?.trim(); const areaPills = ( [ ['주변 명소', '#walk', payload.local.attractions.length > 0], ['주변 맛집', '#eat', payload.local.restaurants.length > 0], ['엽서', '#/story', true], ['축제', '#festival', isSectionEnabled(payload, 'festival') && payload.local.festivals.length > 0], ['일정', '#itinerary', isSectionEnabled(payload, 'itinerary')], ['이야기', '#story', isSectionEnabled(payload, 'story')], ['날씨', '#/', isSectionEnabled(payload, 'weather')], ] as [string, string, boolean][] ).filter(([, , on]) => on); const barItems = [ address && ( 길찾기 ), kakao && ( 카톡 ), place.phone && ( 전화 ), ].filter(Boolean); const cta = stayBooking ? ( ) : booking ? ( {bookingActionLabel(booking)} ) : null; return (
{place.phone ? ( ) : ( )} {english || place.name} {stayBooking ? ( ) : booking ? ( Book
Now
) : ( )}
{place.phone && ( {place.phone} )} {cta}
{(Object.keys(PAGE_HEAD) as Exclude[]).map((id) => (

{PAGE_HEAD[id].kicker}

{id === 'area' ? (place.addressLocality?.replace(/(시|군)$/, '') ?? PAGE_HEAD.area.title) : PAGE_HEAD[id].title}

{PAGE_HEAD[id].note}

{id === 'area' && areaPills.length > 0 && (
{areaPills.map(([label, href]) => ( {label} ))}
)}
))} {children} {links.length > 0 && (

Channel

공식 채널

{links.map((link) => ( {channelLabel(link)} 열기 ↗ ))}
)}
{place.phone && ( {place.phone} )} {place.name} {address &&

{address}

} {hours.length > 0 &&

{hours.join(' · ')}

} {(place.phone || instagram) && (

{place.phone && {place.phone}} {place.phone && instagram && ' · '} {instagram && ( instagram )}

)} {place.legal?.businessRegistrationNumber && (

{place.legal.representative && `대표 ${place.legal.representative} · `} 사업자등록번호 {place.legal.businessRegistrationNumber} {place.legal.mailOrderNumber && ` · 통신판매업신고 ${place.legal.mailOrderNumber}`}

)}

작성·운영 {place.name} · 최종 업데이트{' '}
AI O2O 의 Web4Ai로 만든 사이트입니다.

{english || place.name}

{(barItems.length > 0 || cta) && ( )} {stayBooking && ( setBookingOpen(false)} label="예약 요청" plain>

예약 요청하기

)}
); }