Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
c5fe867
[FE] feat: 친구 관련 타입 및 api 함수 정의 (#89)
chysis Feb 27, 2025
e39761f
[FE] fix: 토큰을 포함한 요청에서 로그인 시 받은 토큰을 사용하도록 수정 (#89)
chysis Mar 1, 2025
e5bcd9b
[FE] feat: 사용자 정보 요청 api 함수 및 관련 타입 정의 (#89)
chysis Mar 1, 2025
1064ac1
[FE] refactor: 사용자 정보 요청 api 연동 및 userId를 전역 상태로 관리해서 사용 (#89)
chysis Mar 1, 2025
935350d
[FE] fix: 친구 관련 요청 api 함수 및 타입 수정 (#89)
chysis Mar 1, 2025
313fb90
[FE] feat: 친구 목록 api 연동 (#89)
chysis Mar 1, 2025
c260d1c
[FE] design: 친구 목록 프로필이 찌그러지는 문제 수정 (#89)
chysis Mar 1, 2025
80f7c0e
[FE] fix: api 함수명 camelCase로 수정 (#89)
chysis Mar 1, 2025
a175e86
[FE] feat: 대기 중인 친구 목록 api 연동 (#89)
chysis Mar 1, 2025
7e9075d
[FE] refactor: 받은 요청, 보낸 요청, 받은 요청에 수락/거절 로직 훅으로 분리 (#89)
chysis Mar 2, 2025
9bcf46f
[FE] feat: 요청 수락/거절 실패 시 에러 메시지 추가 및 요청 중일 때 버튼 클릭할 수 없게 수정 (#89)
chysis Mar 2, 2025
443e849
[FE] fix: api 응답 데이터 수정 (#89)
chysis Mar 2, 2025
d3a4153
[FE] refactor: 훅 이름 변경 및 적용 (#89)
chysis Mar 2, 2025
1a33dc3
[FE] refactor: 친구 목록 요청을 훅으로 분리 (#89)
chysis Mar 2, 2025
1046cc5
[FE] feat: 친구 추가 api 연동 및 유효성 검사 로직 구현 (#89)
chysis Mar 2, 2025
be2094a
[FE] refactor: 친구 요청 폼이 뜰 때마다 요청 보내지 않도록 수정 (#89)
chysis Mar 2, 2025
8305a5b
[FE] feat: DM 카테고리 영역에 친구 탭 추가 (#89)
chysis Mar 2, 2025
dc4a63b
[FE] design: 카테고리 텍스트 폰트 크기 조절 (#89)
chysis Mar 2, 2025
278affd
[FE] feat: DM 관련 api 함수 및 타입 추가 (#89)
chysis Mar 2, 2025
e25d1b4
[FE] feat: DM 목록 api 연동 (#89)
chysis Mar 2, 2025
7eea5dd
[FE] refactor: 구조 분해를 통해 접근하는 데이터의 depth 감소 (#89)
chysis Mar 2, 2025
078bcbb
[FE] design: 공통 모달 컴포넌트 footerWrapper margin 제거, padding 수정 (#89)
chysis Mar 2, 2025
bdeb27c
[FE] refactor: AuthCheckbox의 id를 optional로 수정 (#89)
chysis Mar 2, 2025
da876dd
[FE] fix: text ellipsis 가능하도록 css 수정 (#89)
chysis Mar 2, 2025
d9df4c9
[FE] fix: name이 아닌 nickname이 노출되도록 수정 (#89)
chysis Mar 2, 2025
db3f4be
[FE] fix: closeModal key를 camelCase로 수정 (#89)
chysis Mar 2, 2025
ccd684e
[FE] feat: DM 채팅방 생성 모달 구현 및 api 연동 (#89)
chysis Mar 2, 2025
248ff9b
[FE] feat: 받은 친구 요청 수락 시 DM이 자동으로 생성되도록 구현 (#89)
chysis Mar 2, 2025
07d7ed6
[FE] feat: DM 생성 모달에서 친구의 name을 함께 보여주도록 설정 (#89)
chysis Mar 2, 2025
c400e3a
[FE] design: online 색상 코드 추가 및 적용, nickname 자리에 id를 출력하는 버그 수정 (#89)
chysis Mar 2, 2025
a5be360
[FE] refactor: 회원 정보 검색 api 수정 반영 (#89)
chysis Mar 3, 2025
d5670c8
[FE] design: 친구 목록에 padding 추가 (#89)
chysis Mar 3, 2025
ecd35cc
[FE] feat: 친구 목록에서 삭제 기능 구현 (#89)
chysis Mar 3, 2025
34e5075
[FE] feat: 계정 탈퇴 기능 구현 (#89)
chysis Mar 3, 2025
41ab164
[FE] fix: 로그아웃 후 다른 계정으로 로그인 했을 때 이전 계정의 사용자 정보와 친구 목록이 뜨는 오류 수정 (#89)
chysis Mar 3, 2025
59c5d6c
[FE] fix: 본인을 친구 추가하지 못하도록 수정 (#89)
chysis Mar 3, 2025
f7ec3ef
[FE] refactor: usePostDirect 및 관련 모달 리팩토링 (#89)
chysis Mar 6, 2025
95ed854
[FE] refactor: 친구 삭제 요청을 useMutation을 사용하여 훅으로 분리 (#89)
chysis Mar 6, 2025
a24e54d
[FE] refactor: DM 리스트에서 나를 제외한 다른 멤버 리스트를 변수로 분리 (#89)
chysis Mar 6, 2025
d532252
[FE] refactor: userInfo 전역 상태로 set 할 때 기존 데이터와 달라졌는지 확인하는 로직 추가 (#89)
chysis Mar 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/frontend/src/api/directMessages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { endPoint } from '@/constants/endPoint';
import { GetDirectMessagesResponse, PostDirectMessageRequest } from '@/types/directMessages';
import { tokenAxios } from '@/utils/axios';

export const getDirects = async () => {
const { data } = await tokenAxios.get<GetDirectMessagesResponse>(endPoint.directMessages.GET_DIRECTS);
return data.result.directResponses;
};

interface PostDirectParams {
bodyRequest: PostDirectMessageRequest;
}

export const postDirect = async ({ bodyRequest }: PostDirectParams) => {
const { data } = await tokenAxios.post(endPoint.directMessages.POST_DIRECT, bodyRequest);
return data.result;
};
72 changes: 72 additions & 0 deletions src/frontend/src/api/friends.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { endPoint } from '@/constants/endPoint';
import {
DeleteFriendResponse,
GetFriendsListResponse,
GetFriendInfoResponse,
GetReceivedRequestsResponse,
GetSentRequestsResponse,
PostAcceptResponse,
PostRejectResponse,
PostRequestResponse,
} from '@/types/friends';
import { tokenAxios } from '@/utils/axios';

interface DeleteFriendParams {
friendId: string;
}

export const deleteFriend = async ({ friendId }: DeleteFriendParams) => {
const { data } = await tokenAxios.delete<DeleteFriendResponse>(endPoint.friends.DELETE_FRIEND(friendId));
return data;
};

interface GetFriendInfoParams {
email: string;
}

export const getFriendInfo = async ({ email }: GetFriendInfoParams) => {
const { data } = await tokenAxios.get<GetFriendInfoResponse>(endPoint.friends.GET_FRIENDS, { params: { email } });
return data.result;
};

export const getSentRequest = async () => {
const { data } = await tokenAxios.get<GetSentRequestsResponse>(endPoint.friends.GET_SENT_REQUESTS);
return data.result.friends;
};

export const getReceivedRequest = async () => {
const { data } = await tokenAxios.get<GetReceivedRequestsResponse>(endPoint.friends.GET_RECEIVED_REQUESTS);
return data.result.friends;
};

export const getFriendsList = async () => {
const { data } = await tokenAxios.get<GetFriendsListResponse>(endPoint.friends.GET_FRIENDS_LIST);
return data.result.friends;
};

interface PostFriendRequestParams {
toUserId: string;
}

export const postFriendRequest = async ({ toUserId }: PostFriendRequestParams) => {
const { data } = await tokenAxios.post<PostRequestResponse>(endPoint.friends.POST_REQUEST(toUserId));
return data.result;
};

interface PostRejectRequestParams {
friendId: string;
}

export const postRejectRequest = async ({ friendId }: PostRejectRequestParams) => {
const { data } = await tokenAxios.post<PostRejectResponse>(endPoint.friends.POST_REJECT_REQUEST(friendId));
return data.result;
};

interface PostAcceptRequestParams {
friendId: string;
}

export const postAcceptRequest = async ({ friendId }: PostAcceptRequestParams) => {
const { data } = await tokenAxios.post<PostAcceptResponse>(endPoint.friends.POST_ACCEPT_REQUEST(friendId));
return data.result;
};
17 changes: 16 additions & 1 deletion src/frontend/src/api/users.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { endPoint } from '@/constants/endPoint';
import {
GetUserInfoResponse,
PatchUserInfoRequest,
PatchUserInfoResponse,
PostAuthCodeRequest,
Expand All @@ -12,6 +13,15 @@ import {
} from '@/types/users';
import { publicAxios, tokenAxios } from '@/utils/axios';

interface DeleteAccountParams {
userId: string;
}

export const deleteAccount = async ({ userId }: DeleteAccountParams) => {
const { data } = await tokenAxios.delete(endPoint.users.DELETE_USER, { params: userId });
return data;
};

export const postLogin = async (requestBody: PostLoginRequest) => {
const { data } = await publicAxios.post<PostLoginResponse>(endPoint.users.POST_SIGN_IN, requestBody);
return data;
Expand All @@ -38,12 +48,17 @@ export const postEmailDuplicate = async ({ email }: PostEmailDuplicateParams) =>
return data;
};

export const getUserInfo = async () => {
const { data } = await tokenAxios.get<GetUserInfoResponse>(endPoint.users.GET_USER_INFO);
return data;
};

interface PatchUserInfoParams {
userId: string;
bodyRequest: PatchUserInfoRequest;
}

export const PatchUserInfo = async ({ userId, bodyRequest }: PatchUserInfoParams) => {
export const patchUserInfo = async ({ userId, bodyRequest }: PatchUserInfoParams) => {
const { data } = await publicAxios.patch<PatchUserInfoResponse>(endPoint.users.PATCH_USER_INFO, bodyRequest, {
params: userId,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import CheckedIcon from '@/assets/checked.svg';
import * as S from './styles';

interface AuthCheckboxProps {
id: string;
id?: string;
isChecked: boolean;
handleChange?: () => void;
label?: string;
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/src/components/common/Modal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const Modal = ({ children, name }: ModalProps) => {

return (
<>
<S.Overlay onClick={() => closeModal(name, 'close-modal')} />
<S.Overlay onClick={() => closeModal(name, 'closeModal')} />
<S.ModalContainer>
<S.CloseButton>
<S.CloseIcon size={28} onClick={() => closeAllModal()} />
Expand Down
3 changes: 1 addition & 2 deletions src/frontend/src/components/common/Modal/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ export const FooterWrapper = styled.div`
align-items: center;
justify-content: center;

margin-top: 1.6rem;
padding: 2.4rem 2.4rem 0;
padding: 0 2.4rem 2.4rem;
`;

export const CloseButton = styled.div`
Expand Down
62 changes: 58 additions & 4 deletions src/frontend/src/components/friend/AddFriendForm/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { useState } from 'react';

import { getFriendInfo, getFriendsList, getReceivedRequest, getSentRequest, postFriendRequest } from '@/api/friends';
import useGetUserInfo from '@/pages/FriendsPage/components/UserProfile/hooks/useGetUserInfo';

import * as S from './styles';

export type ResultMessageType = 'success' | 'fail';
Expand All @@ -10,17 +13,68 @@ interface ResultMessage {
}

const AddFriendForm = () => {
// TODO: 친구 검색 및 요청 보내기 로직 연동
// TODO: 요청 보내기 결과에 따라 resultMessage를 설정하고 표시
const { userInfo } = useGetUserInfo();
const [inputData, setInputData] = useState('');
const [resultMessage, setResultMessage] = useState<ResultMessage | null>(null);

const handleInputChange = (value: string) => {
setInputData(value);
};

const handleSendRequestButtonClick = () => {
console.log('친구 요청');
const getFriendInfoByEmail = async () => {
try {
// 버튼을 눌렀을 때 요청
const [friends, sentRequests, receivedRequests] = await Promise.all([
getFriendsList(),
getSentRequest(),
getReceivedRequest(),
]);
Comment on lines +27 to +31
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

친구 검색 버튼을 누르기 전에 불필요한 요청이 가지 않도록 Promise.all을 사용했어요.


// 1. 자신을 친구로 추가하려 하는지 확인
if (userInfo?.email === inputData) {
setResultMessage({ type: 'fail', content: '본인은 추가할 수 없어요.' });
return null;
}

// 2. 이미 친구로 등록된 사용자인지 확인
if (friends && friends.some((friend) => friend.email === inputData)) {
setResultMessage({ type: 'fail', content: '이미 친구로 등록된 사용자예요.' });
return null;
}

// 3. 이미 친구 요청을 보낸 사용자인지 확인
if (sentRequests && sentRequests.some((request) => request.email === inputData)) {
setResultMessage({ type: 'fail', content: '이미 친구 요청을 보냈어요. 상대방의 응답을 기다려주세요.' });
return null;
}

// 4. 상대가 이미 친구 요청을 보냈는지 확인
if (receivedRequests && receivedRequests.some((request) => request.email === inputData)) {
setResultMessage({ type: 'fail', content: '상대방이 이미 친구 요청을 보냈어요. 친구 요청을 확인해주세요.' });
return null;
}

const response = await getFriendInfo({ email: inputData });
return response;
} catch (error) {
console.log('회원 검색 실패', error);
setResultMessage({ type: 'fail', content: '회원 검색에 실패했어요. 다시 시도해주세요.' });
return null;
}
};

const handleSendRequestButtonClick = async () => {
try {
const friendInfo = await getFriendInfoByEmail();
if (!friendInfo) return;

await postFriendRequest({ toUserId: friendInfo.userId });

setResultMessage({ type: 'success', content: '친구 요청을 보냈어요!' });
} catch (error) {
console.log('친구 요청 실패', error);
setResultMessage({ type: 'fail', content: '친구 요청을 보내는 데 실패했어요. 다시 시도해주세요.' });
}
};

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import styled from 'styled-components';

import { BodyRegularText, ChipText, TitleText2 } from '@/styles/Typography';
import { BodyRegularText, TitleText2 } from '@/styles/Typography';

import { ResultMessageType } from '.';

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';

import { postDirect } from '@/api/directMessages';
import useModalStore from '@/stores/modalStore';

const usePostDirect = () => {
const queryClient = useQueryClient();
const { closeAllModal } = useModalStore();

const createDirectMessageMutation = useMutation({
mutationFn: postDirect,
mutationKey: ['createDirectMessage'],
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['directMessagesList'] });
closeAllModal();
},
onError: (error) => {
console.error('DM 생성 실패', error);
},
});

const createDirectMessage = (memberIds: string[]) => {
createDirectMessageMutation.mutate({ bodyRequest: { memberIds } });
};

return { createDirectMessage, isPending: createDirectMessageMutation.isPending };
};

export default usePostDirect;
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { useState } from 'react';

import AuthCheckbox from '@/components/common/AuthCheckbox';
import Modal from '@/components/common/Modal';

import useGetFriendsList from '../FriendsList/hooks/useGetFriendsList';

import usePostDirect from './hooks/usePostDirect';
import * as S from './styles';

const MAX_DIRECT_MESSAGE_FRIENDS = 9;

const CreateDirectMessageModal = () => {
const [selectedFriends, setSelectedFriends] = useState<string[]>([]);
const { createDirectMessage, isPending } = usePostDirect();
const { friends } = useGetFriendsList();
if (!friends) return null;

const handleCheckboxClick = (userId: string) => {
// 이미 선택된 친구를 누르면 선택 해제 가능
if (selectedFriends.length === MAX_DIRECT_MESSAGE_FRIENDS) {
if (!selectedFriends.includes(userId)) return;
}

setSelectedFriends((prev) => (prev.includes(userId) ? prev.filter((id) => id !== userId) : [...prev, userId]));
};

const handleCreateDirectMessage = async () => {
createDirectMessage(selectedFriends);
};

const isButtonDisabled = !selectedFriends.length || isPending;

return (
<Modal name="withFooter">
<Modal.Header>
<S.ModalTitle>친구 선택하기</S.ModalTitle>
<S.SelectedFriendsCount>
친구를 {MAX_DIRECT_MESSAGE_FRIENDS - selectedFriends.length}명 더 선택할 수 있어요.
</S.SelectedFriendsCount>
</Modal.Header>
<Modal.Content>
<S.FriendList>
{friends.map((friend) => (
<S.FriendItem key={friend.userId} onClick={() => handleCheckboxClick(friend.userId)}>
<S.FriendProfileImage $imageUrl={friend.profileImageUrl}>
<S.FriendStatusMark $isOnline={true} />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$isOnline은 테스트를 위해 임시로 true로 고정해두신거죵??

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네 맞아요! 상태 관리가 도입되면 수정할 예정이에요

</S.FriendProfileImage>
<S.FriendInfo>
<S.FriendNickname>{friend.nickname}</S.FriendNickname>
<S.FriendName>{friend.name}</S.FriendName>
</S.FriendInfo>
<AuthCheckbox isChecked={selectedFriends.includes(friend.userId)} />
</S.FriendItem>
))}
</S.FriendList>
</Modal.Content>
<Modal.Footer>
<S.CreateButton disabled={isButtonDisabled} $isDisabled={isButtonDisabled} onClick={handleCreateDirectMessage}>
{isPending ? '요청 중...' : 'DM 생성'}
</S.CreateButton>
</Modal.Footer>
</Modal>
);
};

export default CreateDirectMessageModal;
Loading