Skip to content

Commit

Permalink
feat: auth in place detail (#133)
Browse files Browse the repository at this point in the history
* feat: auth in place detail

* chore: test

* feat: ask gps when click gps button

* chore: notify

* chore: delete isFetching

* refactor: header graident
  • Loading branch information
poiu694 committed Sep 21, 2024
1 parent 9ff6796 commit 0a71333
Show file tree
Hide file tree
Showing 11 changed files with 183 additions and 55 deletions.
4 changes: 4 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,8 @@ body {
.invitation-gradient {
background: linear-gradient(180deg, rgba(33, 33, 36, 0%) 0%, #212124 30%);
}

.header-gradient {
background: linear-gradient(180deg, #212124 70%, rgba(33, 33, 36, 0%) 100%);
}
}
49 changes: 31 additions & 18 deletions src/app/place/[placeId]/place-box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { formatDistance, getDistance } from '@/utils/location'
import { roundToNthDecimal } from '@/utils/number'
import { allowUserPositionStorage } from '@/utils/storage'
import ProxyImage from '@/components/common/proxy-image'
import cn from '@/utils/cn'

interface PlaceBoxProps {
place: PlaceDetail
Expand All @@ -39,7 +40,7 @@ const PlaceBox = ({ place, mapId }: PlaceBoxProps) => {
const [isRecentlyLike, setIsRecentlyLike] = useState<boolean | null>(null)
const router = useSafeRouter()
const [isAlreadyPick, setIsAlreadyPick] = useState(place.isRegisteredPlace)
const userLocation = useUserGeoLocation()
const { userLocation } = useUserGeoLocation()
const isAllowPosition = allowUserPositionStorage.getValueOrNull()
const diffDistance = getDistance(
userLocation.latitude,
Expand All @@ -52,6 +53,12 @@ const PlaceBox = ({ place, mapId }: PlaceBoxProps) => {
key: ['user'],
})

const { data: mapInfo, isFetching } = useFetch(() => api.maps.id.get(mapId), {
enabled: !!mapId,
})
const myRole =
mapInfo?.users.find((mapUser) => mapUser.id === user?.id)?.role ?? 'READ'

const numOfLikes = (() => {
const likedUsersCount = place.likedUsers?.length ?? 0

Expand Down Expand Up @@ -214,25 +221,31 @@ const PlaceBox = ({ place, mapId }: PlaceBoxProps) => {
<KakaoRating
rating={roundToNthDecimal(place.score, 2)}
placeId={place.kakaoId}
className="mb-[102px] px-5 py-5"
className={cn(
'px-5 py-5',
myRole !== 'READ' && !isFetching && 'mb-[102px]',
)}
/>

<footer className="px-5">
<div className="invitation-gradient fixed bottom-0 left-1/2 flex h-[102px] w-full max-w-[420px] -translate-x-1/2 items-center justify-center px-5">
{isAlreadyPick ? (
<PlaceActionButtons
like={isLikePlace}
onLikePlace={handleLikePlace}
onDeletePlace={() => setIsDeleteModalOpen(true)}
onUnLikePlace={handleUnLikePlace}
/>
) : (
<Button type="button" onClick={handleRegisterPlace}>
맛집 등록하기
</Button>
)}
</div>
</footer>
{myRole !== 'READ' && !isFetching && (
<footer className="px-5">
<div className="invitation-gradient fixed bottom-0 left-1/2 flex h-[102px] w-full max-w-[420px] -translate-x-1/2 items-center justify-center px-5">
{isAlreadyPick ? (
<PlaceActionButtons
like={isLikePlace}
role={myRole}
onLikePlace={handleLikePlace}
onDeletePlace={() => setIsDeleteModalOpen(true)}
onUnLikePlace={handleUnLikePlace}
/>
) : (
<Button type="button" onClick={handleRegisterPlace}>
맛집 등록하기
</Button>
)}
</div>
</footer>
)}
</div>

<PlaceDeleteModal
Expand Down
37 changes: 22 additions & 15 deletions src/app/recommendation/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ const Recommendation = () => {
const [chats, setChats] = useState<Chat[]>([initialRecommendChat])
const [isFinish, setIsFinish] = useState(false)
const [isLoading, setIsLoading] = useState(false)
const [, setIsOpenShowModal] = useState(false)
const bottomChat = useRef<HTMLDivElement>(null)
const router = useSafeRouter()

Expand Down Expand Up @@ -179,23 +180,29 @@ const Recommendation = () => {
return (
<>
<div className="flex min-h-dvh flex-col bg-neutral-700">
<header className="relative flex items-center pt-4">
<AccessibleIconButton
icon={{ type: 'caretLeft', size: 'xl' }}
label="이전 페이지"
className="p-[10px]"
onClick={() => router.safeBack()}
/>
<Typography
className="absolute left-1/2 translate-x-[-50%]"
as="h1"
size="body0"
>
AI 맛집 추천받기
</Typography>
<header className="fixed w-full h-[80px] px-[10px] header-gradient z-[100]">
<div className='w-full relative h-[80px] flex justify-between items-center'>
<AccessibleIconButton
icon={{ type: 'caretLeft', size: 'xl' }}
label="이전 페이지"
onClick={() => router.safeBack()}
/>
<Typography
className="absolute left-1/2 translate-x-[-50%]"
as="h1"
size="body0"
>
AI 맛집 추천받기
</Typography>
<AccessibleIconButton
icon={{ type: 'info', size: 'xl' }}
label="사용 정보 확인하기"
onClick={() => setIsOpenShowModal(true)}
/>
</div>
</header>

<section className="no-scrollbar max-h-[calc(100vh-156px)] flex-1 overflow-y-scroll">
<section className="no-scrollbar max-h-[calc(100vh-156px)] flex-1 overflow-y-scroll mt-[80px]">
<div className="relative flex flex-col items-center justify-center gap-4 pb-6">
<img
src="/images/ai.png"
Expand Down
2 changes: 1 addition & 1 deletion src/app/search/[query]/result-search-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const ResultSearchListBox = ({
const { data: user, revalidate } = useFetch(api.users.me.get, {
key: ['user'],
})
const userLocation = useUserGeoLocation()
const { userLocation } = useUserGeoLocation()
const isAllowPosition = allowUserPositionStorage.getValueOrNull()
const [likeInfoPlaces, setLikeInfoPlaces] = useState(
[...places].map((place) => ({
Expand Down
2 changes: 1 addition & 1 deletion src/app/search/suggest-place-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface SuggestPlaceListProps {
}

const SuggestPlaceList = ({ places, query }: SuggestPlaceListProps) => {
const userLocation = useUserGeoLocation()
const { userLocation } = useUserGeoLocation()
const isAllowPosition = allowUserPositionStorage.getValueOrNull()

return (
Expand Down
67 changes: 67 additions & 0 deletions src/app/test-design/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client'

import BoardingDivider from '@/components/boarding-pass/boarding-divider'
import Typography from '@/components/common/typography'

const TestDesign = () => {
Expand Down Expand Up @@ -33,6 +34,72 @@ const TestDesign = () => {
<Typography size="body0" color="neutral-000" className="font-[900]">
900
</Typography>

<BoardingDivider className="h-[50px] w-full bg-neutral-500" />

<Typography
size="body0"
color="neutral-000"
className="font-serif font-[100]"
>
100
</Typography>
<Typography
size="body0"
color="neutral-000"
className="font-serif font-[200]"
>
200
</Typography>
<Typography
size="body0"
color="neutral-000"
className="font-serif font-[300]"
>
300
</Typography>
<Typography
size="body0"
color="neutral-000"
className="font-serif font-[400]"
>
400
</Typography>
<Typography
size="body0"
color="neutral-000"
className="font-serif font-[500]"
>
500
</Typography>
<Typography
size="body0"
color="neutral-000"
className="font-serif font-[600]"
>
weight-600
</Typography>
<Typography
size="body0"
color="neutral-000"
className="font-serif font-[700]"
>
weight-700
</Typography>
<Typography
size="body0"
color="neutral-000"
className="font-serif font-[800]"
>
800
</Typography>
<Typography
size="body0"
color="neutral-000"
className="font-serif font-[900]"
>
900
</Typography>
</div>
</>
)
Expand Down
13 changes: 12 additions & 1 deletion src/components/kakao-map/gps-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import useUserGeoLocation from '@/hooks/use-user-geo-location'
import useWindowSize from '@/hooks/use-window-size'
import { getCorners } from '@/utils/map'
import { mapBoundSessionStorage } from '@/utils/storage'
import { notify } from '../common/custom-toast'

const BUTTON_OFFSET_Y = 16
const BUTTON_HEIGHT = 11
Expand All @@ -16,7 +17,8 @@ interface GpsButtonProps {
}

const GpsButton = ({ topOfBottomBounds }: GpsButtonProps) => {
const userLocation = useUserGeoLocation()
const { userLocation, allowLocation, handleUserLocation } =
useUserGeoLocation()
const [gpsBottomPositionY, setGpsBottomPositionY] = useState(BUTTON_OFFSET_Y)
const [gpsMode, setGpsMode] = useState(false)
const { height: windowHeight } = useWindowSize()
Expand All @@ -25,6 +27,15 @@ const GpsButton = ({ topOfBottomBounds }: GpsButtonProps) => {
const handleGpsClick = () => {
if (!map) return

if (!allowLocation) {
const isAllowLocation = handleUserLocation({
onError: () => {
notify.error('위치 권한을 허용해 주세요.')
},
})
if (!isAllowLocation) return
}

if (!gpsMode) {
const location = new window.kakao.maps.LatLng(
userLocation.latitude,
Expand Down
2 changes: 1 addition & 1 deletion src/components/kakao-map/kakao-map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const KakaoMap = forwardRef<HTMLElement, KakaoMapProps>(
lat: (bounds.latitude1 + bounds.latitude2) / 2,
lng: (bounds.longitude1 + bounds.longitude2) / 2,
}
const userLocation = useUserGeoLocation()
const { userLocation } = useUserGeoLocation()
const userLocationCenter = {
lat: userLocation.latitude,
lng: userLocation.longitude,
Expand Down
15 changes: 12 additions & 3 deletions src/components/place/place-action-buttons.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import AccessibleIconButton from '@/components/common/accessible-icon-button'
import Button from '@/components/common/button'
import type { ClassName } from '@/models/common'
import type { MapRole } from '@/models/map'
import cn from '@/utils/cn'

interface PlaceActionButtonsProps extends ClassName {
like?: boolean
role: MapRole
onLikePlace: VoidFunction
onUnLikePlace: VoidFunction
onDeletePlace: VoidFunction
Expand All @@ -13,6 +15,7 @@ interface PlaceActionButtonsProps extends ClassName {
const PlaceActionButtons = ({
className,
like,
role,
onLikePlace,
onUnLikePlace,
onDeletePlace,
Expand All @@ -35,9 +38,15 @@ const PlaceActionButtons = ({
onClick={like ? onUnLikePlace : onLikePlace}
/>
</div>
<Button colorScheme="neutral" onClick={onDeletePlace}>
맛집 삭제하기
</Button>
{role === 'READ' ? (
<Button colorScheme="neutral" disabled>
맛집 삭제 권한이 없어요
</Button>
) : (
<Button colorScheme="neutral" onClick={onDeletePlace}>
맛집 삭제하기
</Button>
)}
</div>
)
}
Expand Down
45 changes: 31 additions & 14 deletions src/hooks/use-user-geo-location.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from 'react'
import { useCallback, useState } from 'react'

import { useIsomorphicLayoutEffect } from './use-isomorphic-layout-effect'

Expand All @@ -22,23 +22,40 @@ const useUserGeoLocation = () => {
const [location, setLocation] = useState<LocationType>(
INITIAL_LATITUDE_LONGITUDE,
)
const [allowLocation, setAllowLocation] = useState(false)

const handleUserLocation = useCallback(
(options?: { onError: (error: GeolocationPositionError) => void }) => {
let isAllow = false
try {
navigator.geolocation.getCurrentPosition(
({ coords: { latitude, longitude } }) => {
allowUserPositionStorage.set(true)
setLocation({ latitude, longitude })
setAllowLocation(true)
isAllow = true
},
(err) => {
options?.onError(err)
setAllowLocation(false)
setLocation(INITIAL_LATITUDE_LONGITUDE)
},
)
} catch (err) {
allowUserPositionStorage.set(false)
setLocation(INITIAL_LATITUDE_LONGITUDE)
setAllowLocation(false)
}
return isAllow
},
[],
)

useIsomorphicLayoutEffect(() => {
try {
navigator.geolocation.getCurrentPosition(
({ coords: { latitude, longitude } }) => {
allowUserPositionStorage.set(true)
setLocation({ latitude, longitude })
},
() => setLocation(INITIAL_LATITUDE_LONGITUDE),
)
} catch (err) {
allowUserPositionStorage.set(false)
setLocation(INITIAL_LATITUDE_LONGITUDE)
}
handleUserLocation()
}, [])

return location
return { userLocation: location, allowLocation, handleUserLocation }
}

export default useUserGeoLocation
2 changes: 1 addition & 1 deletion src/models/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Creator, User } from './user'

import type { IconKey } from '@/components/common/icon'

type MapRole = 'ADMIN' | 'READ' | 'WRITE'
export type MapRole = 'ADMIN' | 'READ' | 'WRITE'

export interface MapMemberData {
id: User['id']
Expand Down

0 comments on commit 0a71333

Please sign in to comment.