diff --git a/src/service/feature/common/axios/axiosInstance.ts b/src/service/feature/common/axios/axiosInstance.ts index 8e6faeb..b13feb4 100644 --- a/src/service/feature/common/axios/axiosInstance.ts +++ b/src/service/feature/common/axios/axiosInstance.ts @@ -22,7 +22,6 @@ interface ErrorResponse { const handleAxiosError = (error: AxiosError) => { const { response } = error; - if (!response) { toast.error('네트워크 오류 또는 서버 응답 없음'); return Promise.reject(error); diff --git a/src/service/feature/team/ChatServer.tsx b/src/service/feature/team/ChatServer.tsx deleted file mode 100644 index 37e9e31..0000000 --- a/src/service/feature/team/ChatServer.tsx +++ /dev/null @@ -1,21 +0,0 @@ -interface ChatServerProps { - title: string; - isActive: boolean; - onClick: () => void; -} -export default function ChatServer({ - title, - isActive, - onClick, -}: ChatServerProps) { - return ( - - ); -} diff --git a/src/service/feature/team/api/teamApi.ts b/src/service/feature/team/api/teamApi.ts new file mode 100644 index 0000000..48a9165 --- /dev/null +++ b/src/service/feature/team/api/teamApi.ts @@ -0,0 +1,24 @@ +import axiosInstance from '../../common/axios/axiosInstance'; +import { Team } from '../types/team'; + +export const getTeams = async (): Promise => { + const res = await axiosInstance.get('/teams', { + withCredentials: true, + }); + return res.data.data; +}; + +export const postTeam = async ( + name: string, + iconUrl: string, +): Promise<{ teamId: string }> => { + const res = await axiosInstance.post( + '/teams', + { + name, + iconUrl, + }, + { withCredentials: true }, + ); + return res.data.data; +}; diff --git a/src/service/feature/team/hooks/useTeamSidebar.ts b/src/service/feature/team/hooks/useTeamSidebar.ts new file mode 100644 index 0000000..4cbfa34 --- /dev/null +++ b/src/service/feature/team/hooks/useTeamSidebar.ts @@ -0,0 +1,31 @@ +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { getTeams, postTeam } from '../api/teamApi'; + +// 쿼리키 +const teamKeys = { + all: ['teams'] as const, + lists: () => [...teamKeys.all, 'list'] as const, +}; + +export const useGetTeam = () => { + const { data, isLoading, error } = useQuery({ + queryKey: teamKeys.lists(), + queryFn: getTeams, + staleTime: 10 * 60 * 1000, + gcTime: 60 * 60 * 1000, + }); + + return { data, isLoading, error }; +}; + +export const useCreateTeam = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: ({ name, iconUrl }: { name: string; iconUrl: string }) => + postTeam(name, iconUrl), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: teamKeys.lists() }); + }, + onError: () => {}, + }); +}; diff --git a/src/service/feature/team/types/team.ts b/src/service/feature/team/types/team.ts new file mode 100644 index 0000000..cddc6ff --- /dev/null +++ b/src/service/feature/team/types/team.ts @@ -0,0 +1,6 @@ +export interface Team { + id: string; + name: string; + masterId: string; + iconUrl: string; +} diff --git a/src/view/components/common/Icon.tsx b/src/view/components/common/Icon.tsx index d9ad137..f3aa9fb 100644 --- a/src/view/components/common/Icon.tsx +++ b/src/view/components/common/Icon.tsx @@ -1,11 +1,9 @@ interface IconProps { path: string; className?: string; - width?: number; - height?: number; } -const Icon = ({ path, className, width, height }: IconProps) => { +const Icon = ({ path, className }: IconProps) => { return (
{ alt={`${path} icon`} className='w-full h-full object-contain' src={`/assets/img/${path}.svg`} - width={width} - height={height} />
); diff --git a/src/view/components/layout/LayoutWithSidebar.tsx b/src/view/components/layout/LayoutWithSidebar.tsx index bdbef14..83b11c6 100644 --- a/src/view/components/layout/LayoutWithSidebar.tsx +++ b/src/view/components/layout/LayoutWithSidebar.tsx @@ -1,11 +1,10 @@ import { Outlet, useLocation } from 'react-router-dom'; import UserProfileBar from '@components/layout/UserProfileBar.tsx'; -import TeamSidebar from '@components/layout/sidebar/TeamSidebar.tsx'; +import TeamSidebar from '@components/layout/sidebar/team/TeamSidebar.tsx'; import DirectChannelSidebar from './sidebar/channel/DirectChannelSidebar.tsx'; import ServerChannelSidebar from './sidebar/channel/ServerChannelSidebar.tsx'; const LayoutWithSidebar = () => { - const location = useLocation(); const isDMView = location.pathname.startsWith('/channels/@me'); diff --git a/src/view/components/layout/sidebar/TeamSidebar.tsx b/src/view/components/layout/sidebar/TeamSidebar.tsx deleted file mode 100644 index 48d976e..0000000 --- a/src/view/components/layout/sidebar/TeamSidebar.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import AddServerModal from '@pages/chat/components/AddServerModal'; -import { useNavigate, useParams } from 'react-router-dom'; -import ChatServer from 'src/service/feature/team/ChatServer'; - -const servers = [ - { id: 1, title: '서버1' }, - { id: 2, title: 'server2' }, -]; - -// 대충 넣은 것 -const TeamSidebar = () => { - const params = useParams(); - const channelId = params.serverId; - const navigate = useNavigate(); - - const handleChannel = (server: { id: number; title: string }) => { - console.log('server: ', server); - if (server.id === 0) { - navigate(`/channels/@me`); - } else { - navigate(`/channels/${server.id}`); - } - }; - return ( - //
- // {[1, 2, 3].map((server) => ( - //
- // {server} - //
- // ))} - //
-
- {/* 자기 채널 */} - handleChannel({ id: 0, title: 'me' })} - title={'me'} - /> -
- - {servers.map((server) => ( - handleChannel(server)} - title={server.title} - /> - ))} - {/* 서버 추가하기 */} - -
- ); -}; - -export default TeamSidebar; // src/view/ui/Common/UserProfileBar.tsx diff --git a/src/view/components/layout/sidebar/components/team/ChatServer.tsx b/src/view/components/layout/sidebar/components/team/ChatServer.tsx new file mode 100644 index 0000000..d208614 --- /dev/null +++ b/src/view/components/layout/sidebar/components/team/ChatServer.tsx @@ -0,0 +1,35 @@ +import Icon from '@components/common/Icon'; +import { Team } from '../../../../../../service/feature/team/types/team'; + +interface ChatServerProps { + server?: Team; + isActive: boolean; + onClick: () => void; + isAdd?: boolean; +} +export default function ChatServer({ + isAdd, + server, + isActive, + onClick, +}: ChatServerProps) { + return ( + + ); +} diff --git a/src/view/components/layout/sidebar/team/TeamSidebar.tsx b/src/view/components/layout/sidebar/team/TeamSidebar.tsx new file mode 100644 index 0000000..2a7ef19 --- /dev/null +++ b/src/view/components/layout/sidebar/team/TeamSidebar.tsx @@ -0,0 +1,45 @@ +import AddServerModal from '@pages/chat/components/AddServerModal'; +import { useNavigate, useParams } from 'react-router-dom'; +import ChatServer from '@components/layout/sidebar/components/team/ChatServer'; +import { useGetTeam } from 'src/service/feature/team/hooks/useTeamSidebar'; + +const TeamSidebar = () => { + const params = useParams(); + const channelId = params.serverId; + const navigate = useNavigate(); + + // 팀 목록 불러오기 + const { data: servers } = useGetTeam(); + + const handleChannel = (id: string) => { + if (id === '') { + navigate(`/channels/@me`); + } else { + navigate(`/channels/${id}`); + } + }; + + return ( +
+ {/* 자기 채널 */} + handleChannel('')} + /> +
+ + {servers?.map((server) => ( + handleChannel(server.id)} + server={server} + /> + ))} + {/* 서버 추가하기 */} + +
+ ); +}; + +export default TeamSidebar; diff --git a/src/view/pages/chat/components/AddServerModal.tsx b/src/view/pages/chat/components/AddServerModal.tsx index 6e592f4..ca3349f 100644 --- a/src/view/pages/chat/components/AddServerModal.tsx +++ b/src/view/pages/chat/components/AddServerModal.tsx @@ -1,8 +1,8 @@ import { useRef, useState } from 'react'; import Modal from '@components/common/Modal'; -import ChatServer from '@service/feature/team/ChatServer.tsx'; -import { useCreateTeamMutation } from '@service/feature/team/hook/mutation/useCreateTeamMutation.ts'; -import { toast } from 'sonner'; +import ChatServer from '../../../components/layout/sidebar/components/team/ChatServer.tsx'; +import axiosInstance from 'src/service/feature/common/axios/axiosInstance.ts'; +import { useCreateTeam } from 'src/service/feature/team/hooks/useTeamSidebar.ts'; export default function AddServerModal() { const inputRef = useRef(null); @@ -10,32 +10,66 @@ export default function AddServerModal() { const [name, setName] = useState('누구님의 서버'); const [file, setFile] = useState(null); - const createTeamMutation = useCreateTeamMutation(); + const { mutate } = useCreateTeam(); + + console.log('서버 추가 보이냐'); + + const inputRef = useRef(null); + const fileRef = useRef(null); // 이미지 실제 파일 + + const handleClickAddServer = () => {}; + + const handleSubmit = async () => { + // 백엔드에서 multipart/form-data를 받을 수 있어야 해. + if (!name.trim()) { + alert('서버 이름을 입력해주세요.'); + return; + } + + try { + // 이미지가 있는 경우, 먼저 업로드 + let iconUrl = ''; + const formData = new FormData(); + + if (fileRef.current) { + formData.append('file', fileRef.current); - const handleChangeFile = (e: React.ChangeEvent) => { + const res = await axiosInstance.post('/images', formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); + iconUrl = res.data.message; + } + mutate({ name, iconUrl }); + + // TODO: 성공 시 모달 닫거나 페이지 이동 등의 후속 처리 + } catch (error) { + console.error('서버 생성 실패', error); + alert('서버 생성에 실패했습니다.'); + } + }; + + // 이미지 변경 + const handleChange = (e: React.ChangeEvent) => { + const createTeamMutation = useCreateTeamMutation(); const file = e.target.files?.[0]; if (file) { setFile(file); const url = URL.createObjectURL(file); setPreview(url); + fileRef.current = file; } }; - const handleClickAddServer = () => { + const handleClick = () => { inputRef.current?.click(); }; - const handleSubmit = () => { - if (!name.trim()) { - return toast.error('서버 이름을 입력해주세요.'); - } - createTeamMutation.mutate({ name, file: file ?? undefined }); - }; - return ( - +