Skip to content

Commit

Permalink
Merge pull request #126 from prgrms-web-devcourse-final-project/44-fe…
Browse files Browse the repository at this point in the history
…ature/social-data-binding

[Feature] #44 소셜, 프로필 페이지 데이터 바인딩
  • Loading branch information
shlee9999 authored Dec 5, 2024
2 parents ac1380a + 819f3e2 commit 7390324
Show file tree
Hide file tree
Showing 34 changed files with 466 additions and 307 deletions.
8 changes: 4 additions & 4 deletions .vscode/typescript.code-snippets
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@
" default:",
" throw new Error(message || '알 수 없는 오류가 발생했습니다.')",
" }",
" } else {",
" // 요청 자체가 실패한 경우",
" throw new Error('네트워크 연결을 확인해주세요')",
" }",
" } ",
" // 요청 자체가 실패한 경우",
" throw new Error('네트워크 연결을 확인해주세요')",
" ",
" }",
"",
" console.error('예상치 못한 에러:', error);",
Expand Down
1 change: 1 addition & 0 deletions lighthouserc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module.exports = {
// 'http://localhost:4173/walk',
'http://localhost:4173/login',
'http://localhost:4173/mypage',
'http://localhost:4173/profile/:id',
],
numberOfRuns: 2,
startServerReadyPattern: 'Local',
Expand Down
12 changes: 12 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,19 @@
"build-and-test": "npm run build && lhci autorun"
},
"dependencies": {
"@stomp/stompjs": "^7.0.0",
"@emotion/react": "^11.13.5",
"@emotion/styled": "^11.13.5",
"@mui/material": "^6.1.9",
"@mui/styled-engine-sc": "^6.1.9",
"@stomp/stompjs": "^7.0.0",
"@tanstack/react-query": "^5.62.2",
"@tanstack/react-query-devtools": "^5.62.2",
"d3": "^7.9.0",
"ios-style-picker": "^0.0.6",
"ol": "^10.2.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-error-boundary": "^4.1.2",
"react-helmet-async": "^2.0.5",
"react-icons": "^5.3.0",
"react-router-dom": "^6.28.0",
Expand All @@ -42,8 +43,8 @@
"@types/ol": "^7.0.0",
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.0",
"@types/sockjs-client": "^1.5.4",
"@types/react-textarea-autosize": "^8.0.0",
"@types/sockjs-client": "^1.5.4",
"@types/styled-components": "^5.1.34",
"@typescript-eslint/eslint-plugin": "^7.8.0",
"@typescript-eslint/parser": "^7.8.0",
Expand Down
48 changes: 48 additions & 0 deletions src/apis/chatRoom/createChatRoom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { AxiosError } from 'axios'
import { APIResponse, CommonAPIResponse, ErrorResponse } from '~types/api'
import { axiosInstance } from '~apis/axiosInstance'

export type CreateChatRoomRequest = {
opponentMemberId: number
}

export type CreateChatRoomResponse = Pick<
CommonAPIResponse,
'chatRoomId' | 'name' | 'lastMessage' | 'unreadMessageCount' | 'members'
>

/**
* 새로운 채팅방을 생성하고, 채팅방 정보를 반환합니다.
*? 이미 동일한 맴버와의 채팅방이 있다면, 해당 채팅방을 반환합니다.
*/
export const createChatRoom = async (req: CreateChatRoomRequest): Promise<APIResponse<CreateChatRoomResponse>> => {
try {
const { data } = await axiosInstance.post<APIResponse<CreateChatRoomResponse>>(`/chat/rooms`, req)
console.log(data.message || '채팅방 생성 성공')
console.log(data.data)
return data
} catch (error) {
if (error instanceof AxiosError) {
const { response } = error as AxiosError<ErrorResponse>

if (response) {
const { code, message } = response.data
switch (code) {
case 400:
throw new Error(message || '잘못된 요청입니다.')
case 401:
throw new Error(message || '인증에 실패했습니다.')
case 500:
throw new Error(message || '서버 오류가 발생했습니다.')
default:
throw new Error(message || '알 수 없는 오류가 발생했습니다.')
}
}
// 요청 자체가 실패한 경우
throw new Error('네트워크 연결을 확인해주세요')
}

console.error('예상치 못한 에러:', error)
throw new Error('다시 시도해주세요')
}
}
37 changes: 37 additions & 0 deletions src/apis/chatRoom/fetchChatRoomList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { AxiosError } from 'axios'
import { APIResponse, CommonAPIResponse, ErrorResponse } from '~types/api'
import { axiosInstance } from '~apis/axiosInstance'

export type FetchChatRoomListResponse = Array<
Pick<CommonAPIResponse, 'chatRoomId' | 'name' | 'lastMessage' | 'unreadMessageCount' | 'members'>
>

export const fetchChatRoomList = async (): Promise<APIResponse<FetchChatRoomListResponse>> => {
try {
const { data } = await axiosInstance.get<APIResponse<FetchChatRoomListResponse>>(`/chat/rooms`)
return data
} catch (error) {
if (error instanceof AxiosError) {
const { response } = error as AxiosError<ErrorResponse>

if (response) {
const { code, message } = response.data
switch (code) {
case 400:
throw new Error(message || '잘못된 요청입니다.')
case 401:
throw new Error(message || '인증에 실패했습니다.')
case 500:
throw new Error(message || '서버 오류가 발생했습니다.')
default:
throw new Error(message || '알 수 없는 오류가 발생했습니다.')
}
}
// 요청 자체가 실패한 경우
throw new Error('네트워크 연결을 확인해주세요')
}

console.error('예상치 못한 에러:', error)
throw new Error('다시 시도해주세요')
}
}
31 changes: 31 additions & 0 deletions src/apis/chatRoom/useCreateChatRoom.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useMutation, UseMutationResult } from '@tanstack/react-query'
import ChatModal from '~modals/ChatModal'
import { useModalStore } from '~stores/modalStore'
import { APIResponse } from '~types/api'
import { createChatRoom, CreateChatRoomRequest, CreateChatRoomResponse } from './createChatRoom'

export const useCreateChatRoom = (): UseMutationResult<
APIResponse<CreateChatRoomResponse>,
Error,
CreateChatRoomRequest
> & {
createRoom: (req: CreateChatRoomRequest) => void
} => {
const { pushModal } = useModalStore()

const mutation = useMutation<APIResponse<CreateChatRoomResponse>, Error, CreateChatRoomRequest>({
mutationFn: createChatRoom,
onSuccess: (data, { opponentMemberId }) => {
console.log(data.message || '채팅방 생성 성공')
pushModal(<ChatModal chatRoomId={data.data.chatRoomId} userId={opponentMemberId} />)
},
onError: error => {
console.error('채팅방 생성 실패:', error.message)
},
})

return {
...mutation,
createRoom: mutation.mutate,
}
}
28 changes: 28 additions & 0 deletions src/apis/chatRoom/useSocialData.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useSuspenseQueries } from '@tanstack/react-query'
import { fetchChatRoomList } from '~apis/chatRoom/fetchChatRoomList'
import { fetchFriendList } from '~apis/friend/fetchFriendList'
import { queryKey } from '~constants/queryKey'

export function useSocialData() {
const results = useSuspenseQueries({
queries: [
{
queryKey: queryKey.social.chatRoomList(),
queryFn: () => fetchChatRoomList().then(res => res.data),
},
{
queryKey: queryKey.social.friendList(),
queryFn: () => fetchFriendList().then(res => res.data),
},
],
})

const [chatListQuery, friendListQuery] = results

return {
chatList: chatListQuery.data ?? [],
friendList: friendListQuery.data ?? [],
isLoading: chatListQuery.isLoading || friendListQuery.isLoading,
isError: chatListQuery.isError || friendListQuery.isError,
}
}
37 changes: 37 additions & 0 deletions src/apis/friend/fetchFriendList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { AxiosError } from 'axios'
import { APIResponse, CommonAPIResponse, ErrorResponse } from '~types/api'
import { axiosInstance } from '~apis/axiosInstance'

export type FetchFriendListResponse = Array<
Pick<CommonAPIResponse, 'memberId' | 'gender' | 'familyRole' | 'profileImg' | 'name'>
>

export const fetchFriendList = async (): Promise<APIResponse<FetchFriendListResponse>> => {
try {
const { data } = await axiosInstance.get<APIResponse<FetchFriendListResponse>>(`/friend`)
return data
} catch (error) {
if (error instanceof AxiosError) {
const { response } = error as AxiosError<ErrorResponse>

if (response) {
const { code, message } = response.data
switch (code) {
case 400:
throw new Error(message || '잘못된 요청입니다.')
case 401:
throw new Error(message || '인증에 실패했습니다.')
case 500:
throw new Error(message || '서버 오류가 발생했습니다.')
default:
throw new Error(message || '알 수 없는 오류가 발생했습니다.')
}
}
// 요청 자체가 실패한 경우
throw new Error('네트워크 연결을 확인해주세요')
}

console.error('예상치 못한 에러:', error)
throw new Error('다시 시도해주세요')
}
}
49 changes: 49 additions & 0 deletions src/apis/member/fetchProfile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { AxiosError } from 'axios'
import { APIResponse, CommonAPIRequest, CommonAPIResponse, ErrorResponse } from '~types/api'
import { axiosInstance } from '~apis/axiosInstance'

export type FetchProfileRequest = Pick<CommonAPIRequest, 'memberId'>

export type FetchProfileResponse = Pick<
CommonAPIResponse,
| 'memberId'
| 'name'
| 'address'
| 'gender'
| 'familyRole'
| 'profileImg'
| 'totalDistance'
| 'walkCount'
| 'countWalksWithMember'
| 'dog'
>

export const fetchProfile = async ({ memberId }: FetchProfileRequest): Promise<APIResponse<FetchProfileResponse>> => {
try {
const { data } = await axiosInstance.get<APIResponse<FetchProfileResponse>>(`/member/${memberId}`)
return data
} catch (error) {
if (error instanceof AxiosError) {
const { response } = error as AxiosError<ErrorResponse>

if (response) {
const { code, message } = response.data
switch (code) {
case 400:
throw new Error(message || '잘못된 요청입니다.')
case 401:
throw new Error(message || '인증에 실패했습니다.')
case 500:
throw new Error(message || '서버 오류가 발생했습니다.')
default:
throw new Error(message || '알 수 없는 오류가 발생했습니다.')
}
}
// 요청 자체가 실패한 경우
throw new Error('네트워크 연결을 확인해주세요')
}

console.error('예상치 못한 에러:', error)
throw new Error('다시 시도해주세요')
}
}
11 changes: 11 additions & 0 deletions src/apis/member/useFetchProfile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { useSuspenseQuery, UseSuspenseQueryResult } from '@tanstack/react-query'
import { fetchProfile, FetchProfileResponse } from '~apis/member/fetchProfile'
import { queryKey } from '~constants/queryKey'

export const useFetchProfile = (memberId: number): UseSuspenseQueryResult<FetchProfileResponse, Error> => {
return useSuspenseQuery<FetchProfileResponse, Error>({
queryKey: queryKey.profile(memberId),
queryFn: () => fetchProfile({ memberId }).then(data => data.data),
staleTime: 1000 * 60 * 5, // 5분
})
}
32 changes: 12 additions & 20 deletions src/components/DogProfile/index.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
import * as S from './styles'
import { Typo13, Typo15, Typo20 } from '~components/Typo'
import { Separator } from '~components/Separator'

import Profile from '~components/Profile'
export default function DogProfile() {
import { Dog } from '~types/api'
import { calculateAge } from '~utils/calculateAge'

type DogProfileProps = Pick<Dog, 'birthDate' | 'breed' | 'comment' | 'gender' | 'name' | 'profileImg'>

export default function DogProfile({ birthDate, breed, comment, gender, name, profileImg }: DogProfileProps) {
return (
<S.DogInfoArea>
<S.DogInfoWrapper>
<Profile $size={80} $src={dogInfo.profileImg} />
<Profile $size={80} $src={profileImg} />
<S.DogDetailWrapper>
<S.TypoWrapper>
<Typo20 $weight='700'>{dogInfo.name}</Typo20>
<Typo15 $weight='400'>{dogInfo.breed}</Typo15>
<Typo20 $weight='700'>{name}</Typo20>
<Typo15 $weight='400'>{breed}</Typo15>
<Separator $height={8} />
<Typo15 $weight='400'>{dogInfo.age}</Typo15>
<Typo15 $weight='400'>{calculateAge(birthDate)}</Typo15>
<Separator $height={8} />
<Typo15 $weight='400'>{dogInfo.gender === 'male' ? '남' : '여'}</Typo15>
<Typo15 $weight='400'>{gender === 'MALE' ? '남' : '여'}</Typo15>
</S.TypoWrapper>
<S.TypoWrapper>
<Typo13>중성화 X</Typo13>
Expand All @@ -30,21 +34,9 @@ export default function DogProfile() {
우리 댕댕이를 소개해요!
</Typo15>
<Typo13 $weight='500' style={{ lineHeight: 1.2 }}>
{dogInfo.intro}
{comment}
</Typo13>
</S.OneLineIntro>
</S.DogInfoArea>
)
}

const dogInfo = {
name: '밤톨이',
breed: '포메라니안',
age: 4,
gender: 'male',
neutered: false, // 중성화 여부
weight: 3.4,
profileImg: '',
intro: `우리아이 안 물어요 착해요.
강아지껌을 너무 좋아해요 같이 놀아요. `,
}
Loading

0 comments on commit 7390324

Please sign in to comment.