Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: merge dev branch to main #162

Merged
merged 14 commits into from
Oct 4, 2024
Merged
7 changes: 6 additions & 1 deletion src/app/actions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use server'

import { cookies } from 'next/headers'
import { revalidateTag } from 'next/cache'

const setCookie = async (name: string, value: string) => {
cookies().set(name, value)
Expand All @@ -10,4 +11,8 @@ const deleteCookie = async (name: string) => {
cookies().delete(name)
}

export { setCookie, deleteCookie }
const revalidatePlaces = async (mapId: string) => {
revalidateTag(`places-${mapId}`)
}

export { setCookie, deleteCookie, revalidatePlaces }
8 changes: 5 additions & 3 deletions src/app/map/[mapId]/place-list-bottom-sheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { APIError } from '@/models/api/index'
import type { PlaceType } 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
Expand All @@ -30,16 +31,17 @@ const PlaceListBottomSheet = forwardRef<
{ places, mapId, selectedFilter, onClickFilterButton, onRefreshOldPlace },
ref,
) => {
const [placeList, setPlaceList] = useState<PlaceType[]>(places || [])
const [placeList, setPlaceList] = useState<PlaceType[]>([])
const { data: slicedPlaceList, listRef } = useInfiniteScroll<PlaceType>({
totalData: places || [],
itemsPerPage: 10,
})

const { data: user, revalidate } = useFetch(api.users.me.get, {
const { data: user } = useFetch(api.users.me.get, {
key: ['user'],
})
const userId = user?.id

const numOfSelectedFilter =
(selectedFilter?.category !== 'all' ? 1 : 0) +
(selectedFilter?.tags.length ?? 0)
Expand Down Expand Up @@ -77,7 +79,7 @@ const PlaceListBottomSheet = forwardRef<
placeId: place.place.id,
})
}
revalidate(['places', mapId])
revalidatePlaces(mapId)
} catch (error) {
if (error instanceof APIError) {
notify.error(error.message)
Expand Down
25 changes: 1 addition & 24 deletions src/app/my-map/[mapId]/crew-info-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,13 @@ import type { User } from '@/models/user'
import cn from '@/utils/cn'
import CrewInfoReadOnlyItem from './crew-info-read-only-item'
import CrewInfoEditableItem from './crew-info-editable-item'
import { getColorForName } from '@/utils/avatar-color'
interface CrewInfoListProps extends ClassName {
user: User
mapInfo: MapInfo
refetchMapInfo: VoidFunction
}

const memberColors = [
'coral',
'dark-blue',
'sky-blue',
'violet',
'green',
] as const
type AvatarColor = (typeof memberColors)[number]

const hashString = (str: string): number => {
let hash = 0
for (let i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash)
}
return Math.abs(hash)
}

const getColorForName = (name: string): AvatarColor => {
const hash = hashString(name)

const colorIndex = hash % memberColors.length
return memberColors[colorIndex]
}

const CrewInfoList = ({
mapInfo,
className,
Expand Down
1 change: 1 addition & 0 deletions src/app/my-map/[mapId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const MyMap = ({ params: { mapId } }: { params: { mapId: string } }) => {
refetchMapInfo={refetch}
/>
<CrewInfoList
className="pb-[102px]"
mapInfo={mapInfo}
user={user}
refetchMapInfo={refetch}
Expand Down
7 changes: 6 additions & 1 deletion src/app/place/[placeId]/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
'use client'

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

import AccessibleIconButton from '@/components/common/accessible-icon-button'

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

if (pathname.includes('register')) {
return <>{children}</>
}

return (
<div className="relative flex min-h-dvh flex-col bg-neutral-700">
Expand Down
2 changes: 2 additions & 0 deletions src/app/place/[placeId]/like-tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { AnimatePresence, motion } from 'framer-motion'
import cn from '@/utils/cn'
import type { ClassName } from '@/models/common'
import Avatar from '@/components/common/avatar'
import { getColorForName } from '@/utils/avatar-color'

interface LikeToolTipProps extends ClassName {
likeMembers: string[]
Expand Down Expand Up @@ -70,6 +71,7 @@ const LikeToolTip = ({ likeMembers, className, onClick }: LikeToolTipProps) => {
<LikeAvatarMotion
key={`${avatar}-${index}`}
value={avatar}
colorScheme={getColorForName(avatar)}
size="sm"
className="p-0 transition-all"
{...(index === 0
Expand Down
18 changes: 10 additions & 8 deletions src/app/place/[placeId]/place-box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ 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'

interface PlaceBoxProps {
place: PlaceDetail
Expand All @@ -40,7 +41,7 @@ const PlaceBox = ({ place, mapId }: PlaceBoxProps) => {
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false)
const [isRecentlyLike, setIsRecentlyLike] = useState<boolean | null>(null)
const router = useSafeRouter()
const [isAlreadyPick, setIsAlreadyPick] = useState(place.isRegisteredPlace)
const isAlreadyPick = place.isRegisteredPlace
const { userLocation } = useUserGeoLocation()
const isAllowPosition = allowUserPositionStorage.getValueOrNull()
const diffDistance = getDistance(
Expand All @@ -50,10 +51,9 @@ const PlaceBox = ({ place, mapId }: PlaceBoxProps) => {
place.x,
)

const { data: user, revalidate } = useFetch(api.users.me.get, {
const { data: user } = useFetch(api.users.me.get, {
key: ['user'],
})

const { data: mapInfo, isFetching } = useFetch(() => api.maps.id.get(mapId), {
enabled: !!mapId,
})
Expand All @@ -76,6 +76,10 @@ const PlaceBox = ({ place, mapId }: PlaceBoxProps) => {
return likedUserCount + recentlyLikedBonus
})()

useEffect(() => {
router.refresh()
}, [router])

useEffect(() => {
if (!place || !user) return
setIsLikePlace(
Expand All @@ -93,7 +97,7 @@ const PlaceBox = ({ place, mapId }: PlaceBoxProps) => {
placeId: place.id,
mapId,
})
revalidate(['places', mapId])
revalidatePlaces(mapId)
} catch (error) {
setIsLikePlace(false)
setIsRecentlyLike(
Expand All @@ -115,7 +119,7 @@ const PlaceBox = ({ place, mapId }: PlaceBoxProps) => {
placeId: place.id,
mapId,
})
revalidate(['places', mapId])
revalidatePlaces(mapId)
} catch (error) {
setIsLikePlace(true)
setIsRecentlyLike(
Expand All @@ -136,9 +140,8 @@ const PlaceBox = ({ place, mapId }: PlaceBoxProps) => {
mapId,
})

setIsAlreadyPick(false)
revalidatePlaces(mapId)
setIsDeleteModalOpen(false)
window.location.reload()
} catch (error) {
if (error instanceof APIError || error instanceof Error) {
notify.error(error.message)
Expand All @@ -154,7 +157,6 @@ const PlaceBox = ({ place, mapId }: PlaceBoxProps) => {
label: 'register',
})
router.push(`/place/${place.kakaoId}/register`)
revalidate(['places', mapId])
} catch (error) {
if (error instanceof APIError || error instanceof Error) {
notify.error(error.message)
Expand Down
2 changes: 2 additions & 0 deletions src/app/place/[placeId]/place-liked-users.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { LikeUser } from '@/components/place/types'
import type { ClassName } from '@/models/common'
import type { User } from '@/models/user'
import { getMapId } from '@/services/map-id'
import { getColorForName } from '@/utils/avatar-color'
import cn from '@/utils/cn'
import { useRouter } from 'next/navigation'

Expand Down Expand Up @@ -32,6 +33,7 @@ const PlaceLikedUser = ({ likedUser, className, me }: PlaceLikedUserProps) => {
<Avatar
value={user.nickname}
imageUrl={user.profileImage}
colorScheme={getColorForName(user.nickname)}
me={me ? me.id === user.id : false}
/>
<Typography size="body1" color="neutral-100">
Expand Down
6 changes: 3 additions & 3 deletions src/app/place/[placeId]/register/register-box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import type { TagItem } from '@/models/api/maps'
import type { PlaceDetail } from '@/models/api/place'
import { api } from '@/utils/api'
import get조사 from '@/utils/조사'
import useFetch from '@/hooks/use-fetch'
import { revalidatePlaces } from '@/app/actions'

const toTagNames = (tags: TagItem[]): TagItem['name'][] =>
tags.map((tag) => tag.name)
Expand All @@ -29,7 +29,6 @@ const RegisterBox = ({
tags: TagItem[]
mapId: string
}) => {
const { revalidate } = useFetch()
const router = useSafeRouter()
const [selectedTags, setSelectedTags] = useState<TagItem[]>([])
const [isOpenBackModal, setIsOpenBackModal] = useState(false)
Expand All @@ -46,7 +45,8 @@ const RegisterBox = ({
tagNames: toTagNames(selectedTags),
})

revalidate(['map-list'])
revalidatePlaces(mapId)

notify.success('맛집 등록이 완료되었습니다.')
router.safeBack({ defaultHref: `/place/${place.kakaoId}` })
} catch (error) {
Expand Down
2 changes: 2 additions & 0 deletions src/app/profile/[mapId]/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { useState } from 'react'
import LikedPlacePanel from './liked-place-panel'
import RegisterededPlacePanel from './registered-place-panel'
import type { MapInfo } from '@/models/map'
import { getColorForName } from '@/utils/avatar-color'

type PlaceFilter = 'register' | 'liked'

Expand Down Expand Up @@ -49,6 +50,7 @@ const Profile = ({
<Avatar
value={userData?.nickname}
imageUrl={userData?.profileImage}
colorScheme={getColorForName(userData?.nickname || '')}
className={`h-20 w-20 text-[40px] ${!userData?.profileImage && 'border-2 border-[#17171A] border-opacity-20'}`}
/>
<Typography size="h3">{userData?.nickname}</Typography>
Expand Down
7 changes: 4 additions & 3 deletions src/app/profile/[mapId]/[id]/place-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { api } from '@/utils/api'
import cn from '@/utils/cn'
import { roundOnePoint } from '@/utils/number'
import { getStarByScore } from '@/utils/score'
import { revalidatePlaces } from '@/app/actions'

interface PlaceItemProps extends ClassName {
selectedPlace: PlaceType
Expand All @@ -29,7 +30,7 @@ interface PlaceItemProps extends ClassName {
const PlaceItem = forwardRef<HTMLAnchorElement, PlaceItemProps>(
({ selectedPlace, className, mapId, onRefreshOldPlace }, ref) => {
const [isLikePlace, setIsLikePlace] = useState(false)
const { data: user, revalidate } = useFetch(api.users.me.get, {
const { data: user } = useFetch(api.users.me.get, {
key: ['user'],
})

Expand All @@ -56,7 +57,7 @@ const PlaceItem = forwardRef<HTMLAnchorElement, PlaceItemProps>(
placeId: place.id,
mapId,
})
revalidate(['places', mapId])
revalidatePlaces(mapId)
} catch (error) {
setIsLikePlace(false)
if (error instanceof APIError || error instanceof Error) {
Expand All @@ -78,7 +79,7 @@ const PlaceItem = forwardRef<HTMLAnchorElement, PlaceItemProps>(
placeId: place.id,
mapId,
})
revalidate(['places', mapId])
revalidatePlaces(mapId)
} catch (error) {
setIsLikePlace(true)
if (error instanceof APIError || error instanceof Error) {
Expand Down
8 changes: 4 additions & 4 deletions src/app/recommendation/ai-recommendation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from './guide'
import { api } from '@/utils/api'
import type { LocationType } from '@/models/kakao-map'
import { recommendationChatsStorage } from '@/utils/storage'

interface AIRecommendationProps {
authorization: boolean
Expand Down Expand Up @@ -58,10 +59,7 @@ export const handleAIRecommendation = async ({
try {
const x = String(userLocation?.longitude || '')
const y = String(userLocation?.latitude || '')
const recommendationApi =
process.env.NODE_ENV === 'production'
? api.gpt.restaurants.recommend
: api.gpt.restaurants.recommend.test
const recommendationApi = api.gpt.restaurants.recommend
const response = await recommendationApi.get(question, x, y)

const reader = response.body.getReader()
Expand Down Expand Up @@ -140,6 +138,8 @@ export const handleAIRecommendation = async ({
setChats((prev) =>
prev.map((chat, index) => {
if (isLastChat(index)) {
recommendationChatsStorage.set(prev)

return {
...chat,
suggestionKeywords: ['처음으로'],
Expand Down
1 change: 1 addition & 0 deletions src/app/recommendation/chat-box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const ChatBox = ({ chats, className, onClickSuggestion }: ChatBoxProps) => {
chat={chat}
type={chat.type}
isFirst={index === 0}
isLast={index === chats.length - 1}
onClickSuggestion={(suggestion) => {
onClickSuggestion(suggestion)
}}
Expand Down
11 changes: 7 additions & 4 deletions src/app/recommendation/chat-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,20 @@ interface ChatItemProps extends ClassName {
export const AISuggestion = ({
chat,
isFirst,
isLast,
type,
className,
onClickSuggestion,
}: ChatItemProps & {
isFirst: boolean
isLast: boolean
type: 'gpt-typing' | 'gpt-stream'
onClickSuggestion: (suggestion: string) => void
}) => {
const { typingText, typingStart, typingComplete } = useTypewriter(
chat.value,
100,
type === 'gpt-typing',
type === 'gpt-typing' && isLast,
)

if (chat.type === 'user') return null
Expand Down Expand Up @@ -67,8 +69,7 @@ export const AISuggestion = ({
color="neutral-000"
className="whitespace-pre-line"
>
{chat.type === 'gpt-stream' && chat.value}
{chat.type === 'gpt-typing' && typingText}
{chat.type === 'gpt-typing' && isLast ? typingText : chat.value}
</Typography>
)}
</div>
Expand All @@ -90,11 +91,13 @@ export const AISuggestion = ({
{typingComplete && chat.suggestionKeywords.length > 0 && (
<ul
className={cn(
'ml-[44px] flex animate-fadein gap-[6px] transition-all',
'ml-[44px] flex gap-[6px]',
isLast && 'animate-fadein transition-all',
chat.suggestionKeywords[0] === '강남에 맛있는 돈까스 식당 추천해줘'
? 'flex-col'
: '',
)}
style={{ minHeight: chat.suggestionKeywords.length * 35 }}
>
{chat.suggestionKeywords.map((suggestion) => {
if (
Expand Down
2 changes: 1 addition & 1 deletion src/app/recommendation/guide.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { Chat } from './type'

export const initialRecommendChat: Chat = {
type: 'gpt-typing',
value: `어떤 맛집을 찾고 계시나요?`,
value: `어떤 맛집을 찾고 계신가요?`,
suggestionKeywords: [
'강남에 맛있는 돈까스 식당 추천해줘',
'성수에 인기있는 양식 식당 추천해줘',
Expand Down
Loading
Loading