diff --git a/src/App.tsx b/src/App.tsx
index a1ad81f..abf4aac 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,9 +1,6 @@
-import styled from '@emotion/native';
import { DefaultTheme, NavigationContainer } from '@react-navigation/native';
-import { useState } from 'react';
import { AppProviders } from '~providers/AppProviders';
import { lightTheme } from '~styles/theme';
-import StoryBookUI from '../.storybook';
import { RootNavigator } from '~navigation/RootNavigator';
import { useWebSocket } from '~hooks/useWebSocket';
import { WebSocketProvider } from '~providers/WebSocketProvider';
@@ -35,40 +32,41 @@ const MainApp = () => {
export const App = () => {
// const { isMswEnabled } = useInitializeMsw();
- const [storybookEnabled, setStorybookEnabled] = useState(false);
+ // const [storybookEnabled, setStorybookEnabled] = useState(false);
// if (__DEV__ && !isMswEnabled) {
// return Loading MSW...;
// }
- const toggleStorybook = () => setStorybookEnabled(prev => !prev);
+ // const toggleStorybook = () => setStorybookEnabled(prev => !prev);
return (
<>
- {__DEV__ && (
+ {/* {__DEV__ && (
S
)}
- {__DEV__ && storybookEnabled ? : }
+ {__DEV__ && storybookEnabled ? : } */}
+
>
);
};
-const StoryBookFloatingButton = styled.TouchableOpacity`
- position: absolute;
- right: 30px;
- bottom: 30px;
- z-index: 1000;
- width: 60px;
- height: 60px;
- border-radius: 30px;
- background-color: purple;
- align-items: center;
- justify-content: center;
-`;
+// const StoryBookFloatingButton = styled.TouchableOpacity`
+// position: absolute;
+// right: 30px;
+// bottom: 30px;
+// z-index: 1000;
+// width: 60px;
+// height: 60px;
+// border-radius: 30px;
+// background-color: purple;
+// align-items: center;
+// justify-content: center;
+// `;
-const StoryBookButtonText = styled.Text`
- color: white;
- font-size: 24px;
-`;
+// const StoryBookButtonText = styled.Text`
+// color: white;
+// font-size: 24px;
+// `;
diff --git a/src/apis/api.ts b/src/apis/api.ts
index e66475a..1efa4bf 100644
--- a/src/apis/api.ts
+++ b/src/apis/api.ts
@@ -12,15 +12,7 @@ export const api = ky.create({
async request => {
const accessToken = await getAccessToken();
if (accessToken) {
- request.headers.set(
- 'Authorization',
- // accessToken,
- // `Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJBY2Nlc3NUb2tlbiIsInByb3ZpZGVyIjoiS0FLQU8iLCJleHAiOjE3Mzk3NjQwMDIsImVtYWlsIjoibWtoNjc5M0BuYXZlci5jb20ifQ.EF03NpevMSZ2DcM5Q-trEEmRa0KEb5HpJ1HlD-Vj8xy3N2JoFvdQFoWDJRM3IGVwx58L9T2oV7GBTr6wJOevnA`,
- // 패밀리장, 가족많음
- // `Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJBY2Nlc3NUb2tlbiIsInByb3ZpZGVyIjoiR09PR0xFIiwiZXhwIjoxNzQwMjQ4MjA3LCJlbWFpbCI6ImJibGJibGFuNjlAZ21haWwuY29tIn0.CpauBw9_yXlYQjr-BZP7xqm1u63pj1g1aM3kX9HwCm37BMhpOQGz1Mq8R42CihtC8henTRy0OHaxa7q9-1Svzw`,
- //나 혼자 패밀리장
- `Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJBY2Nlc3NUb2tlbiIsInByb3ZpZGVyIjoiS0FLQU8iLCJleHAiOjE3NDEyNzUwNjcsImVtYWlsIjoibndpNjk1OUBnbWFpbC5jb20ifQ.pOk3HSBSFGPKGIL4KS7tbwCrzPfCloQZrA4xWzZVpngYTnodSZuenuoUYRC1DkWY-EdmvK-cv_am2EnPryhsxg`,
- );
+ request.headers.set('Authorization', `Bearer ${accessToken}`);
}
},
],
diff --git a/src/apis/block/blockUser.ts b/src/apis/block/blockUser.ts
new file mode 100644
index 0000000..7a046b7
--- /dev/null
+++ b/src/apis/block/blockUser.ts
@@ -0,0 +1,23 @@
+import { HTTPError } from 'ky';
+import { api } from '~apis/api';
+import { APIResponse } from '~types/api';
+
+export interface ResponseBlockUser {
+ blockId: number;
+ blockerMemberId: number;
+ blockedMemberId: number;
+ blockedMemberName: string;
+}
+
+export const blockUser = async (blockedId: number): Promise> => {
+ try {
+ const response = await api.post(`block/${blockedId}`).json>();
+ return response;
+ } catch (error) {
+ if (error instanceof HTTPError) {
+ const errorData = await error.response.json();
+ console.error(errorData);
+ }
+ throw error;
+ }
+};
diff --git a/src/apis/block/fetchBlockedUsers.ts b/src/apis/block/fetchBlockedUsers.ts
new file mode 100644
index 0000000..c8ca93c
--- /dev/null
+++ b/src/apis/block/fetchBlockedUsers.ts
@@ -0,0 +1,52 @@
+import { HTTPError } from 'ky';
+import { api } from '~apis/api';
+import { APIResponse } from '~types/api';
+import { FamilyRole } from '~types/family-role';
+import { Gender } from '~types/gender';
+
+export interface BlockedUser {
+ blockId: number;
+ blockedMemberName: string;
+ memberGender: Gender;
+ familyRole: FamilyRole;
+}
+
+interface Sort {
+ unsorted: boolean;
+ sorted: boolean;
+ empty: boolean;
+}
+
+interface Pageable {
+ pageNumber: number;
+ pageSize: number;
+ paged: boolean;
+ unpaged: boolean;
+ offset: number;
+ sort: Sort;
+}
+
+export interface ResponseBlockList {
+ pageable: Pageable;
+ numberOfElements: number;
+ size: number;
+ content: BlockedUser[];
+ number: number;
+ sort: Sort;
+ first: boolean;
+ last: boolean;
+ empty: boolean;
+}
+
+export const fetchBlockedUsers = async (page = 0): Promise> => {
+ try {
+ const response = await api.get(`block/list?page=${page}`).json>();
+ return response;
+ } catch (error) {
+ if (error instanceof HTTPError) {
+ const errorData = error.response.json();
+ console.error(errorData);
+ }
+ throw error;
+ }
+};
diff --git a/src/apis/block/unblockUser.ts b/src/apis/block/unblockUser.ts
new file mode 100644
index 0000000..6f0424a
--- /dev/null
+++ b/src/apis/block/unblockUser.ts
@@ -0,0 +1,16 @@
+import { HTTPError } from 'ky';
+import { api } from '~apis/api';
+import { APIResponse } from '~types/api';
+
+export const unblockUser = async (blockId: number): Promise> => {
+ try {
+ const response = await api.delete(`block/${blockId}`).json>();
+ return response;
+ } catch (error) {
+ if (error instanceof HTTPError) {
+ const errorData = await error.response.json();
+ console.error(errorData);
+ }
+ throw error;
+ }
+};
diff --git a/src/apis/block/useBlock.ts b/src/apis/block/useBlock.ts
new file mode 100644
index 0000000..56516d3
--- /dev/null
+++ b/src/apis/block/useBlock.ts
@@ -0,0 +1,47 @@
+import { useMutation, useSuspenseQuery } from '@tanstack/react-query';
+import { blockUser } from '~apis/block/blockUser';
+import { UseMutationCustomOptions } from '~types/api';
+import { unblockUser } from './unblockUser';
+import { BlockedUser, fetchBlockedUsers } from '~apis/block/fetchBlockedUsers';
+
+const useBlockedUserList = () => {
+ return useSuspenseQuery({
+ queryKey: ['allBlockedUsers'],
+ queryFn: async () => {
+ let allData: BlockedUser[] = [];
+ let currentPage = 0;
+ let isLastPage = false;
+
+ while (!isLastPage) {
+ const response = await fetchBlockedUsers(currentPage);
+ allData = [...allData, ...response.data.content];
+ isLastPage = response.data.last;
+ currentPage += 1;
+ }
+
+ return allData;
+ },
+ });
+};
+
+const useBlockUser = (mutationOptions?: UseMutationCustomOptions) => {
+ return useMutation({
+ mutationFn: (blockedId: number) => blockUser(blockedId),
+ ...mutationOptions,
+ });
+};
+
+const useUnblockUser = (mutationOptions?: UseMutationCustomOptions) => {
+ return useMutation({
+ mutationFn: (blockId: number) => unblockUser(blockId),
+ ...mutationOptions,
+ });
+};
+
+export const useBlock = () => {
+ const blockedUsers = useBlockedUserList().data;
+ const blockUserMutation = useBlockUser();
+ const unblockUserMutation = useUnblockUser();
+
+ return { blockedUsers, blockUserMutation, unblockUserMutation };
+};
diff --git a/src/apis/chat/fetchChatMessages.ts b/src/apis/chat/fetchChatMessages.ts
new file mode 100644
index 0000000..8794236
--- /dev/null
+++ b/src/apis/chat/fetchChatMessages.ts
@@ -0,0 +1,73 @@
+import { HTTPError } from 'ky';
+import { api } from '~apis/api';
+import { APIResponse } from '~types/api';
+import { BooleanString } from '~types/boolean-string';
+import { FamilyRole } from '~types/family-role';
+import { Gender } from '~types/gender';
+
+interface Sort {
+ unsorted: boolean;
+ sorted: boolean;
+ empty: boolean;
+}
+
+interface Pageble {
+ pageNumber: number;
+ pageSize: number;
+ paged: boolean;
+ unpaged: boolean;
+ offset: number;
+ sort: Sort;
+}
+
+interface MemberInfo {
+ memberId: number;
+ memberName: string;
+ email: string;
+ memberGender: Gender;
+ familyRole: FamilyRole;
+ memberProfileImg: number;
+}
+
+interface ChatContent {
+ chatId: number;
+ createdAt: string;
+ updatedAt: string;
+ chatRoomId: number;
+ memberInfo: MemberInfo;
+ chatType: string;
+ isRead: BooleanString;
+ text: string;
+}
+
+export interface ResponseFetchChatMessages {
+ pageable: Pageble;
+ numberOfElements: number;
+ size: number;
+ content: ChatContent[];
+ number: number;
+ sort: Sort;
+ first: boolean;
+ last: boolean;
+ empty: boolean;
+}
+
+export const fetchChatMessages = async (
+ chatRoomId: number,
+ lastMessageCreatedAt: string,
+): Promise> => {
+ try {
+ const response = await api
+ .get(`chat/message/${chatRoomId}`, {
+ json: { chatRoomId, lastMessageCreatedAt },
+ })
+ .json>();
+ return response;
+ } catch (error) {
+ if (error instanceof HTTPError) {
+ const errorData = await error.response.json();
+ console.error(errorData);
+ }
+ throw error;
+ }
+};
diff --git a/src/apis/chat/fetchChatRooms.ts b/src/apis/chat/fetchChatRooms.ts
index 1df9523..0d01cb6 100644
--- a/src/apis/chat/fetchChatRooms.ts
+++ b/src/apis/chat/fetchChatRooms.ts
@@ -4,25 +4,25 @@ import { AvatarNumber } from '~types/avatar-number';
import { FamilyRole } from '~types/family-role';
import { Gender } from '~types/gender';
-export type FetchChatRoomsResponseType = {
+interface Member {
+ memberId: number;
+ memberName: string;
+ email: string;
+ memberGender: Gender;
+ familyRole: FamilyRole;
+ memberProfileImg: AvatarNumber;
+}
+export interface ResponseFetchChatRoom {
chatRoomId: number;
name: string;
lastMessage: string;
unreadMessageCount: number;
- members: [
- {
- memberId: number;
- memberName: string;
- email: string;
- memberGender: Gender;
- familyRole: FamilyRole;
- memberProfileImg: AvatarNumber;
- },
- ];
-}[];
-export const fetchChatRooms = async (): Promise> => {
+ members: Member[];
+}
+
+export const fetchChatRooms = async (): Promise> => {
try {
- const response = await api.get('chat/rooms').json>();
+ const response = await api.get('chat/rooms').json>();
return response;
} catch (error) {
console.error('Error:', error);
diff --git a/src/apis/chat/useChatMessages.ts b/src/apis/chat/useChatMessages.ts
new file mode 100644
index 0000000..85109b9
--- /dev/null
+++ b/src/apis/chat/useChatMessages.ts
@@ -0,0 +1,18 @@
+import { useQuery } from '@tanstack/react-query';
+import { fetchChatMessages } from '~apis/chat/fetchChatMessages';
+import { UseQueryCustomOptions } from '~types/api';
+
+export const useChatMessages = (
+ chatRoomId: number,
+ lastMessageCreatedAt: string,
+ queryOptions?: UseQueryCustomOptions,
+) => {
+ return useQuery({
+ queryKey: ['chatMessages', chatRoomId, lastMessageCreatedAt],
+ queryFn: ({ queryKey }) => {
+ const [, chatRoomId, lastMessageCreatedAt] = queryKey;
+ return fetchChatMessages(chatRoomId as number, lastMessageCreatedAt as string);
+ },
+ ...queryOptions,
+ });
+};
diff --git a/src/apis/dog/createDog.ts b/src/apis/dog/createDog.ts
index 8984a1f..76c8e77 100644
--- a/src/apis/dog/createDog.ts
+++ b/src/apis/dog/createDog.ts
@@ -21,7 +21,6 @@ export const createDog = async (dogProfile: DogProfileType): Promise> => {
try {
- const response = await api.get(`dogs/${dogId}/walks`).json>();
+ const response = await api
+ .get(`member/walk-info/${memberId}`)
+ .json>();
return response;
} catch (error) {
console.error('Error:', error);
diff --git a/src/apis/dog/useAccumulatedWalkInfo.ts b/src/apis/dog/useAccumulatedWalkInfo.ts
index 02fe972..657fc66 100644
--- a/src/apis/dog/useAccumulatedWalkInfo.ts
+++ b/src/apis/dog/useAccumulatedWalkInfo.ts
@@ -2,12 +2,12 @@ import { useSuspenseQuery } from '@tanstack/react-query';
import { fetchAccumulatedWalkInfo } from '~apis/dog/fetchAccumulatedWalkInfo';
interface useAccumulatedWalkInfoProps {
- dogId: number;
+ memberId: number;
}
-export const useAccumulatedWalkInfo = ({ dogId }: useAccumulatedWalkInfoProps) => {
+export const useAccumulatedWalkInfo = ({ memberId }: useAccumulatedWalkInfoProps) => {
const { data: accumulatedWalkInfo } = useSuspenseQuery({
- queryKey: ['accumulatedWalkInfo', dogId],
- queryFn: () => fetchAccumulatedWalkInfo({ dogId }),
+ queryKey: ['accumulatedWalkInfo', memberId],
+ queryFn: () => fetchAccumulatedWalkInfo({ memberId }),
select: ({ data }) => data,
});
return accumulatedWalkInfo;
diff --git a/src/apis/friend/deleteFriend.ts b/src/apis/friend/deleteFriend.ts
new file mode 100644
index 0000000..423f1c7
--- /dev/null
+++ b/src/apis/friend/deleteFriend.ts
@@ -0,0 +1,16 @@
+import { HTTPError } from 'ky';
+import { api } from '~apis/api';
+import { APIResponse } from '~types/api';
+
+export const deleteFriend = async (memberId: number): Promise> => {
+ try {
+ const response = await api.delete(`friend/${memberId}`).json>();
+ return response;
+ } catch (error) {
+ if (error instanceof HTTPError) {
+ const errorData = await error.response.json();
+ console.error(errorData);
+ }
+ throw error;
+ }
+};
diff --git a/src/apis/friend/respondToFriendRequest.ts b/src/apis/friend/respondToFriendRequest.ts
new file mode 100644
index 0000000..01d1b36
--- /dev/null
+++ b/src/apis/friend/respondToFriendRequest.ts
@@ -0,0 +1,37 @@
+import { HTTPError } from 'ky';
+import { api } from '~apis/api';
+import { APIResponse } from '~types/api';
+import { FamilyRole } from '~types/family-role';
+import { Gender } from '~types/gender';
+
+interface ResponseFreindRequestAction {
+ memberId: number;
+ memberName: string;
+ email: string;
+ provider: string;
+ memberGender: Gender;
+ memberBirthDate: string;
+ address: string;
+ familyRole: FamilyRole;
+ memberProfileImg: number;
+}
+
+export const respondToFriendRequest = async (
+ memberId: number,
+ decision: 'ACCEPT' | 'DENY',
+): Promise> => {
+ try {
+ const response = await api
+ .post('friend', {
+ json: { memberId, decision },
+ })
+ .json>();
+ return response;
+ } catch (error) {
+ if (error instanceof HTTPError) {
+ const errorData = await error.response.json();
+ console.error(errorData);
+ }
+ throw error;
+ }
+};
diff --git a/src/apis/friend/useDeleteFriend.ts b/src/apis/friend/useDeleteFriend.ts
new file mode 100644
index 0000000..d44a65f
--- /dev/null
+++ b/src/apis/friend/useDeleteFriend.ts
@@ -0,0 +1,17 @@
+import { useMutation } from '@tanstack/react-query';
+import { deleteFriend } from '~apis/friend/deleteFriend';
+import { useToast } from '~hooks/useToast';
+import { queryClient } from '~providers/QueryClientProvider';
+import { UseMutationCustomOptions } from '~types/api';
+
+export const useDeleteFriend = (mutationOptions?: UseMutationCustomOptions) => {
+ const { successToast } = useToast();
+ return useMutation({
+ mutationFn: (memberId: number) => deleteFriend(memberId),
+ onSuccess: () => {
+ successToast('친구목록에서 삭제되었습니다.');
+ queryClient.invalidateQueries({ queryKey: ['friends'] });
+ },
+ ...mutationOptions,
+ });
+};
diff --git a/src/apis/friend/useFriends.ts b/src/apis/friend/useFriends.ts
index 97a01a4..f3116e1 100644
--- a/src/apis/friend/useFriends.ts
+++ b/src/apis/friend/useFriends.ts
@@ -1,10 +1,11 @@
-import { useQuery } from '@tanstack/react-query';
+import { keepPreviousData, useQuery } from '@tanstack/react-query';
import { fetchFriends } from '~apis/friend/fetchFriends';
export const useFriends = () => {
const { data } = useQuery({
queryKey: ['friends'],
queryFn: fetchFriends,
+ placeholderData: keepPreviousData,
});
return data?.data;
};
diff --git a/src/apis/friend/useRespondToFriendRequest.ts b/src/apis/friend/useRespondToFriendRequest.ts
new file mode 100644
index 0000000..11b3f14
--- /dev/null
+++ b/src/apis/friend/useRespondToFriendRequest.ts
@@ -0,0 +1,11 @@
+import { useMutation } from '@tanstack/react-query';
+import { respondToFriendRequest } from '~apis/friend/respondToFriendRequest';
+import { UseMutationCustomOptions } from '~types/api';
+
+export const useRespondToFriendRequest = (mutationOptions?: UseMutationCustomOptions) => {
+ return useMutation({
+ mutationFn: ({ memberId, decision }: { memberId: number; decision: 'ACCEPT' | 'DENY' }) =>
+ respondToFriendRequest(memberId, decision),
+ ...mutationOptions,
+ });
+};
diff --git a/src/apis/member/fetchUserById.ts b/src/apis/member/fetchUserById.ts
index 3e131d6..f3cae76 100644
--- a/src/apis/member/fetchUserById.ts
+++ b/src/apis/member/fetchUserById.ts
@@ -14,17 +14,15 @@ export type FetchUserByIdResponseType = {
email: string;
address: string;
memberGender: Gender;
+ memberBirthDate: string;
familyRole: FamilyRole;
- memberProfileImg: string;
- avatarNumber: AvatarNumber;
+ memberProfileImg: AvatarNumber;
};
-export const fetchUserById = async ({
- memberId,
-}: FetchUserByIdRequestType): Promise> => {
+export const fetchUserById = async ({ memberId }: FetchUserByIdRequestType): Promise => {
try {
const response = await api.get(`member/${memberId}`).json>();
- return response;
+ return response.data;
} catch (error) {
console.error('Error:', error);
throw error;
diff --git a/src/apis/member/useUserById.ts b/src/apis/member/useUserById.ts
index 67f225d..6014145 100644
--- a/src/apis/member/useUserById.ts
+++ b/src/apis/member/useUserById.ts
@@ -1,12 +1,10 @@
-import { useSuspenseQuery } from '@tanstack/react-query';
-import { fetchUserById, FetchUserByIdRequestType } from '~apis/member/fetchUserById';
+import { keepPreviousData, useQuery } from '@tanstack/react-query';
+import { fetchUserById, FetchUserByIdRequestType, FetchUserByIdResponseType } from '~apis/member/fetchUserById';
export const useUserById = ({ memberId }: FetchUserByIdRequestType) => {
- const { data: userInfo } = useSuspenseQuery({
+ return useQuery({
queryKey: ['userInfoById', memberId],
queryFn: () => fetchUserById({ memberId }),
- select: ({ data }) => data,
+ placeholderData: keepPreviousData,
});
-
- return userInfo;
};
diff --git a/src/assets/icons/extra-option.svg b/src/assets/icons/extra-option.svg
new file mode 100644
index 0000000..aafd699
--- /dev/null
+++ b/src/assets/icons/extra-option.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/components/Common/CompoundOptions/index.tsx b/src/components/Common/CompoundOptions/index.tsx
index 4c0a0ec..e695b40 100644
--- a/src/components/Common/CompoundOptions/index.tsx
+++ b/src/components/Common/CompoundOptions/index.tsx
@@ -1,9 +1,11 @@
-import { PropsWithChildren, ReactNode, createContext, useContext, useEffect, useRef } from 'react';
+import { PropsWithChildren, ReactNode, createContext, useContext, useEffect, useRef, useState } from 'react';
import { Animated, GestureResponderEvent, Modal, ModalProps, PressableProps, StyleSheet } from 'react-native';
import * as S from './styles';
interface OptionContextValue {
onClickOutSide?: (event: GestureResponderEvent) => void;
+ isVisible: boolean;
+ setShowModal: React.Dispatch>;
}
const OptionContext = createContext(undefined);
@@ -15,15 +17,21 @@ interface OptionMainProps extends ModalProps {
}
const OptionMain = ({ children, isVisible, hideOption, ...props }: OptionMainProps) => {
+ const [showModal, setShowModal] = useState(isVisible);
const onClickOutSide = (event: GestureResponderEvent) => {
if (event.target === event.currentTarget) {
hideOption();
}
};
+ useEffect(() => {
+ if (isVisible) {
+ setShowModal(true);
+ }
+ }, [isVisible]);
return (
-
- {children}
+
+ {children}
);
};
@@ -37,13 +45,31 @@ const Background = ({ children }: PropsWithChildren) => {
const Container = ({ children }: PropsWithChildren) => {
const slideAnim = useRef(new Animated.Value(300)).current;
- useEffect(() => {
+ const optionContext = useContext(OptionContext);
+ if (!optionContext) {
+ throw new Error('Container must be used within an OptionMain');
+ }
+
+ const { isVisible, setShowModal } = optionContext;
+
+
+useEffect(() => {
+ if (isVisible) {
+ setShowModal(true);
Animated.timing(slideAnim, {
toValue: 0,
- duration: 300,
+ duration: 200,
useNativeDriver: true,
}).start();
- }, [slideAnim]);
+ } else {
+ Animated.timing(slideAnim, {
+ toValue: 600,
+ duration: 200,
+ useNativeDriver: true,
+ }).start(() => setShowModal(false));
+ }
+}, [isVisible, setShowModal, slideAnim]);
+
return (
`
+export const OptionText = styled(TextRegular)<{ isDanger: boolean }>`
font-size: 17px;
color: ${props => (props.isDanger ? 'red' : 'black')};
`;
@@ -25,56 +25,12 @@ export const TitleContainer = styled.View`
padding: 15px;
`;
-export const TitleText = styled(TextSemiBold)`
+export const TitleText = styled(TextRegular)`
font-size: 16px;
`;
export const Divider = styled.View`
width: 100%;
height: 1px;
- background-color: black;
+ background-color: ${props => props.theme.colors.gc_2};
`;
-
-// optionBackground: {
-// flex: 1,
-// justifyContent: 'flex-end',
-// backgroundColor: 'rgba(0,0,0/0.5)',
-// },
-// optionContainer: {
-// borderRadius: 15,
-// marginHorizontal: 10,
-// marginBottom: 10,
-// backgroundColor: 'white',
-// overflow: 'hidden',
-// },
-// optionButton: {
-// flexDirection: 'row',
-// alignItems: 'center',
-// justifyContent: 'center',
-// height: 50,
-// gap: 5,
-// },
-// optionButtonPressed: {
-// backgroundColor: '#F1F1F5',
-// },
-// optionText: {
-// fontSize: 17,
-// color: 'black',
-// fontWeight: '500',
-// },
-// dangerText: {
-// color: 'red',
-// },
-// titleContainer: {
-// alignItems: 'center',
-// padding: 15,
-// },
-// titleText: {
-// fontSize: 16,
-// fontWeight: '500',
-// color: 'black',
-// },
-// border: {
-// borderBottomColor: 'gray',
-// borderBottomWidth: 1,
-// },
diff --git a/src/components/Common/Icons/index.tsx b/src/components/Common/Icons/index.tsx
index 64d40f4..8838231 100644
--- a/src/components/Common/Icons/index.tsx
+++ b/src/components/Common/Icons/index.tsx
@@ -34,6 +34,7 @@ import FamilyJoinGuide from '~assets/family-join-guide.svg';
import FamilyJoinGuide2 from '~assets/family-join-guide2.svg';
import FamilyJoinGuide3 from '~assets/family-join-guide3.svg';
import DogTurnedBack from '~assets/dogs/dog-turned-back.svg';
+import FriendOption from '~assets/icons/extra-option.svg';
import Crown from '~assets/crown.svg';
export const Icon = {
@@ -72,5 +73,6 @@ export const Icon = {
FamilyJoinGuide2: (props: SvgProps) => ,
FamilyJoinGuide3: (props: SvgProps) => ,
DogTurnedBack: (props: SvgProps) => ,
+ FriendOption: (props: SvgProps) => ,
Crown: (props: SvgProps) => ,
};
diff --git a/src/components/Common/Profile/index.tsx b/src/components/Common/Profile/index.tsx
index b770fb1..6a4e03f 100644
--- a/src/components/Common/Profile/index.tsx
+++ b/src/components/Common/Profile/index.tsx
@@ -39,15 +39,18 @@ export const Profile = ({ size, src, userId, testID, avatarNumber, onPress }: Pr
);
}
- if (!src) {
+ if (!src && !avatarNumber) {
+ console.log(src, avatarNumber);
throw new Error('Profile 컴포넌트의 props가 적절하지 않습니다. src, avatarNumber 중 하나는 작성해 주세요.');
}
if (typeof src === 'string') {
return ;
}
- const SvgComponent = src;
- return ;
+ if (src) {
+ const SvgComponent = src;
+ return ;
+ }
};
return (
diff --git a/src/components/Common/UserInfo/index.tsx b/src/components/Common/UserInfo/index.tsx
index eb69dee..ddcc210 100644
--- a/src/components/Common/UserInfo/index.tsx
+++ b/src/components/Common/UserInfo/index.tsx
@@ -1,22 +1,24 @@
-import { PropsWithChildren } from 'react';
+import { PropsWithChildren, useContext } from 'react';
import { Profile } from '~components/Common/Profile';
import { Separator } from '~components/Common/Seperator';
import { FamilyRole } from '~types/family-role';
import { Gender } from '~types/gender';
-import { getKoreanRole } from '~utils/getKoreanRoleWithName';
import * as S from './styles';
import { AvatarNumber } from '~types/avatar-number';
+import { FAMILY_ROLE } from '~constants/family-role';
+import { Icon } from '~components/Common/Icons';
+import { FriendOptionContext } from '~components/Social/Friend';
export interface UserItemProps {
name: string;
gender: Gender;
- dogGender: Gender;
familyRole: FamilyRole;
buttonText: string;
isLast?: boolean;
avatarNumber: AvatarNumber;
onPressButton: () => void;
userId: number;
+ optionButton?: boolean;
}
export const UserInfo = ({ children }: PropsWithChildren) => {
@@ -30,11 +32,20 @@ const Item = ({
name,
isLast = false,
onPressButton,
- dogGender,
avatarNumber,
userId,
+ optionButton,
}: UserItemProps) => {
- //! 유저 강아지 성별
+ const friendOptionContext = useContext(FriendOptionContext);
+
+ const handlePressFriendOption = () => {
+ if (friendOptionContext) {
+ const { setIsFriendOptionsVisible, setFriendId } = friendOptionContext;
+ setIsFriendOptionsVisible(true);
+ setFriendId(userId);
+ }
+ };
+
return (
@@ -45,13 +56,20 @@ const Item = ({
{gender === 'MALE' ? '남자' : '여자'}
- {getKoreanRole({ dogGender, familyRole })}
+ {FAMILY_ROLE[familyRole]}
-
- {buttonText}
-
+
+
+ {buttonText}
+
+ {optionButton && (
+
+
+
+ )}
+
);
diff --git a/src/components/Common/UserInfo/styles.ts b/src/components/Common/UserInfo/styles.ts
index a384af2..dbd8692 100644
--- a/src/components/Common/UserInfo/styles.ts
+++ b/src/components/Common/UserInfo/styles.ts
@@ -41,3 +41,14 @@ export const Button = styled.Pressable`
export const ButtonText = styled(TextBold)`
text-align: center;
`;
+
+export const RightContainer = styled.View`
+ flex-direction: row;
+ gap: 12px;
+`;
+
+export const FriendOptionButton = styled.Pressable`
+ margin-right: 10px;
+ justify-content: center;
+ align-items: center;
+`;
diff --git a/src/components/Login/LoginButton/index.tsx b/src/components/Login/LoginButton/index.tsx
index 340d7a3..2b76c3e 100644
--- a/src/components/Login/LoginButton/index.tsx
+++ b/src/components/Login/LoginButton/index.tsx
@@ -1,4 +1,5 @@
import { Icon } from '~components/Common/Icons';
+import { AuthNavigations } from '~constants/navigations';
export const SOCIAL_LOGIN_BUTTONS = [
{
@@ -6,13 +7,13 @@ export const SOCIAL_LOGIN_BUTTONS = [
textColor: '#000000',
IconComponent: Icon.Kakao,
text: '카카오계정 로그인',
- onPress: (navigation: any) => navigation.navigate('KakaoLogin'),
+ onPress: (navigation: any) => navigation.navigate(AuthNavigations.KAKAO_LOGIN),
},
{
backgroundColor: '#F2F2F2',
textColor: '#000000',
IconComponent: Icon.Google,
text: '구글계정 로그인',
- onPress: (navigation: any) => navigation.navigate('GoogleLogin'),
+ onPress: (navigation: any) => navigation.navigate(AuthNavigations.GOOGLE_LOGIN),
},
];
diff --git a/src/components/MyPage/Block/BlockedUsers/index.tsx b/src/components/MyPage/Block/BlockedUsers/index.tsx
index 7b6fbce..cd5f043 100644
--- a/src/components/MyPage/Block/BlockedUsers/index.tsx
+++ b/src/components/MyPage/Block/BlockedUsers/index.tsx
@@ -1,25 +1,52 @@
-import { useBlockedUsers } from '~apis/member/useBlockedUsers';
+import { Alert } from 'react-native';
+import { useBlock } from '~apis/block/useBlock';
import { UserInfo } from '~components/Common/UserInfo';
+import { useToast } from '~hooks/useToast';
+import { queryClient } from '~providers/QueryClientProvider';
+import { NoBlockedUsers } from '~components/MyPage/NoBlockedUsers';
export const BlockedUsers = () => {
- const blockedUsers = useBlockedUsers();
+ const { blockedUsers, unblockUserMutation } = useBlock();
+ const { successToast } = useToast();
+
+ const handleUnblock = (id: number) => {
+ Alert.alert('차단 해제하시겠습니까?', '', [
+ {
+ text: '취소',
+ style: 'cancel',
+ },
+ {
+ text: '차단 해제',
+ onPress: () =>
+ unblockUserMutation.mutate(id, {
+ onSuccess: () => {
+ successToast('차단이 해제되었습니다');
+ queryClient.invalidateQueries({ queryKey: ['allBlockedUsers'] });
+ },
+ }),
+ },
+ ]);
+ };
return (
-
- {blockedUsers?.map((user, idx) => (
- {}}
- userId={user.memberId}
- isLast={idx === blockedUsers.length - 1}
- />
- ))}
+
+ {blockedUsers.length === 0 ? (
+
+ ) : (
+ blockedUsers.map((user, idx) => (
+ handleUnblock(user.blockId)}
+ userId={user.blockId}
+ isLast={idx === blockedUsers.length - 1}
+ />
+ ))
+ )}
);
};
diff --git a/src/components/MyPage/NoBlockedUsers/index.tsx b/src/components/MyPage/NoBlockedUsers/index.tsx
new file mode 100644
index 0000000..0bdf5fc
--- /dev/null
+++ b/src/components/MyPage/NoBlockedUsers/index.tsx
@@ -0,0 +1,9 @@
+import * as S from './styles';
+
+export const NoBlockedUsers = () => {
+ return (
+
+ 차단 목록이 없습니다.
+
+ );
+};
diff --git a/src/components/MyPage/NoBlockedUsers/styles.ts b/src/components/MyPage/NoBlockedUsers/styles.ts
new file mode 100644
index 0000000..c9b046d
--- /dev/null
+++ b/src/components/MyPage/NoBlockedUsers/styles.ts
@@ -0,0 +1,12 @@
+import styled from '@emotion/native';
+import { TextRegular } from '~components/Common/Text';
+
+export const NoBlockedUsers = styled.View`
+ flex: 1;
+ justify-content: center;
+ align-items: center;
+`;
+
+export const Description = styled(TextRegular)`
+ color: ${props => props.theme.colors.font_1};
+`;
diff --git a/src/components/Profile/UserProfile/index.tsx b/src/components/Profile/UserProfile/index.tsx
index c78a3b9..b58f46f 100644
--- a/src/components/Profile/UserProfile/index.tsx
+++ b/src/components/Profile/UserProfile/index.tsx
@@ -9,18 +9,21 @@ interface UserProfileProps {
}
export const UserProfile = ({ userId }: UserProfileProps) => {
- const { address, familyRole, avatarNumber, memberGender, memberName } = useUserById({ memberId: userId });
+ const { data: user, isPending, isError } = useUserById({ memberId: userId });
+
+ if (isPending || isError) {
+ return <>>;
+ }
return (
-
- {memberName}
- {address}
+
+ {user.memberName}
+ {user.address}
- {memberGender === 'MALE' ? '남자' : '여자'}
+ {user.memberGender === 'MALE' ? '남자' : '여자'}
- {/* 임시 dogGender */}
- {getKoreanRole({ dogGender: 'FEMALE', familyRole })}
+ {getKoreanRole({ dogGender: 'FEMALE', familyRole: user.familyRole })}
);
diff --git a/src/components/Profile/WalkInfo/index.tsx b/src/components/Profile/WalkInfo/index.tsx
index 8a2ec89..1ec2f97 100644
--- a/src/components/Profile/WalkInfo/index.tsx
+++ b/src/components/Profile/WalkInfo/index.tsx
@@ -1,8 +1,8 @@
import { useAccumulatedWalkInfo } from '~apis/dog/useAccumulatedWalkInfo';
import { StatContainer } from '~components/Common/StatContainer';
-export const WalkInfo = ({ dogId }: { dogId: number }) => {
- const { walkCount, countWalksWithMember, totalDistance } = useAccumulatedWalkInfo({ dogId });
+export const WalkInfo = ({ memberId }: { memberId: number }) => {
+ const { walkCount, countWalksWithMember, totalDistance } = useAccumulatedWalkInfo({ memberId });
return (
diff --git a/src/components/Social/Friend/index.tsx b/src/components/Social/Friend/index.tsx
index 353b012..4fd1e7d 100644
--- a/src/components/Social/Friend/index.tsx
+++ b/src/components/Social/Friend/index.tsx
@@ -1,23 +1,53 @@
import { NavigationProp, useNavigation } from '@react-navigation/native';
+import { createContext, useMemo, useState } from 'react';
import { useDogInfoByMemberId } from '~apis/dog/useDogInfoByMemberId';
import { FetchFriendsResponseType } from '~apis/friend/fetchFriends';
import { useFriends } from '~apis/friend/useFriends';
import { UserInfo } from '~components/Common/UserInfo';
-import { TabBarParamList } from '~navigation/BottomTabNavigator';
+import { FriendOptions } from '~components/Social/FriendOptions';
+import { SocialNavigations } from '~constants/navigations';
+import { SocialParamList } from '~navigation/SocialNavigator';
+
+interface FriendOptionProps {
+ setIsFriendOptionsVisible: React.Dispatch>;
+ setFriendId: React.Dispatch>;
+}
+
+export const FriendOptionContext = createContext(undefined);
export const FriendTab = () => {
const friends = useFriends();
- console.log({ friends });
+ const [isFriendOptionsVisible, setIsFriendOptionsVisible] = useState(false);
+ const [friendId, setFriendId] = useState(1);
+
+ const friendItems = useMemo(() => {
+ return (
+
+
+ {friends?.map((friend, idx) => (
+
+ ))}
+
+
+ );
+ }, [friends]);
+
return (
-
- (
-
- {friends?.map((friend, idx) => (
-
- ))}
-
- ),
-
+ <>
+
+ {friendItems}
+
+ setIsFriendOptionsVisible(false)}
+ friendId={friendId}
+ />
+ >
);
};
@@ -31,22 +61,23 @@ const Item = ({
isLast: boolean;
}) => {
const dogInfos = useDogInfoByMemberId({ memberId });
- const navigation = useNavigation>();
- console.log({ dogInfos }); //todo 3번 멤버 강아지 없어서 에러 발생. 데이터 추가 요청하기
+ const navigation = useNavigation>();
if (!dogInfos || !dogInfos.length) {
return null;
}
return (
- navigation.navigate('Talk', { userId: friend.memberId })}
- userId={friend.memberId}
- isLast={isLast}
- />
+ <>
+ navigation.navigate(SocialNavigations.CHATROOM, { userId: friend.memberId })}
+ userId={friend.memberId}
+ isLast={isLast}
+ optionButton
+ />
+ >
);
};
diff --git a/src/components/Social/FriendOptions/index.tsx b/src/components/Social/FriendOptions/index.tsx
new file mode 100644
index 0000000..af773c5
--- /dev/null
+++ b/src/components/Social/FriendOptions/index.tsx
@@ -0,0 +1,64 @@
+import { useUserById } from '~apis/member/useUserById';
+import { CompoundOption } from '~components/Common/CompoundOptions';
+import { Profile } from '~components/Common/Profile';
+import { TextBold } from '~components/Common/Text';
+import * as S from './styles';
+import { useDeleteFriend } from '~apis/friend/useDeleteFriend';
+import { Alert } from 'react-native';
+
+interface FriendOptionsProps {
+ isVisible: boolean;
+ hideOption: () => void;
+ friendId: number;
+}
+
+export const FriendOptions = ({ isVisible, hideOption, friendId }: FriendOptionsProps) => {
+ const { data: friendInfo, isPending, isError } = useUserById({ memberId: friendId });
+ const deleteFrinedMutation = useDeleteFriend();
+
+ if (isPending || isError) {
+ return <>>;
+ }
+
+ const handleDeleteFriend = () => {
+ Alert.alert(`'${friendInfo.memberName}'님을 친구 목록에서 삭제하시겠습니까?`, '', [
+ {
+ text: '취소',
+ style: 'cancel',
+ },
+ {
+ text: '삭제하기',
+ onPress: () => {
+ deleteFrinedMutation.mutate(friendId, {
+ onSuccess: hideOption,
+ onError: error => console.error(error),
+ onSettled: () => console.log(friendId),
+ });
+ },
+ },
+ ]);
+ };
+
+
+ return (
+
+
+
+
+
+ {friendInfo.memberName}
+
+
+ null}>상세 프로필 보기(미구현)
+
+
+ 친구 삭제
+
+
+
+ 취소
+
+
+
+ );
+};
diff --git a/src/components/Social/FriendOptions/styles.ts b/src/components/Social/FriendOptions/styles.ts
new file mode 100644
index 0000000..957436e
--- /dev/null
+++ b/src/components/Social/FriendOptions/styles.ts
@@ -0,0 +1,10 @@
+import styled from '@emotion/native';
+
+export const FriendProfile = styled.View`
+ width: 100%;
+ height: 150px;
+ justify-content: center;
+ align-items: center;
+ text-align: center;
+ gap: 10px;
+`;
diff --git a/src/components/Social/TalkTab/index.tsx b/src/components/Social/TalkTab/index.tsx
index bfec1bd..ebef553 100644
--- a/src/components/Social/TalkTab/index.tsx
+++ b/src/components/Social/TalkTab/index.tsx
@@ -4,11 +4,12 @@ import * as S from './styles';
import { UnreadChatCount } from '~components/Common/UnreadChatCount';
import { Pressable } from 'react-native';
import { NavigationProp, useNavigation } from '@react-navigation/native';
-import { TabBarParamList } from '~navigation/BottomTabNavigator';
import { useChatRooms } from '~apis/chat/useChatRooms';
-import { FetchChatRoomsResponseType } from '~apis/chat/fetchChatRooms';
-import { getKoreanRole } from '~utils/getKoreanRoleWithName';
-import { useDogInfoByMemberId } from '~apis/dog/useDogInfoByMemberId';
+import { SocialNavigations } from '~constants/navigations';
+import { SocialParamList } from '~navigation/SocialNavigator';
+import { ResponseFetchChatRoom } from '~apis/chat/fetchChatRooms';
+import { FAMILY_ROLE } from '~constants/family-role';
+import { useEffect } from 'react';
export const TalkTab = () => {
const chatRooms = useChatRooms();
@@ -22,11 +23,15 @@ export const TalkTab = () => {
);
};
-const TalkItem = ({ lastMessage, members, name, unreadMessageCount }: FetchChatRoomsResponseType[number]) => {
- const navigation = useNavigation>();
- const opponentDogInfos = useDogInfoByMemberId({ memberId: members[0].memberId });
+const TalkItem = ({ lastMessage, members, name, unreadMessageCount }: ResponseFetchChatRoom) => {
+ const navigation = useNavigation>();
+
+ useEffect(() => {
+ console.log('members[0]', members[0]);
+ console.log('members[1]', members[1]);
+ }, [members]);
return (
- navigation.navigate('Talk', { userId: members[0].memberId })}>
+ navigation.navigate(SocialNavigations.CHATROOM, { userId: members[0].memberId })}>
@@ -34,10 +39,7 @@ const TalkItem = ({ lastMessage, members, name, unreadMessageCount }: FetchChatR
{name}
- {getKoreanRole({
- dogGender: opponentDogInfos ? opponentDogInfos[0]?.dogGender : 'MALE',
- familyRole: members[0].familyRole,
- })}
+ {FAMILY_ROLE[members[0].familyRole]}
diff --git a/src/components/Talk/ChatRoomOptions/index.tsx b/src/components/Talk/ChatRoomOptions/index.tsx
new file mode 100644
index 0000000..5eefc23
--- /dev/null
+++ b/src/components/Talk/ChatRoomOptions/index.tsx
@@ -0,0 +1,84 @@
+import { Alert } from 'react-native';
+import { useBlock } from '~apis/block/useBlock';
+import { CompoundOption } from '~components/Common/CompoundOptions';
+import { useToast } from '~hooks/useToast';
+import { queryClient } from '~providers/QueryClientProvider';
+
+interface ChatRoomOptionsProps {
+ isVisible: boolean;
+ hideOption: () => void;
+ chatPartnerId: number;
+}
+
+export const ChatRoomOptions = ({ isVisible, hideOption, chatPartnerId }: ChatRoomOptionsProps) => {
+ const { blockedUsers, blockUserMutation, unblockUserMutation } = useBlock();
+ const { successToast } = useToast();
+
+ const isBlocked = blockedUsers.some(blockedUser => blockedUser.blockId === chatPartnerId);
+
+ const handleBlock = () => {
+ Alert.alert(
+ '상대방을 차단하시겠습니까?',
+ '차단하면 차단한 상대방의 메시지를 더 이상 받지 않게 됩니다. 친구로 등록되어 있다면 친구 목록에서도 삭제됩니다.',
+ [
+ {
+ text: '취소',
+ style: 'cancel',
+ },
+ {
+ text: '차단하기',
+ onPress: () =>
+ blockUserMutation.mutate(chatPartnerId, {
+ onSuccess: () => {
+ successToast('차단되었습니다');
+ queryClient.invalidateQueries({ queryKey: ['allBlockedUsers'] });
+ queryClient.invalidateQueries({ queryKey: ['friends'] });
+ },
+ onSettled: () => hideOption(),
+ }),
+ },
+ ],
+ );
+ };
+
+ const handleUnblock = () => {
+ Alert.alert('차단 해제하시겠습니까?', '', [
+ {
+ text: '취소',
+ style: 'cancel',
+ },
+ {
+ text: '차단 해제',
+ onPress: () =>
+ unblockUserMutation.mutate(chatPartnerId, {
+ onSuccess: () => {
+ successToast('차단이 해제되었습니다');
+ queryClient.invalidateQueries({ queryKey: ['allBlockedUsers'] });
+ },
+ onSettled: () => hideOption(),
+ }),
+ },
+ ]);
+ };
+
+ return (
+
+
+
+ null}>채팅방 나가기(미구현)
+
+ {isBlocked ? (
+ 차단 해제
+ ) : (
+
+ 차단하기
+
+ )}
+
+
+ 취소
+
+
+
+ );
+};
diff --git a/src/components/Talk/Message/styles.ts b/src/components/Talk/Message/styles.ts
index 2a8f669..9ffd05d 100644
--- a/src/components/Talk/Message/styles.ts
+++ b/src/components/Talk/Message/styles.ts
@@ -8,6 +8,7 @@ const Message = styled(View)`
white-space: pre-line;
margin: 8px 0;
z-index: 1;
+ max-width: 230px;
`;
export const IncomingMessage = styled(Message)`
width: fit-content;
diff --git a/src/components/Talk/TalkArea/index.tsx b/src/components/Talk/TalkArea/index.tsx
index e3aced7..2de8cc5 100644
--- a/src/components/Talk/TalkArea/index.tsx
+++ b/src/components/Talk/TalkArea/index.tsx
@@ -1,71 +1,65 @@
-import { IncomingMessage, OutgoingMessage } from '~components/Talk/Message/styles';
+import { FlatList } from 'react-native';
import * as S from './styles';
import { TextBold } from '~components/Common/Text';
-import DogHowling from '~assets/dogs/dog-howling.svg';
import { Dimensions } from 'react-native';
+import { IncomingMessage, OutgoingMessage } from '~components/Talk/Message/styles';
+import DogHowling from '~assets/dogs/dog-howling.svg';
+import { useState } from 'react';
+
+interface TalkAreaProps {
+ messages: any[];
+}
-interface TalkAreaProps {}
+export const TalkArea = ({ messages }: TalkAreaProps) => {
+ const deviceWidth = Dimensions.get('window').width;
+ const [talkAreaHeight, setTalkAreaHeight] = useState(0);
+ const [flatListHeight, setFlatListHeight] = useState(0);
+
+ const renderMessage = ({ item }: { item: (typeof messages)[0] }) => {
+ if (item.type === 'incoming') {
+ return (
+
+ {item.text}
+
+ );
+ }
+ return (
+
+ {item.text}
+
+ );
+ };
-export const TalkArea = ({}: TalkAreaProps) => {
- const width = Dimensions.get('window').width;
return (
-
+ {
+ const { height } = event.nativeEvent.layout;
+ setTalkAreaHeight(height);
+ }}
+ >
+ item.id.toString()}
+ removeClippedSubviews={false}
+ inverted
+ onContentSizeChange={(_, height) => {
+ setFlatListHeight(height);
}}
/>
-
- 안녕하세요 성훈님
-
-
- 안녕하세요!!
- {' '}
-
- 안녕하세요 성훈님
-
-
- 안녕하세요!!
- {' '}
-
- 안녕하세요 성훈님
-
-
- 안녕하세요!!
- {' '}
-
- 안녕하세요 성훈님
-
-
- 안녕하세요!!
-
-
- 안녕하세요 성훈님
-
-
- 안녕하세요!!
- {' '}
-
- 안녕하세요 성훈님
-
-
- 안녕하세요!!
- {' '}
-
- 안녕하세요 성훈님
-
-
- 안녕하세요!!
- {' '}
-
- 안녕하세요 성훈님
-
-
- 안녕하세요!!
-
);
};
diff --git a/src/components/Talk/TalkArea/styles.ts b/src/components/Talk/TalkArea/styles.ts
index 7713f76..32a6495 100644
--- a/src/components/Talk/TalkArea/styles.ts
+++ b/src/components/Talk/TalkArea/styles.ts
@@ -1,8 +1,6 @@
import styled from '@emotion/native';
-export const TalkArea = styled.ScrollView`
+export const TalkArea = styled.View`
flex: 1;
background-color: ${({ theme }) => theme.colors.lighten_3};
- padding: 20px;
- gap: 0;
`;
diff --git a/src/constants/navigations.ts b/src/constants/navigations.ts
index 4e25e79..188097c 100644
--- a/src/constants/navigations.ts
+++ b/src/constants/navigations.ts
@@ -1,3 +1,15 @@
+export const RootNavigations = {
+ BOTTOM_TAB: 'BottomTab',
+ REGISTER_DOG: 'RegisterDog',
+} as const;
+
+export const AuthNavigations = {
+ LOGIN: 'Login',
+ KAKAO_LOGIN: 'KakaoLogin',
+ GOOGLE_LOGIN: 'GoogleLogin',
+ OWNER_PROFILE: 'OwnerProfile',
+} as const;
+
export const RegisterDogNavigations = {
HOME: 'Home',
BASIC_PROFILE: 'BasicProfile',
@@ -6,7 +18,27 @@ export const RegisterDogNavigations = {
DOG_CONFIRMATION: 'DogConfirmation',
} as const;
+export const TabNavigations = {
+ HOME: 'Home',
+ LOG: 'Log',
+ SOCIAL: 'Social',
+ FAMILYDANG: 'FamilyDang',
+ MYPAGE: 'MyPage',
+ PROFILE: 'Profile',
+} as const;
+
+export const HomeNavigations = {
+ MAIN: 'Main',
+ WALK: 'Walk',
+ NOTIFICATION: 'Notification',
+} as const;
+
export const WalkLogNavigations = {
- LogHome: 'LogHome',
- Stats: 'Stats',
+ LOG_HOME: 'LogHome',
+ STATS: 'Stats',
+} as const;
+
+export const SocialNavigations = {
+ SOCIAL_HOME: 'SocialHome',
+ CHATROOM: 'ChatRoom',
} as const;
diff --git a/src/hooks/useChat.ts b/src/hooks/useChat.ts
new file mode 100644
index 0000000..2e60cbb
--- /dev/null
+++ b/src/hooks/useChat.ts
@@ -0,0 +1,59 @@
+import { Client } from '@stomp/stompjs';
+import { useEffect, useRef, useState } from 'react';
+import SockJS from 'sockjs-client';
+import { getAccessToken } from '~utils/controlAccessToken';
+
+const SERVER_URL = 'https://ddang.site/ws';
+
+export const useChat = (email: string) => {
+ const stompClientRef = useRef(null);
+ const [messages, setMessages] = useState([]);
+
+ useEffect(() => {
+ const initializeWebSocket = async () => {
+ const accessToken = await getAccessToken();
+ const stompClient = new Client({
+ webSocketFactory: () => new SockJS(SERVER_URL),
+ reconnectDelay: 5000,
+ debug: msg => console.log(msg),
+ connectHeaders: {
+ Authorization: `Bearer ${accessToken}`,
+ },
+ });
+
+ stompClient.onConnect = () => {
+ console.log('STOMP 연결 성공');
+ console.log('WebSocket 연결 상태:', stompClientRef.current?.connected);
+
+ stompClient.subscribe(`/sub/${email}`, message => {
+ console.log(message.body);
+ const receivedMessage = JSON.parse(message.body);
+ console.log('받은 메시지', receivedMessage);
+ setMessages(prevMessages => [receivedMessage, ...prevMessages]);
+ });
+ };
+
+ stompClient.activate();
+ stompClientRef.current = stompClient;
+
+ return () => {
+ stompClient.deactivate();
+ };
+ };
+
+ initializeWebSocket();
+ }, [email]);
+
+ const sendMessage = (receiverEmail: string, text: string) => {
+ if (stompClientRef.current && stompClientRef.current.connected) {
+ const payload = JSON.stringify({ receiverEmail, message: text });
+ console.log('payload', payload);
+ stompClientRef.current.publish({
+ destination: '/pub/api/v1/chat/message',
+ body: payload,
+ });
+ }
+ };
+
+ return { messages, sendMessage };
+};
diff --git a/src/hooks/useImagePicker.ts b/src/hooks/useImagePicker.ts
index eae39bc..f312af3 100644
--- a/src/hooks/useImagePicker.ts
+++ b/src/hooks/useImagePicker.ts
@@ -1,4 +1,3 @@
-import { Platform } from 'react-native';
import ImageCropPicker from 'react-native-image-crop-picker';
import { ImageFileType } from '~types/image-file';
@@ -9,7 +8,7 @@ export const useImagePicker = () => {
}
const file: ImageFileType = {
- uri: Platform.OS === 'android' ? image.path.replace('file://', '') : `file://${image.path}`,
+ uri: `file://${image.path}`,
type: image.mime,
name: image.path.split('/').pop() || 'unknown.jpg',
};
diff --git a/src/navigation/AuthNavigator.tsx b/src/navigation/AuthNavigator.tsx
index 4823f64..64b3251 100644
--- a/src/navigation/AuthNavigator.tsx
+++ b/src/navigation/AuthNavigator.tsx
@@ -5,6 +5,8 @@ import { Icon } from '~components/Common/Icons';
import { Header } from '~components/Common/Header';
import { Login } from '~screens/Auth/Login';
import { GoogleLogin } from '~screens/Auth/GoogleLogin';
+import { AuthNavigations } from '~constants/navigations';
+import { useTheme } from '@emotion/react';
export type AuthParamList = {
Login: undefined;
@@ -15,24 +17,25 @@ export type AuthParamList = {
export const AuthNavigator = () => {
const Stack = createNativeStackNavigator();
+ const theme = useTheme();
return (
(
@@ -41,7 +44,7 @@ export const AuthNavigator = () => {
}}
/>
(
@@ -50,7 +53,7 @@ export const AuthNavigator = () => {
}}
/>
} onLeftPress={() => navigation.goBack()} />,
diff --git a/src/navigation/BottomTabNavigator.tsx b/src/navigation/BottomTabNavigator.tsx
index 9167ef2..fb9c661 100644
--- a/src/navigation/BottomTabNavigator.tsx
+++ b/src/navigation/BottomTabNavigator.tsx
@@ -11,9 +11,10 @@ import { HomeNavigator } from '~navigation/HomeNavigator';
import { MyPageNavigator } from '~navigation/MyPageNavigator';
import { ProfileScreen } from '~screens/Profile';
import { WalkLogNavigator } from '~navigation/WalkLogNavigator';
-import { SocialScreen } from '~screens/Social';
-import { FamilyDDangNavigator, FamilyDdangParamList } from '~navigation/FamilyDDangNavigator';
-import { TalkScreen } from '~screens/Talk';
+import { FamilyDDangNavigator } from '~navigation/FamilyDDangNavigator';
+import { getFocusedRouteNameFromRoute } from '@react-navigation/native';
+import { HomeNavigations, SocialNavigations, TabNavigations, WalkLogNavigations } from '~constants/navigations';
+import { SocialNavigator } from '~navigation/SocialNavigator';
export type TabBarParamList = {
Home: undefined;
@@ -22,11 +23,15 @@ export type TabBarParamList = {
FamilyDang: undefined;
MyPage: undefined;
Profile: { userId: number };
- FamilyDDang: { screen?: keyof FamilyDdangParamList };
- Talk: { userId: number };
};
const Tab = createBottomTabNavigator();
+const hiddenTabRoutes: ReadonlyArray = [
+ HomeNavigations.NOTIFICATION,
+ WalkLogNavigations.STATS,
+ SocialNavigations.CHATROOM,
+ 'CreateInviteCode',
+];
const TabIcon = ({ focused, name, size, color }: { focused: boolean; name: string } & IconButtonProps) => (
{
const theme = useTheme();
return (
({
tabBarActiveTintColor: '#783D16',
tabBarLabelPosition: 'below-icon',
headerShown: false,
- }}
+ tabBarStyle: (tabRoute => {
+ const routeName = getFocusedRouteNameFromRoute(tabRoute);
+ if (routeName && hiddenTabRoutes.includes(routeName)) {
+ return { display: 'none' };
+ }
+ })(route),
+ })}
>
,
}}
/>
(
@@ -66,8 +77,8 @@ export const BottomTabNavigator = () => {
}}
/>
(
@@ -75,7 +86,7 @@ export const BottomTabNavigator = () => {
}}
/>
(
@@ -86,7 +97,7 @@ export const BottomTabNavigator = () => {
/>
(
@@ -95,7 +106,7 @@ export const BottomTabNavigator = () => {
}}
/>
({
tabBarButton: () => null,
@@ -121,16 +132,6 @@ export const BottomTabNavigator = () => {
animation: 'shift',
})}
/>
- null,
- tabBarItemStyle: {
- display: 'none',
- },
- }}
- />
);
};
diff --git a/src/navigation/HomeNavigator.tsx b/src/navigation/HomeNavigator.tsx
index 6e57942..cddb4b4 100644
--- a/src/navigation/HomeNavigator.tsx
+++ b/src/navigation/HomeNavigator.tsx
@@ -2,6 +2,7 @@ import { useTheme } from '@emotion/react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { Header } from '~components/Common/Header';
import { Icon } from '~components/Common/Icons';
+import { HomeNavigations } from '~constants/navigations';
import { HomeScreen } from '~screens/Home';
import { NotificationScreen } from '~screens/Home/Notification';
import { WalkScreen } from '~screens/Home/WalkScreen';
@@ -30,10 +31,14 @@ export const HomeNavigator = () => {
},
}}
>
-
-
+
+ (
diff --git a/src/navigation/RootNavigator.tsx b/src/navigation/RootNavigator.tsx
index 1ec7dad..b730317 100644
--- a/src/navigation/RootNavigator.tsx
+++ b/src/navigation/RootNavigator.tsx
@@ -6,6 +6,7 @@ import { useAuth } from '~apis/member/useAuth';
import { useEffect } from 'react';
import SplashScreen from 'react-native-splash-screen';
import { NavigatorScreenParams } from '@react-navigation/native';
+import { RootNavigations } from '~constants/navigations';
export type RootStackNavigationProp = NativeStackNavigationProp;
@@ -37,8 +38,8 @@ export const RootNavigator = () => {
return (
-
-
+
+
);
};
diff --git a/src/navigation/SocialNavigator.tsx b/src/navigation/SocialNavigator.tsx
new file mode 100644
index 0000000..6cde0c4
--- /dev/null
+++ b/src/navigation/SocialNavigator.tsx
@@ -0,0 +1,30 @@
+import { useTheme } from '@emotion/react';
+import { createNativeStackNavigator } from '@react-navigation/native-stack';
+import { SocialNavigations } from '~constants/navigations';
+import { SocialHomeScreen } from '~screens/Social';
+import { ChatRoomScreen } from '~screens/Social/ChatRoom';
+
+export type SocialParamList = {
+ SocialHome: undefined;
+ ChatRoom: { userId: number };
+};
+
+export const SocialNavigator = () => {
+ const Stack = createNativeStackNavigator();
+ const theme = useTheme();
+
+ return (
+
+
+
+
+ );
+};
diff --git a/src/navigation/WalkLogNavigator.tsx b/src/navigation/WalkLogNavigator.tsx
index 42ceb63..e73b99b 100644
--- a/src/navigation/WalkLogNavigator.tsx
+++ b/src/navigation/WalkLogNavigator.tsx
@@ -26,7 +26,7 @@ export const WalkLogNavigator = () => {
options={() => ({
headerShown: false,
})}
- name={WalkLogNavigations.LogHome}
+ name={WalkLogNavigations.LOG_HOME}
component={LogHome}
/>
{
),
headerTitle: '산책 분석',
})}
- name={WalkLogNavigations.Stats}
+ name={WalkLogNavigations.STATS}
component={Stats}
/>
diff --git a/src/screens/Auth/KakaoLogin/index.tsx b/src/screens/Auth/KakaoLogin/index.tsx
index 6b7aaca..c6189c3 100644
--- a/src/screens/Auth/KakaoLogin/index.tsx
+++ b/src/screens/Auth/KakaoLogin/index.tsx
@@ -7,6 +7,7 @@ import { storeAccessToken } from '~utils/controlAccessToken';
import { queryClient } from '~providers/QueryClientProvider';
import { useState } from 'react';
import { ActivityIndicator, Dimensions } from 'react-native';
+import { AuthNavigations } from '~constants/navigations';
export const KakaoLogin = () => {
const navigation = useNavigation>();
@@ -29,7 +30,7 @@ export const KakaoLogin = () => {
const provider = params.get('provider') || '';
console.log('Register Params:', { email, provider });
- navigation.replace('OwnerProfile', { email, provider });
+ navigation.replace(AuthNavigations.OWNER_PROFILE, { email, provider });
} else if (url.includes('accessToken')) {
const accessToken = params.get('accessToken') || '';
diff --git a/src/screens/FamilyDang/FamilyInfo/familylist.tsx b/src/screens/FamilyDang/FamilyInfo/familylist.tsx
index a2e2cea..94b390c 100644
--- a/src/screens/FamilyDang/FamilyInfo/familylist.tsx
+++ b/src/screens/FamilyDang/FamilyInfo/familylist.tsx
@@ -1,4 +1,3 @@
-import React from 'react';
import * as S from '../styles';
import { Separator } from '~components/Common/Seperator';
import { useFamilyInfo } from '~apis/family/useFamilyInfo';
diff --git a/src/screens/FamilyDang/FamilySetting/FamilyOut/index.tsx b/src/screens/FamilyDang/FamilySetting/FamilyOut/index.tsx
index 49e8a7f..c135e44 100644
--- a/src/screens/FamilyDang/FamilySetting/FamilyOut/index.tsx
+++ b/src/screens/FamilyDang/FamilySetting/FamilyOut/index.tsx
@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import { useState } from 'react';
import * as S from '../styles';
import { ClickFamily } from '~screens/FamilyDang/FamilyInfo/clickfamily';
import { FamilyComment } from '~screens/FamilyDang/FamilyInfo/familycomment';
@@ -73,4 +73,4 @@ export const FamilyOut = () => {
);
-};
+};
\ No newline at end of file
diff --git a/src/screens/FamilyDang/FamilySetting/index.tsx b/src/screens/FamilyDang/FamilySetting/index.tsx
index bd29c69..0af3ffd 100644
--- a/src/screens/FamilyDang/FamilySetting/index.tsx
+++ b/src/screens/FamilyDang/FamilySetting/index.tsx
@@ -147,4 +147,4 @@ export const FamilySetting = () => {
);
-};
+};
\ No newline at end of file
diff --git a/src/screens/Log/index.tsx b/src/screens/Log/index.tsx
index 5adfe1c..af005e8 100644
--- a/src/screens/Log/index.tsx
+++ b/src/screens/Log/index.tsx
@@ -58,7 +58,7 @@ export const LogHome = () => {
/>
}
right={
- navigation.navigate(WalkLogNavigations.Stats)}>
+ navigation.navigate(WalkLogNavigations.STATS)}>
}
diff --git a/src/screens/Profile/index.tsx b/src/screens/Profile/index.tsx
index 77ad8c3..271bc48 100644
--- a/src/screens/Profile/index.tsx
+++ b/src/screens/Profile/index.tsx
@@ -1,8 +1,6 @@
import { BottomTabScreenProps } from '@react-navigation/bottom-tabs';
-import { useQuery } from '@tanstack/react-query';
import { Suspense, useEffect } from 'react';
import ErrorBoundary from 'react-native-error-boundary';
-import { fetchUserById } from '~apis/member/fetchUserById';
import { DogProfile } from '~components/Profile/DogProfile';
import { DogProfileFallback } from '~components/Profile/DogProfile/fallback';
import { DogProfileLoader } from '~components/Profile/DogProfile/loader';
@@ -14,22 +12,23 @@ import { WalkInfoFallback } from '~components/Profile/WalkInfo/fallback';
import { WalkInfoLoader } from '~components/Profile/WalkInfo/loader';
import { TabBarParamList } from '~navigation/BottomTabNavigator';
import * as S from './styles';
+import { useUserById } from '~apis/member/useUserById';
interface ProfileScreenProps extends BottomTabScreenProps {}
export const ProfileScreen = ({ navigation, route }: ProfileScreenProps) => {
- const memberId = route.params!.userId; //! 항상 params로 userId를 넘겨줌
- const { data: userInfoById } = useQuery({
- queryKey: ['userInfoById', memberId],
- queryFn: () => fetchUserById({ memberId }),
- select: ({ data }) => data,
- });
+ const memberId = route.params!.userId;
+ const { data: user, isPending, isError } = useUserById({ memberId });
useEffect(() => {
navigation.setOptions({
- headerTitle: userInfoById?.memberName,
+ headerTitle: user?.memberName,
});
- }, [navigation, userInfoById?.memberName]);
+ }, [navigation, user?.memberName]);
+
+ if (isPending || isError) {
+ return <>>;
+ }
return (
@@ -40,12 +39,12 @@ export const ProfileScreen = ({ navigation, route }: ProfileScreenProps) => {
}>
-
+
}>
-
+
diff --git a/src/screens/Social/ChatRoom/index.tsx b/src/screens/Social/ChatRoom/index.tsx
new file mode 100644
index 0000000..781c5fd
--- /dev/null
+++ b/src/screens/Social/ChatRoom/index.tsx
@@ -0,0 +1,87 @@
+import { Separator } from '~components/Common/Seperator';
+import * as S from './styles';
+import { BottomTabScreenProps } from '@react-navigation/bottom-tabs';
+import { TalkArea } from '~components/Talk/TalkArea';
+import { Icon } from '~components/Common/Icons';
+import { SocialParamList } from '~navigation/SocialNavigator';
+import { KeyboardAvoidingView, Platform } from 'react-native';
+import { TextBold } from '~components/Common/Text';
+import { useUserById } from '~apis/member/useUserById';
+import { Profile } from '~components/Common/Profile';
+import { FAMILY_ROLE } from '~constants/family-role';
+import { useState } from 'react';
+import { ChatRoomOptions } from '~components/Talk/ChatRoomOptions';
+import { useUser } from '~apis/member/useUser';
+import { useChat } from '~hooks/useChat';
+
+interface TalkScreenProps extends BottomTabScreenProps {}
+
+export const ChatRoomScreen = ({ navigation, route }: TalkScreenProps) => {
+ const memberId = route.params!.userId;
+ const { data: chatPartner, isPending, isError } = useUserById({ memberId });
+ const myInfo = useUser();
+ const [isOptionVisible, setIsOptionVisible] = useState(false);
+ const [inputText, setInputText] = useState('');
+ const { messages, sendMessage } = useChat(myInfo.email);
+
+ if (isPending || isError) {
+ return <>>;
+ }
+
+ const handleSendMessage = () => {
+ if (inputText.trim()) {
+ sendMessage(chatPartner.email, inputText);
+ setInputText('');
+ }
+ };
+
+ return (
+
+
+
+
+ navigation.goBack()} />
+
+
+ {chatPartner.memberName}
+
+ {chatPartner.memberGender === 'MALE' ? '남자' : '여자'}
+
+ {FAMILY_ROLE[chatPartner.familyRole]}
+
+
+
+ setIsOptionVisible(true)}
+ />
+
+
+
+
+
+
+
+ 전송
+
+
+
+ setIsOptionVisible(false)}
+ chatPartnerId={chatPartner.memberId}
+ />
+
+
+ );
+};
diff --git a/src/screens/Talk/styles.ts b/src/screens/Social/ChatRoom/styles.ts
similarity index 65%
rename from src/screens/Talk/styles.ts
rename to src/screens/Social/ChatRoom/styles.ts
index e6e70a7..4740677 100644
--- a/src/screens/Talk/styles.ts
+++ b/src/screens/Social/ChatRoom/styles.ts
@@ -1,9 +1,9 @@
import styled from '@emotion/native';
import { Theme } from '@emotion/react';
-import { TextInput } from 'react-native';
+import { Platform, TextInput } from 'react-native';
import { TextBold, TextMedium } from '~components/Common/Text';
-export const Talk = styled.View`
+export const Talk = styled.SafeAreaView`
flex: 1;
`;
export const Header = styled.View`
@@ -27,11 +27,13 @@ export const Gender = styled(TextMedium)``;
export const FamilyRole = styled(TextMedium)``;
export const TalkInputWrapper = styled.View`
width: 100%;
- position: fixed;
- bottom: 0;
height: 64px;
padding: 12px 20px;
+ padding-top: ${Platform.OS === 'ios' ? 4 + 'px' : 12 + 'px'};
background-color: ${({ theme }) => theme.colors.gc_4};
+ flex-direction: row;
+ align-items: center;
+ gap: 20px;
`;
interface TextProps {
@@ -39,11 +41,27 @@ interface TextProps {
color?: keyof Theme['colors'];
}
export const TalkInput = styled(TextInput)`
+ flex: 1;
font-family: 'SUIT-Medium';
- width: 100%;
font-size: ${({ fontSize }) => fontSize + 'px'};
color: ${({ theme, color = 'font_1' }) => theme.colors[color]};
line-height: ${({ fontSize }) => fontSize * 1.5 + 'px'};
letter-spacing: ${({ fontSize }) => fontSize * -0.025 + 'px'};
- // medium 15px
+`;
+
+export const MessageSendButtonWrapper = styled.View`
+ width: 57px;
+ height: 64px;
+ justify-content: center;
+ align-items: center;
+ transform: translateY(${Platform.OS === 'ios' ? 3 + 'px' : 0 + 'px'});
+`;
+
+export const MessageSendButton = styled.Pressable`
+ width: 57px;
+ height: 40px;
+ background-color: ${props => props.theme.colors.lighten_2};
+ border-radius: 32px;
+ justify-content: center;
+ align-items: center;
`;
diff --git a/src/screens/Social/index.tsx b/src/screens/Social/index.tsx
index 7313588..4322d87 100644
--- a/src/screens/Social/index.tsx
+++ b/src/screens/Social/index.tsx
@@ -6,16 +6,16 @@ import { BlockedUsersLoader } from '~components/MyPage/Block/BlockedUsers/loader
import { FriendTab } from '~components/Social/Friend';
import { Tab } from '~components/Social/Tab';
import { TalkTab } from '~components/Social/TalkTab';
-import { TabBarParamList } from '~navigation/BottomTabNavigator';
import * as S from './styles';
+import { SocialParamList } from '~navigation/SocialNavigator';
+import { SocialNavigations } from '~constants/navigations';
-type Props = BottomTabScreenProps;
+type Props = BottomTabScreenProps;
-export const SocialScreen = ({}: Props) => {
+export const SocialHomeScreen = ({}: Props) => {
const [selectedTab, setSelectedTab] = useState<'댕친' | '댕톡'>('댕친');
-
return (
-
+
소셜
@@ -29,6 +29,6 @@ export const SocialScreen = ({}: Props) => {
) : (
)}
-
+
);
};
diff --git a/src/screens/Social/styles.ts b/src/screens/Social/styles.ts
index ef9add2..f263294 100644
--- a/src/screens/Social/styles.ts
+++ b/src/screens/Social/styles.ts
@@ -2,7 +2,7 @@ import styled from '@emotion/native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { TextBold } from '~components/Common/Text';
-export const SocialScreen = styled(SafeAreaView)`
+export const SocialHomeScreen = styled(SafeAreaView)`
background-color: ${({ theme }) => theme.colors.gc_4};
flex: 1;
`;
diff --git a/src/screens/Talk/index.tsx b/src/screens/Talk/index.tsx
deleted file mode 100644
index 2845288..0000000
--- a/src/screens/Talk/index.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import { Separator } from '~components/Common/Seperator';
-import { Profile } from '~components/Common/Profile';
-import { getKoreanRole } from '~utils/getKoreanRoleWithName';
-import * as S from './styles';
-import { BottomTabScreenProps } from '@react-navigation/bottom-tabs';
-import { TabBarParamList } from '~navigation/BottomTabNavigator';
-import { useEffect } from 'react';
-import { useQuery } from '@tanstack/react-query';
-import { fetchUserById } from '~apis/member/fetchUserById';
-import { TalkArea } from '~components/Talk/TalkArea';
-import { Icon } from '~components/Common/Icons';
-
-interface TalkScreenProps extends BottomTabScreenProps {}
-
-export const TalkScreen = ({ navigation, route }: TalkScreenProps) => {
- const memberId = route.params!.userId; //! 항상 params로 userId를 넘겨줌
- const { data: userInfoById } = useQuery({
- queryKey: ['userInfoById', memberId],
- queryFn: () => fetchUserById({ memberId }),
- select: ({ data }) => data,
- });
- const avatarNumber = 1;
- const userId = 1;
- const name = '감자탕수육';
- const gender = 'MALE';
- const dogGender = 'FEMALE';
- const familyRole = 'FATHER';
-
- useEffect(() => {
- navigation.setOptions({
- headerTitle: userInfoById?.memberName,
- });
- }, [navigation, userInfoById?.memberName]);
- return (
-
-
-
- navigation.navigate('Social')} />
-
-
- {name}
-
- {gender === 'MALE' ? '남자' : '여자'}
-
- {getKoreanRole({ dogGender, familyRole })}
-
-
-
-
-
-
-
-
-
- {/* 전송 버튼 만들기 */}
-
- );
-};