Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/service/feature/common/axios/axiosInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ interface ErrorResponse {

const handleAxiosError = (error: AxiosError<ErrorResponse>) => {
const { response } = error;

if (!response) {
toast.error('네트워크 오류 또는 서버 응답 없음');
return Promise.reject(error);
Expand Down
21 changes: 0 additions & 21 deletions src/service/feature/team/ChatServer.tsx

This file was deleted.

24 changes: 24 additions & 0 deletions src/service/feature/team/api/teamApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import axiosInstance from '../../common/axios/axiosInstance';
import { Team } from '../types/team';

export const getTeams = async (): Promise<Team[]> => {
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;
};
31 changes: 31 additions & 0 deletions src/service/feature/team/hooks/useTeamSidebar.ts
Original file line number Diff line number Diff line change
@@ -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: () => {},
});
};
6 changes: 6 additions & 0 deletions src/service/feature/team/types/team.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface Team {
id: string;
name: string;
masterId: string;
iconUrl: string;
}
6 changes: 1 addition & 5 deletions src/view/components/common/Icon.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div
className={`w-full h-full flex items-center justify-center ${className}`}
Expand All @@ -14,8 +12,6 @@ const Icon = ({ path, className, width, height }: IconProps) => {
alt={`${path} icon`}
className='w-full h-full object-contain'
src={`/assets/img/${path}.svg`}
width={width}
height={height}
/>
</div>
);
Expand Down
3 changes: 1 addition & 2 deletions src/view/components/layout/LayoutWithSidebar.tsx
Original file line number Diff line number Diff line change
@@ -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');

Expand Down
58 changes: 0 additions & 58 deletions src/view/components/layout/sidebar/TeamSidebar.tsx

This file was deleted.

35 changes: 35 additions & 0 deletions src/view/components/layout/sidebar/components/team/ChatServer.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<button
className={`flex rounded-[13px] w-[48px] h-[48px] mt-[6px] ${isActive ? 'bg-primary' : 'bg-chat'} text-des justify-center items-center overflow-hidden hover:bg-primary transition-colors duration-300 ease-in-out
`}
onClick={onClick}
type='button'
>
{isAdd ? (
<Icon path='plus' className='!w-5 !h-5' />
) : (
!server && <div className='text-center text-lg'>me</div>
)}
{server?.iconUrl ? (
<img src={server.iconUrl} alt={server.name} />
) : (
<div className='text-center text-lg'>{server?.name}</div>
)}
</button>
);
}
45 changes: 45 additions & 0 deletions src/view/components/layout/sidebar/team/TeamSidebar.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className='wrapper flex'>
{/* 자기 채널 */}
<ChatServer
isActive={channelId === '@me'}
onClick={() => handleChannel('')}
/>
<div className='border w-[48px] h-[1px] border-[#42454A]' />

{servers?.map((server) => (
<ChatServer
isActive={channelId === String(server.id)}
key={server.id}
onClick={() => handleChannel(server.id)}
server={server}
/>
))}
{/* 서버 추가하기 */}
<AddServerModal />
</div>
);
};

export default TeamSidebar;
62 changes: 48 additions & 14 deletions src/view/pages/chat/components/AddServerModal.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,75 @@
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<HTMLInputElement | null>(null);
const [preview, setPreview] = useState<string | null>(null);
const [name, setName] = useState<string>('누구님의 서버');
const [file, setFile] = useState<File | null>(null);

const createTeamMutation = useCreateTeamMutation();
const { mutate } = useCreateTeam();

console.log('서버 추가 보이냐');

const inputRef = useRef<HTMLInputElement | null>(null);
const fileRef = useRef<File | null>(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<HTMLInputElement>) => {
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<HTMLInputElement>) => {
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 (
<Modal.Root>
<Modal.Trigger>
<ChatServer isActive={false} onClick={handleClickAddServer} title="추가" />
<ChatServer isActive={false} onClick={handleClickAddServer} isAdd />
</Modal.Trigger>
<Modal.Portal>
<Modal.Overlay />
Expand Down