Skip to content

Commit

Permalink
feat: add map view to place page (#168)
Browse files Browse the repository at this point in the history
* refactor: make getMarkerType reusable

* refactor: rename place-box to place-detail

* refactor: make place-map-popup more scalable

* feat: add map view for single place

* feat: add gps button to place-map

* chore: remove console.log

* refactor: make place related types readable

* refactor: make register page as server component

* refactor: refactor file structure under place

- use route groups i/o conditional branching with path

* refactor: remove brackets from className

* refactor: improve type definitions for Place*
  • Loading branch information
hee-suh authored Dec 11, 2024
1 parent e97b104 commit 8d57d2e
Show file tree
Hide file tree
Showing 35 changed files with 370 additions and 275 deletions.
25 changes: 14 additions & 11 deletions src/app/map/[mapId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import PlaceMapPopup from '@/components/place/place-map-popup'
import useFetch from '@/hooks/use-fetch'
import useMeasure from '@/hooks/use-measure'
import type { TagItem } from '@/models/api/maps'
import type { PlaceType } from '@/models/api/place'
import type { KorrkPlace } from '@/models/api/place'
import { getMapIdFromCookie, updateMapIdCookie } from '@/services/map-id'
import { api } from '@/utils/api'
import { onboardingStorage, visitedMapIdsStorage } from '@/utils/storage'
Expand Down Expand Up @@ -64,8 +64,8 @@ const MapMain = ({ params: { mapId } }: { params: { mapId: string } }) => {
const [selectedFilterNames, setSelectedFilterNames] =
useState<FilterIdsType>(INITIAL_FILTER_IDS)

const [filteredPlace, setFilteredPlace] = useState<PlaceType[] | null>(null)
const [selectedPlace, setSelectedPlace] = useState<PlaceType | null>(null)
const [filteredPlace, setFilteredPlace] = useState<KorrkPlace[] | null>(null)
const [selectedPlace, setSelectedPlace] = useState<KorrkPlace | null>(null)

const [mapIdFromCookie, setMapIdFromCookie] = useState('')

Expand All @@ -83,7 +83,7 @@ const MapMain = ({ params: { mapId } }: { params: { mapId: string } }) => {
notify.error(error.message)
}

const handleClickPlace = (place: PlaceType) => {
const handleClickPlace = (place: KorrkPlace) => {
if (selectedPlace?.place.id === place.place.id) {
setSelectedPlace(null)
return
Expand Down Expand Up @@ -251,7 +251,7 @@ const MapMain = ({ params: { mapId } }: { params: { mapId: string } }) => {
</Tooltip>
</header>

<KorrkKakaoMap<PlaceType>
<KorrkKakaoMap<KorrkPlace>
places={filteredPlace}
selectedPlace={selectedPlace}
onClickMap={() => setSelectedPlace(null)}
Expand Down Expand Up @@ -297,13 +297,16 @@ const MapMain = ({ params: { mapId } }: { params: { mapId: string } }) => {
/>
</>
) : (
<PlaceMapPopup
<Link
href={`/place/${selectedPlace.place.kakaoPlace.id}`}
ref={bottomRef}
mapId={mapId}
className="absolute bottom-5 px-5"
selectedPlace={selectedPlace}
onRefreshOldPlace={clearOldPlacedata}
/>
>
<PlaceMapPopup
mapId={mapId}
className="absolute bottom-5 px-5"
selectedPlace={selectedPlace}
/>
</Link>
)}
</div>
)
Expand Down
12 changes: 6 additions & 6 deletions src/app/map/[mapId]/place-list-bottom-sheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import EmptyPlaceList from '@/components/place/empty-place-list'
import PlaceListItem from '@/components/place/place-list-item'
import useFetch from '@/hooks/use-fetch'
import { APIError } from '@/models/api/index'
import type { PlaceType } from '@/models/api/place'
import type { KorrkPlace } from '@/models/api/place'
import { useInfiniteScroll } from '@/hooks/use-infinite-scroll'
import { api } from '@/utils/api'
import { revalidatePlaces } from '@/app/actions'

interface PlaceListBottomSheetProps {
places: PlaceType[] | null
places: KorrkPlace[] | null
mapId: string
selectedFilter?: FilterIdsType
onClickFilterButton: VoidFunction
Expand All @@ -31,8 +31,8 @@ const PlaceListBottomSheet = forwardRef<
{ places, mapId, selectedFilter, onClickFilterButton, onRefreshOldPlace },
ref,
) => {
const [placeList, setPlaceList] = useState<PlaceType[]>([])
const { data: slicedPlaceList, listRef } = useInfiniteScroll<PlaceType>({
const [placeList, setPlaceList] = useState<KorrkPlace[]>([])
const { data: slicedPlaceList, listRef } = useInfiniteScroll<KorrkPlace>({
totalData: places || [],
itemsPerPage: 10,
})
Expand All @@ -46,14 +46,14 @@ const PlaceListBottomSheet = forwardRef<
(selectedFilter?.category !== 'all' ? 1 : 0) +
(selectedFilter?.tags.length ?? 0)

const getIsLike = (place: PlaceType): boolean => {
const getIsLike = (place: KorrkPlace): boolean => {
if (typeof userId === 'undefined') return false

if (place.likedUser?.some((liked) => liked.id === userId)) return true

return false
}
const handleLike = async (place: PlaceType) => {
const handleLike = async (place: KorrkPlace) => {
if (!userId) return
try {
setPlaceList((prevPlaces) =>
Expand Down
38 changes: 38 additions & 0 deletions src/app/place/[placeId]/(detail)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use client'

import type { ReactNode } from 'react'
import { usePathname } from 'next/navigation'

import AccessibleIconButton from '@/components/common/accessible-icon-button'
import useSafeRouter from '@/hooks/use-safe-router'

const PlaceDetailLayout = ({ children }: { children: ReactNode }) => {
const router = useSafeRouter()
const pathname = usePathname()

return (
<div className="relative flex min-h-dvh flex-col bg-neutral-700">
<header className="absolute left-0 top-0 z-[100] h-[60px] w-full bg-gradient-to-t from-[rgba(33,33,36,0)] to-[rgba(33,33,36,0.6)]">
<AccessibleIconButton
icon={{ type: 'caretLeft', size: 'xl' }}
label="뒤로 가기"
className="absolute left-[10px] top-[26px]"
onClick={() => router.back()}
/>

<AccessibleIconButton
icon={{ type: 'mapView', size: 'xl', fill: 'neutral-000' }}
label="지도"
className="absolute right-[10px] top-[26px] z-[120]"
onClick={() => {
router.push(`${pathname}/map`)
}}
/>
</header>

{children}
</div>
)
}

export default PlaceDetailLayout
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import dynamic from 'next/dynamic'
import { getMapId } from '@/services/map-id'
import { api } from '@/utils/api'

const PlaceBox = dynamic(() => import('./place-box'), { ssr: false })
const PlaceDetail = dynamic(() => import('./place-detail'), { ssr: false })

const PlaceDetail = async ({ params }: { params?: { placeId?: number } }) => {
const Place = async ({ params }: { params?: { placeId?: number } }) => {
const mapId = (await getMapId()) || ''
const response =
!!mapId && !!params?.placeId
Expand All @@ -16,7 +16,7 @@ const PlaceDetail = async ({ params }: { params?: { placeId?: number } }) => {
: null
const place = response?.data

return place && <PlaceBox place={place} mapId={mapId} />
return place && <PlaceDetail place={place} mapId={mapId} />
}

export default PlaceDetail
export default Place
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,30 @@ import useFetch from '@/hooks/use-fetch'
import useSafeRouter from '@/hooks/use-safe-router'
import useUserGeoLocation from '@/hooks/use-user-geo-location'
import { APIError } from '@/models/api/index'
import type { PlaceDetail } from '@/models/api/place'
import type { PlaceDetail as PlaceDetailType } from '@/models/api/place'
import { api } from '@/utils/api'
import { formatDistance, getDistance } from '@/utils/location'
import { roundToNthDecimal } from '@/utils/number'
import { allowUserPositionStorage } from '@/utils/storage'
import cn from '@/utils/cn'
import { sendGAEvent } from '@next/third-parties/google'
import { revalidatePlaces } from '@/app/actions'
import type { MapInfo } from '@/models/map'

interface PlaceBoxProps {
place: PlaceDetail
mapId: string
interface PlaceDetailProps {
place: PlaceDetailType
mapId: MapInfo['id']
}

const PlaceBox = ({ place, mapId }: PlaceBoxProps) => {
const PlaceDetail = ({ place, mapId }: PlaceDetailProps) => {
let createdById = place.createdBy?.id ?? -1
const { data: createdUser } = useFetch(() => api.users.id.get(createdById), {
enabled: createdById !== -1,
})
const [isLikePlace, setIsLikePlace] = useState(false)
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false)
const [isRecentlyLike, setIsRecentlyLike] = useState<boolean | null>(null)

const router = useSafeRouter()
const isAlreadyPick = place.isRegisteredPlace
const { userLocation } = useUserGeoLocation()
Expand Down Expand Up @@ -270,4 +272,4 @@ const PlaceBox = ({ place, mapId }: PlaceBoxProps) => {
)
}

export default PlaceBox
export default PlaceDetail
4 changes: 2 additions & 2 deletions src/app/place/[placeId]/loading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Icon from '@/components/common/icon'
import Skeleton from '@/components/common/skeleton'
import PlaceDivider from '@/components/place/place-divider'

const PlaceBoxSkeleton = () => {
const PlaceDetailSkeleton = () => {
return (
<div className="relative flex min-h-dvh flex-col bg-neutral-700">
<Skeleton className="h-[200px] w-full" />
Expand Down Expand Up @@ -77,4 +77,4 @@ const PlaceBoxSkeleton = () => {
)
}

export default PlaceBoxSkeleton
export default PlaceDetailSkeleton
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
'use client'

import type { ReactNode } from 'react'
import { usePathname, useRouter } from 'next/navigation'

import AccessibleIconButton from '@/components/common/accessible-icon-button'
import useSafeRouter from '@/hooks/use-safe-router'

const PlaceDetailLayout = ({ children }: { children: ReactNode }) => {
const router = useRouter()
const pathname = usePathname()

if (pathname.includes('register')) {
return <>{children}</>
}
const PlaceMapLayout = ({ children }: { children: ReactNode }) => {
const router = useSafeRouter()

return (
<div className="relative flex min-h-dvh flex-col bg-neutral-700">
Expand All @@ -28,4 +23,4 @@ const PlaceDetailLayout = ({ children }: { children: ReactNode }) => {
)
}

export default PlaceDetailLayout
export default PlaceMapLayout
19 changes: 19 additions & 0 deletions src/app/place/[placeId]/map/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import PlaceMapBox from '@/app/place/[placeId]/map/place-map-box'
import { getMapId } from '@/services/map-id'
import { api } from '@/utils/api'

const PlaceMap = async ({ params }: { params?: { placeId?: number } }) => {
const mapId = (await getMapId()) || ''
const response =
!!mapId && !!params?.placeId
? await api.place.mapId.kakao.kakaoPlaceId.get({
mapId,
kakaoPlaceId: params?.placeId ?? -1,
})
: null
const place = response?.data

return place && <PlaceMapBox place={place} mapId={mapId} />
}

export default PlaceMap
54 changes: 54 additions & 0 deletions src/app/place/[placeId]/map/place-map-box.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
'use client'

import GPSButton from '@/components/kakao-map/gps-button'
import KakaoMap from '@/components/kakao-map/kakao-map'
import Marker from '@/components/kakao-map/marker'
import PlaceMapPopup from '@/components/place/place-map-popup'
import useSafeRouter from '@/hooks/use-safe-router'
import type { PlaceDetail } from '@/models/api/place'
import type { ClassName } from '@/models/common'
import type { MapInfo } from '@/models/map'
import { getMarkerType } from '@/services/marker'
import cn from '@/utils/cn'

interface PlaceMapBoxProps extends ClassName {
place: PlaceDetail
mapId: MapInfo['id']
}

const PlaceMapBox = ({ className, place, mapId }: PlaceMapBoxProps) => {
const type = getMarkerType(place.category, true)
const router = useSafeRouter()

return (
<div
className={cn(
'flex min-h-dvh w-full flex-col items-center justify-center bg-neutral-700 px-5',
className,
)}
>
<KakaoMap
className="h-dvh w-[calc(100%+40px)]"
center={{ lat: place.y, lng: place.x }}
>
<Marker
key={place.kakaoId}
latitude={place.y}
longitude={place.x}
isSaved={place.isRegisteredPlace}
type={type}
/>

<div className="absolute bottom-5 z-10 flex w-full flex-col items-end gap-4 px-5">
<GPSButton />

<button onClick={() => router.safeBack()} className="w-full">
<PlaceMapPopup mapId={mapId} selectedPlace={place} />
</button>
</div>
</KakaoMap>
</div>
)
}

export default PlaceMapBox
64 changes: 14 additions & 50 deletions src/app/place/[placeId]/register/page.tsx
Original file line number Diff line number Diff line change
@@ -1,59 +1,23 @@
'use client'

import { useEffect, useState } from 'react'

import RegisterBox from './register-box'

import { notify } from '@/components/common/custom-toast'
import LoadingIndicator from '@/components/common/loading-indicator'
import { APIError } from '@/models/api/index'
import type { TagItem } from '@/models/api/maps'
import type { PlaceDetail } from '@/models/api/place'
import { getMapId } from '@/services/map-id'
import { api } from '@/utils/api'

const PlaceRegister = ({ params }: { params?: { placeId?: number } }) => {
const [place, setPlace] = useState<PlaceDetail | null>(null)
const [tags, setTags] = useState<TagItem[]>([])
const [mapId, setMapId] = useState<string>('')

useEffect(() => {
const fetchTags = async () => {
try {
const validMapId = await getMapId()

if (!validMapId || !params?.placeId) {
throw new Error('잘못된 접근입니다.')
}
setMapId(validMapId)

const { data: tagData } = await api.maps.id.tag.get(validMapId)
setTags(tagData)
const { data: placeData } =
await api.place.mapId.kakao.kakaoPlaceId.get({
mapId: validMapId,
kakaoPlaceId: params.placeId,
})
setPlace(placeData)
} catch (error) {
if (error instanceof APIError) {
notify.error(error.message)
return
}
notify.error('예상치 못한 오류가 발생했습니다.')
}
}
fetchTags()
}, [mapId, params?.placeId])
const PlaceRegister = async ({ params }: { params?: { placeId?: number } }) => {
const mapId = (await getMapId()) || ''
const responsePlace =
!!mapId && !!params?.placeId
? await api.place.mapId.kakao.kakaoPlaceId.get({
mapId,
kakaoPlaceId: params?.placeId ?? -1,
})
: null
const place = responsePlace?.data
const responseTags =
!!mapId && !!params?.placeId ? await api.maps.id.tag.get(mapId) : null
const tags = responseTags?.data

return (
<>
{place ? (
<RegisterBox place={place} tags={tags} mapId={mapId} />
) : (
<LoadingIndicator />
)}
</>
place && tags && <RegisterBox place={place} tags={tags} mapId={mapId} />
)
}

Expand Down
Loading

0 comments on commit 8d57d2e

Please sign in to comment.