diff --git a/public/House with garden.svg b/public/House with garden.svg new file mode 100644 index 0000000000..c08ad15b1d --- /dev/null +++ b/public/House with garden.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/app/pages/mobile/mobile-profile-page.tsx b/src/app/pages/mobile/mobile-profile-page.tsx index 20c6b7a937..fee3c45928 100644 --- a/src/app/pages/mobile/mobile-profile-page.tsx +++ b/src/app/pages/mobile/mobile-profile-page.tsx @@ -1,5 +1,6 @@ 'use client'; +import axios from 'axios'; import Link from 'next/link'; import React, { useState, useEffect } from 'react'; import styled from 'styled-components'; @@ -7,9 +8,9 @@ import styled from 'styled-components'; import { Bookmark } from '@/components'; import { useAuthValue, useUserData } from '@/features/auth'; import { + type GetFollowingListDTO, useCertification, useFollowUser, - useFollowingListData, useGetCode, useUnfollowUser, useUserProfile, @@ -312,10 +313,30 @@ function UserInfo({ }: UserProfileInfoProps) { const [isChecked, setIsChecked] = useState(false); - const followList = useFollowingListData(); - const [isMarked, setIsMarked] = useState( - followList.data?.data.followingList[memberId] != null, - ); + const [followList, setFollowList] = useState>(); + const [isMarked, setIsMarked] = useState(false); + + useEffect(() => { + if (followList?.[memberId] != null) { + setIsMarked(true); + } else setIsMarked(false); + }, [followList]); + + useEffect(() => { + (async () => { + try { + const res = await axios.get( + '/maru-api/profile/follow', + ); + const followListData = res.data.data.followingList; + setFollowList(followListData); + return true; + } catch (error) { + console.error(error); + return false; + } + })(); + }, [setIsMarked]); const toggleSwitch = () => { setIsChecked(!isChecked); diff --git a/src/app/pages/mobile/mobile-shared-post-page.tsx b/src/app/pages/mobile/mobile-shared-post-page.tsx index 49ef50159a..721fe1f20a 100644 --- a/src/app/pages/mobile/mobile-shared-post-page.tsx +++ b/src/app/pages/mobile/mobile-shared-post-page.tsx @@ -6,7 +6,7 @@ import styled from 'styled-components'; import { Bookmark, CircularProfileImage } from '@/components'; import { ImageGrid } from '@/components/shared-post-page'; -import { useAuthValue, useUserData } from '@/features/auth'; +import { useAuthValue } from '@/features/auth'; import { useCreateChatRoom } from '@/features/chat'; import { fromAddrToCoord } from '@/features/geocoding'; import { useFollowUser, useUnfollowUser } from '@/features/profile'; @@ -445,15 +445,6 @@ export function MobileSharedPostPage({ enabled: type === 'dormitory' && auth?.accessToken != null, }); - const { data: userData } = useUserData(auth?.accessToken != null); - const [userId, setUserId] = useState(''); - - useEffect(() => { - if (userData != null) { - setUserId(userData.memberId); - } - }, [userData]); - useEffect(() => { if (sharedPost?.data.address.roadAddress != null) { fromAddrToCoord({ query: sharedPost?.data.address.roadAddress }).then( @@ -474,16 +465,7 @@ export function MobileSharedPostPage({ } }, [sharedPost]); - const [roomName, setRoomName] = useState(''); - - useEffect(() => { - if (sharedPost !== undefined) { - setRoomName(sharedPost.data.publisherAccount.nickname); - } - }, [sharedPost]); - - const members = [userId]; - const { mutate: chattingMutate } = useCreateChatRoom(roomName, members); + const { mutate: chattingMutate } = useCreateChatRoom(); const isLoading = useMemo( () => @@ -574,7 +556,12 @@ export function MobileSharedPostPage({ { - chattingMutate(); + if (selected == null) return; + + chattingMutate({ + roomName: selected.nickname, + members: [selected.memberId], + }); }} > 채팅 diff --git a/src/app/pages/profile-page.tsx b/src/app/pages/profile-page.tsx index afeae53605..94feb29e1d 100644 --- a/src/app/pages/profile-page.tsx +++ b/src/app/pages/profile-page.tsx @@ -1,5 +1,6 @@ 'use client'; +import axios from 'axios'; import Link from 'next/link'; import React, { useState, useEffect } from 'react'; import styled from 'styled-components'; @@ -7,9 +8,9 @@ import styled from 'styled-components'; import { Bookmark } from '@/components'; import { useAuthValue, useUserData } from '@/features/auth'; import { + type GetFollowingListDTO, useCertification, useFollowUser, - useFollowingListData, useGetCode, useUnfollowUser, useUserProfile, @@ -443,10 +444,30 @@ function UserInfo({ }: UserProfileInfoProps) { const [isChecked, setIsChecked] = useState(false); - const followList = useFollowingListData(); - const [isMarked, setIsMarked] = useState( - followList.data?.data.followingList[memberId] != null, - ); + const [followList, setFollowList] = useState>(); + const [isMarked, setIsMarked] = useState(false); + + useEffect(() => { + if (followList?.[memberId] != null) { + setIsMarked(true); + } else setIsMarked(false); + }, [followList]); + + useEffect(() => { + (async () => { + try { + const res = await axios.get( + '/maru-api/profile/follow', + ); + const followListData = res.data.data.followingList; + setFollowList(followListData); + return true; + } catch (error) { + console.error(error); + return false; + } + })(); + }, [setIsMarked]); const toggleSwitch = () => { setIsChecked(!isChecked); diff --git a/src/app/pages/shared-post-page.tsx b/src/app/pages/shared-post-page.tsx index 1f3035e703..ad56004904 100644 --- a/src/app/pages/shared-post-page.tsx +++ b/src/app/pages/shared-post-page.tsx @@ -2,12 +2,13 @@ import { useRouter } from 'next/navigation'; import { useEffect, useMemo, useRef, useState } from 'react'; +import { useRecoilState } from 'recoil'; import styled from 'styled-components'; import { Bookmark, CircularProfileImage } from '@/components'; import { CardToggleButton, ImageGrid } from '@/components/shared-post-page'; -import { useAuthValue, useUserData } from '@/features/auth'; -import { useCreateChatRoom } from '@/features/chat'; +import { useAuthValue } from '@/features/auth'; +import { chatOpenState, useCreateChatRoom } from '@/features/chat'; import { fromAddrToCoord } from '@/features/geocoding'; import { useFollowUser, useUnfollowUser } from '@/features/profile'; import { @@ -496,15 +497,6 @@ export function SharedPostPage({ enabled: type === 'dormitory' && auth?.accessToken != null, }); - const { data: userData } = useUserData(auth?.accessToken != null); - const [userId, setUserId] = useState(''); - - useEffect(() => { - if (userData != null) { - setUserId(userData.memberId); - } - }, [userData]); - useEffect(() => { if (sharedPost?.data.address.roadAddress != null) { fromAddrToCoord({ query: sharedPost?.data.address.roadAddress }).then( @@ -525,15 +517,9 @@ export function SharedPostPage({ } }, [sharedPost]); - const [roomName, setRoomName] = useState(''); + const [, setIsChatOpen] = useRecoilState(chatOpenState); - useEffect(() => { - if (sharedPost !== undefined) { - setRoomName(sharedPost.data.publisherAccount.nickname); - } - }, [sharedPost]); - - const { mutate: chattingMutate } = useCreateChatRoom(roomName, [userId]); + const { mutate: chattingMutate } = useCreateChatRoom(); const isLoading = useMemo( () => @@ -754,7 +740,16 @@ export function SharedPostPage({ { - chattingMutate(); + if (selected == null) return; + + chattingMutate({ + roomName: selected.nickname, + members: [selected.memberId], + }); + + setTimeout(() => { + setIsChatOpen(true); + }, 200); }} > 채팅하기 diff --git a/src/components/FloatingChatting.tsx b/src/components/FloatingChatting.tsx index 268e2d733d..8f20cdc569 100644 --- a/src/components/FloatingChatting.tsx +++ b/src/components/FloatingChatting.tsx @@ -4,13 +4,14 @@ import { Client } from '@stomp/stompjs'; import axios from 'axios'; import { useRouter } from 'next/navigation'; import React, { useEffect, useState } from 'react'; +import { useRecoilState } from 'recoil'; import styled from 'styled-components'; import { ChattingList } from './chat/ChattingList'; import { ChattingRoom } from './chat/ChattingRoom'; import { useAuthValue, useUserData } from '@/features/auth'; -import { type GetChatRoomDTO } from '@/features/chat'; +import { chatOpenState, type GetChatRoomDTO } from '@/features/chat'; import { useIsMobile } from '@/shared/mobile'; const styles = { @@ -246,7 +247,7 @@ function FloatingChattingBox() { }, [message, isChatRoomOpen]); // const roomName = 'test2'; - // const members = ['naver_htT4VdDRPKqGqKpnncpa71HCA4CVg5LdRC1cWZhCnF8']; + // const members = ['naver_hW_CDCYdU3NNTQWq_TV_MkpldnMZI6fOD1mnPo-V1NE']; // const { mutate: chattingCreate } = useCreateChatRoom(roomName, members); return ( @@ -269,12 +270,12 @@ function FloatingChattingBox() { {/* */} + onClick={() => { + chattingCreate(); + }} + > + 생성 + */} {chatRooms.map((room, index) => ( (false); + const [isChatOpen, setIsChatOpen] = useRecoilState(chatOpenState); const router = useRouter(); const toggleChat = () => { @@ -315,7 +316,6 @@ export function FloatingChatting() { const isMobile = useIsMobile(); useEffect(() => { - if (!isMobile) router.replace('/'); if (isChatOpen && isMobile) { router.replace('/chat'); } @@ -324,9 +324,14 @@ export function FloatingChatting() { } }, [isChatOpen, isMobile]); + useEffect(() => { + if (!isMobile && window.location.pathname === '/chat') { + router.replace('/'); + } + }, [isMobile]); + const auth = useAuthValue(); if (auth == null) return <>; - return ( <> diff --git a/src/components/card/VitalSection.tsx b/src/components/card/VitalSection.tsx index 32201a63cc..4eb359ccb0 100644 --- a/src/components/card/VitalSection.tsx +++ b/src/components/card/VitalSection.tsx @@ -348,7 +348,7 @@ export function VitalSection({ setFeatures(data); } - }, []); + }, [vitalFeatures]); const handleEssentialFeatureChange = useCallback( (key: 'smoking' | 'roomSharingOption' | 'mateAge', value: string) => { diff --git a/src/components/chat/ChatMenu.tsx b/src/components/chat/ChatMenu.tsx index 17083b6574..752b4b30fe 100644 --- a/src/components/chat/ChatMenu.tsx +++ b/src/components/chat/ChatMenu.tsx @@ -4,8 +4,8 @@ import Link from 'next/link'; import { useEffect, useState } from 'react'; import styled from 'styled-components'; -// import { useFollowingListData } from '@/features/profile'; -import { useChatRoomUser } from '@/features/chat'; +import { useChatRoomUser, useInviteUsers } from '@/features/chat'; +import { useSearchUser } from '@/features/profile'; const styles = { menuContainer: styled.div` @@ -169,7 +169,6 @@ const styles = { followingUserContainer: styled.div` display: flex; padding: 0.625rem; - justify-content: center; align-items: center; gap: 0.625rem; align-self: stretch; @@ -203,7 +202,6 @@ export function ChatMenu({ const [isInviteClick, setIsInviteClick] = useState(false); const users = useChatRoomUser(roomId); const [userList, setUserList] = useState([]); - // const folloingUsers = useFollowingListData(); useEffect(() => { if (users.data !== undefined) { @@ -217,9 +215,26 @@ export function ChatMenu({ onMenuClicked(isCloseClick); }; - // const { mutate: inviteUser } = useInviteUsers(roomId, [ - // 'naver_htT4VdDRPKqGqKpnncpa71HCA4CVg5LdRC1cWZhCnF8', - // ]); + const [email, setEmail] = useState(''); + const { mutate: mutateSearchUser, data: searchData } = useSearchUser(email); + + const [searchUser, setSearchUser] = useState(); + + useEffect(() => { + if (searchData?.data != null) { + setSearchUser(searchData.data); + } + }, [searchData]); + + const { mutate: inviteUser } = useInviteUsers(roomId, [ + searchUser?.memberId ?? '', + ]); + + function handleKeyUp(event: React.KeyboardEvent) { + if (event.keyCode === 13) { + mutateSearchUser(); + } + } return ( @@ -243,7 +258,6 @@ export function ChatMenu({ { - // inviteUser(); setIsInviteClick(prev => !prev); }} > @@ -253,30 +267,31 @@ export function ChatMenu({ - + { + setEmail(e.target.value); + }} + onKeyUp={handleKeyUp} + /> - {/* {Object.values( - folloingUsers.data?.data.followingList as Record< - string, - string[] - >, - ).map((user, index) => ( + {searchUser != null ? ( { + inviteUser(); + }} > - - {user[0]} + + {searchUser?.nickname} - ))} */} + ) : null} )} - 마이 마루 채팅방 나가기 diff --git a/src/components/chat/ChattingList.tsx b/src/components/chat/ChattingList.tsx index f08723c0df..8ca149530d 100644 --- a/src/components/chat/ChattingList.tsx +++ b/src/components/chat/ChattingList.tsx @@ -33,7 +33,7 @@ const styles = { width: 3rem; height: 3rem; border-radius: 50%; - background: url('__avatar_url.png') lightgray 50% / cover no-repeat; + background: url('/House with garden.svg') lightgray 50% / cover no-repeat; `, roomName: styled.p` color: #000; diff --git a/src/components/chat/ChattingRoom.tsx b/src/components/chat/ChattingRoom.tsx index e7941e4138..70c983ef3b 100644 --- a/src/components/chat/ChattingRoom.tsx +++ b/src/components/chat/ChattingRoom.tsx @@ -64,7 +64,7 @@ const styles = { width: 1rem; height: 1rem; flex-shrink: 0; - background: url('kebab-horizontal.svg') no-repeat; + background: url('/kebab-horizontal.svg') no-repeat; cursor: pointer; `, messageContainer: styled.div` @@ -147,6 +147,26 @@ function calTimeDiff(time: string, type: string) { return `${Math.floor(timeDiff / (60 * 24))}일 전`; } +function useInterval(callback: () => void, delay: number) { + const savedCallback = useRef(callback); + + useEffect(() => { + savedCallback.current = callback; + }, [callback]); + + useEffect(() => { + if (delay != null) { + const id = setInterval(() => { + savedCallback.current(); + }, delay); + return () => { + clearInterval(id); + }; + } + return undefined; + }, [delay]); +} + export function ChattingRoom({ userId, userName, @@ -241,7 +261,11 @@ export function ChattingRoom({ }, [auth?.accessToken]); const sendMessage = () => { - if (stompClient !== null && stompClient.connected) { + if ( + stompClient !== null && + stompClient.connected && + inputMessage.length !== 0 + ) { const destination = `/send/${roomId}`; stompClient.publish({ @@ -258,6 +282,16 @@ export function ChattingRoom({ setInputMessage(''); }; + const [timeString, setTimeString] = useState(calTimeDiff(lastTime, 'server')); + + useEffect(() => { + setTimeString(calTimeDiff(time, type)); + }, [time, type]); + + useInterval(() => { + setTimeString(calTimeDiff(time, type)); + }, 60000); + const handleMenuClick = () => { setIsMenuClick(prev => !prev); }; @@ -301,7 +335,7 @@ export function ChattingRoom({ /> {roomName} - {calTimeDiff(time, type)} + {timeString} {isMenuClick && ( diff --git a/src/components/chat/MobileChattingRoom.tsx b/src/components/chat/MobileChattingRoom.tsx index 56d74ecc94..be2d7a7d49 100644 --- a/src/components/chat/MobileChattingRoom.tsx +++ b/src/components/chat/MobileChattingRoom.tsx @@ -143,6 +143,26 @@ function calTimeDiff(time: string, type: string) { return `${Math.floor(timeDiff / (60 * 24))}일 전`; } +function useInterval(callback: () => void, delay: number) { + const savedCallback = useRef(callback); + + useEffect(() => { + savedCallback.current = callback; + }, [callback]); + + useEffect(() => { + if (delay != null) { + const id = setInterval(() => { + savedCallback.current(); + }, delay); + return () => { + clearInterval(id); + }; + } + return undefined; + }, [delay]); +} + export function MobileChattingRoom({ userId, userName, @@ -254,6 +274,16 @@ export function MobileChattingRoom({ setInputMessage(''); }; + const [timeString, setTimeString] = useState(calTimeDiff(lastTime, 'server')); + + useEffect(() => { + setTimeString(calTimeDiff(time, type)); + }, [time, type]); + + useInterval(() => { + setTimeString(calTimeDiff(time, type)); + }, 60000); + const handleMenuClick = () => { setIsMenuClick(prev => !prev); }; @@ -297,7 +327,7 @@ export function MobileChattingRoom({ /> {roomName} - {calTimeDiff(time, type)} + {timeString} {isMenuClick && ( diff --git a/src/components/chat/ReceiverMessage.tsx b/src/components/chat/ReceiverMessage.tsx index f0eab0367e..2ec6b250ab 100644 --- a/src/components/chat/ReceiverMessage.tsx +++ b/src/components/chat/ReceiverMessage.tsx @@ -25,7 +25,7 @@ const styles = { height: 2rem; border-radius: 50%; flex-shrink: 0; - background: url('__avatar_url.png') center no-repeat; + background: url('/__avatar_url.png') center no-repeat; `, userName: styled.p` color: var(--Text-gray, #666668); @@ -49,7 +49,7 @@ const styles = { leftTop: styled.div` width: 0.9375rem; height: 0.75rem; - background: url('Bubble tip R.svg') no-repeat; + background: url('/Bubble tip R.svg') no-repeat; `, leftMiddle: styled.div` width: 0.375rem; @@ -59,7 +59,7 @@ const styles = { leftBottom: styled.div` width: 0.375rem; height: 0.4375rem; - background: url('bottom-curve-vector R.svg') no-repeat; + background: url('/bottom-curve-vector R.svg') no-repeat; `, right: styled.div` display: flex; diff --git a/src/components/chat/SenderMessage.tsx b/src/components/chat/SenderMessage.tsx index e26e1505a8..3e0a15f6cb 100644 --- a/src/components/chat/SenderMessage.tsx +++ b/src/components/chat/SenderMessage.tsx @@ -56,11 +56,6 @@ const styles = { font-weight: 300; line-height: normal; `, - readState: styled.div` - width: 1rem; - height: 0.5rem; - background: url('read.svg') no-repeat; - `, left: styled.div` display: flex; flex-direction: column; @@ -71,7 +66,7 @@ const styles = { leftTop: styled.div` width: 0.9375rem; height: 0.75rem; - background: url('Bubble tip.svg') no-repeat; + background: url('/Bubble tip.svg') no-repeat; `, leftMiddle: styled.div` width: 0.375rem; @@ -82,7 +77,7 @@ const styles = { width: 0.375rem; height: 0.4375rem; fill: var(--Gray-5, #828282); - background: url('bottom-curve-vector.svg') no-repeat; + background: url('/bottom-curve-vector.svg') no-repeat; `, }; @@ -103,7 +98,6 @@ export function SenderMessage({ {message} {getLocalTime(time, type)} - diff --git a/src/features/chat/chat.api.ts b/src/features/chat/chat.api.ts index 81cb1d41be..e7a5938be7 100644 --- a/src/features/chat/chat.api.ts +++ b/src/features/chat/chat.api.ts @@ -14,11 +14,17 @@ export const getChatRoomList = async (token: string | undefined) => }) .then(res => res.data); -export const postChatRoom = async (roomName: string, members: string[]) => { +export const postChatRoom = async ({ + roomName, + members, +}: { + roomName: string; + members: string[]; +}) => { await axios .post(`/maru-api/chatRoom`, { - roomName: roomName, - members: members, + roomName, + members, }) .then(res => res.data); }; @@ -26,7 +32,7 @@ export const postChatRoom = async (roomName: string, members: string[]) => { export const postInviteUser = async (roomId: number, members: string[]) => { await axios .post(`/maru-api/chatRoom/${roomId}/invite`, { - members: members, + members, }) .then(res => res.data); }; @@ -40,9 +46,9 @@ export const getEnterChatRoom = async ( `/maru-api/chatRoom/${roomId}/chat`, { params: { - roomId: roomId, - page: page, - size: size, + roomId, + page, + size, }, }, ); diff --git a/src/features/chat/chat.atom.ts b/src/features/chat/chat.atom.ts new file mode 100644 index 0000000000..659c327b35 --- /dev/null +++ b/src/features/chat/chat.atom.ts @@ -0,0 +1,6 @@ +import { atom } from 'recoil'; + +export const chatOpenState = atom({ + key: 'isChatOpenState', + default: false, +}); diff --git a/src/features/chat/chat.hook.ts b/src/features/chat/chat.hook.ts index 3fdd47e077..c62388bab0 100644 --- a/src/features/chat/chat.hook.ts +++ b/src/features/chat/chat.hook.ts @@ -16,11 +16,9 @@ export const useChatRoomList = (token: string | undefined) => enabled: token !== undefined, }); -export const useCreateChatRoom = (roomName: string, members: string[]) => +export const useCreateChatRoom = () => useMutation({ - mutationFn: async () => { - await postChatRoom(roomName, members); - }, + mutationFn: postChatRoom, }); export const useInviteUsers = (roomId: number, members: string[]) => diff --git a/src/features/chat/index.ts b/src/features/chat/index.ts index be0e3149d5..ddc5adcc10 100644 --- a/src/features/chat/index.ts +++ b/src/features/chat/index.ts @@ -1,2 +1,3 @@ export * from './chat.hook'; export * from './chat.dto'; +export * from './chat.atom'; diff --git a/src/features/profile/index.ts b/src/features/profile/index.ts index e342a017d1..6c4e6fb3d8 100644 --- a/src/features/profile/index.ts +++ b/src/features/profile/index.ts @@ -1 +1,2 @@ export * from './profile.hook'; +export * from './profile.dto'; diff --git a/src/features/profile/profile.api.ts b/src/features/profile/profile.api.ts index 2c55463523..f8a342d9ee 100644 --- a/src/features/profile/profile.api.ts +++ b/src/features/profile/profile.api.ts @@ -39,10 +39,7 @@ export const putUserCard = async ( location, features, }) - .then(res => { - console.log(res.data); - return res.data; - }); + .then(res => res.data); export const getFollowingListData = async () => await axios