Skip to content

Commit

Permalink
refactor: organisms 정리
Browse files Browse the repository at this point in the history
  • Loading branch information
GihoKo committed Jul 14, 2024
1 parent bb16812 commit 88ca959
Show file tree
Hide file tree
Showing 18 changed files with 399 additions and 241 deletions.
11 changes: 11 additions & 0 deletions src/components/Organisms/Content/ChannelContainer.hook.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import useGetAllChannels from '@/apis/hooks/useGetAllChannels';

export default function useChannelContainer() {
const { data: channels, isLoading, isError } = useGetAllChannels();

return {
channels,
isLoading,
isError,
};
}
31 changes: 14 additions & 17 deletions src/components/Organisms/Content/ChannelContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,29 @@
// libraries
import styled from 'styled-components';

// hooks
import useChannelContainer from './ChannelContainer.hook';

// components
import ChannelItem from './ChannelItem';
import { useEffect, useState } from 'react';
import { Channel } from '../../../types/channel';
import MainTitle from '../../Atoms/Text/MainTitle';
import useGetAllChannels from '../../../apis/hooks/useGetAllChannels';

export default function ChannelContainer() {
const [channels, setChannels] = useState<Channel[] | null>(null);
const { data } = useGetAllChannels();
// logics
const { channels, isLoading, isError } = useChannelContainer();

useEffect(() => {
if (data) {
setChannels(data);
}
}, [data]);

if (!channels) {
// view
if (isLoading) {
return <div>Loading...</div>;
}
if (isError) {
return <div>Error...</div>;
}

return (
<Wrapper>
<MainTitle>Channel List</MainTitle>
<Container>
{channels.map((channel) => (
<ChannelItem key={channel.id} channel={channel} />
))}
</Container>
<Container>{channels?.map((channel) => <ChannelItem key={channel.id} channel={channel} />)}</Container>
</Wrapper>
);
}
Expand Down
14 changes: 8 additions & 6 deletions src/components/Organisms/Content/ChannelItem.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
// libraries
import styled from 'styled-components';
import { Channel } from '../../../types/channel';

// components
import { Link } from 'react-router-dom';
import TagContainer from '../../Molecules/Content/TagContainer';

// types
import { ChannelItemProps } from './ChannelItem.type';

// images
import mockImage from '../../../images/dummyImage.png';
import personSvg from '../../../images/svg/person.svg';
import TagContainer from '../../Molecules/Content/TagContainer';

interface ChannelItemProps {
channel: Channel;
}

export default function ChannelItem({ channel }: ChannelItemProps) {
return (
Expand Down
5 changes: 5 additions & 0 deletions src/components/Organisms/Content/ChannelItem.type.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Channel } from '@/types/channel';

export interface ChannelItemProps {
channel: Channel;
}
133 changes: 133 additions & 0 deletions src/components/Organisms/Modal/CreateChannelModal.hook.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// hooks
import { useRef, useState } from 'react';
import { useUserStore } from '@/store/useUserStore';
import useCreateChannel from '@/apis/hooks/useCreateChannel';
import useModalStore from '@/store/useModalStore';

// types
import { ModalType } from '@/types/enum';

export default function useCreateChannelModal() {
const { type, closeModal } = useModalStore();

if (type !== ModalType.CREATE_CHANNEL) return null;

const { user } = useUserStore();
const [channelData, setChannelData] = useState({
name: '',
tags: [] as string[],
description: '',
image: '',
});
const [tagValue, setTagValue] = useState<string>('');
const [previewChannelImageUrl, setPreviewChannelImageUrl] = useState<string | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
const [profileImageFile, setProfileImageFile] = useState<File | null>(null);
const uploadCreateChannel = useCreateChannel();

const handleChannelImageClick = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
e.preventDefault();
if (fileInputRef.current) {
fileInputRef.current.click();
}
};

const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
// 이전 파일 제거
setPreviewChannelImageUrl(null);
setProfileImageFile(null);

if (!e.target.files) return;
const file = e.target.files?.[0];
setProfileImageFile(file);

// 파일 미리보기
const fileUrl = URL.createObjectURL(file);
setPreviewChannelImageUrl(fileUrl);
};

const handleAddTagKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
e.preventDefault();
if (tagValue.trim() === '') {
return console.log('태그를 입력하세요.');
}
if (tagValue.length > 10) {
return console.log('태그는 10자 이내로 입력하세요.');
}
if (channelData.tags.length > 5) {
return console.log('태그는 5개까지만 입력할 수 있습니다.');
}
if (channelData.tags.includes(tagValue)) {
return console.log('이미 입력된 태그입니다.');
}

setChannelData({
...channelData,
tags: [...channelData.tags, tagValue],
});
setTagValue('');
}
};

const handleDeleteTag = (tag: string) => {
setChannelData({
...channelData,
tags: channelData.tags.filter((t) => t !== tag),
});
};

const handleChangeChannelData = (e: React.ChangeEvent<HTMLInputElement>) => {
setChannelData({
...channelData,
[e.target.name]: e.target.value,
});
};

const handleChangeTagValue = (e: React.ChangeEvent<HTMLInputElement>) => {
setTagValue(e.target.value);
};

const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
createChannel();
closeModal();
setChannelData({
name: '',
tags: [],
description: '',
image: '',
});
};

const createChannel = async () => {
const newChannel = new FormData();
if (channelData) {
newChannel.append('name', channelData.name);
// FormData의 경우 배열을 받을 수 없기 때문에 JSON.stringify를 사용하여 문자열로 변환하여 전송
// 이후 서버에서 JSON.parse를 통해 배열로 변환하고 사용
newChannel.append('tags', JSON.stringify(channelData.tags));
newChannel.append('description', channelData.description);
newChannel.append('ownerId', user?.id || '');
}
if (profileImageFile) {
newChannel.append('image', profileImageFile);
}
uploadCreateChannel.mutate({ channel: newChannel });
};

return {
channelData,
tagValue,
previewChannelImageUrl,
fileInputRef,
handleChangeChannelData,
handleChangeTagValue,
handleChannelImageClick,
handleFileChange,
handleAddTagKeyDown,
handleDeleteTag,
handleSubmit,
closeModal,
};
}
143 changes: 29 additions & 114 deletions src/components/Organisms/Modal/CreateChannelModal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
// libraries
import styled from 'styled-components';

// hooks
import useCreateChannelModal from './CreateChannelModal.hook';

// components
import Dimmed from '../../Atoms/Modal/Dimmed';
import Button from '../../Atoms/Modal/Button';
import { useRef, useState } from 'react';
import {
Wrapper,
Form,
Expand All @@ -13,121 +19,30 @@ import {
TagContainer,
Tag,
} from '../../Atoms/Modal/StyledComponents';
import { useUserStore } from '../../../store/useUserStore';
import useModalStore from '../../../store/useModalStore';
import { ModalType } from '../../../types/enum';
import styled from 'styled-components';
import useCreateChannel from '../../../apis/hooks/useCreateChannel';

export default function CreateChannelModal() {
const { type, closeModal } = useModalStore();

if (type !== ModalType.CREATE_CHANNEL) return null;

const { user } = useUserStore();
const [channelData, setChannelData] = useState({
name: '',
tags: [] as string[],
description: '',
image: '',
});
const [tagValue, setTagValue] = useState<string>('');
const [previewChannelImageUrl, setPreviewChannelImageUrl] = useState<string | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
const [profileImageFile, setProfileImageFile] = useState<File | null>(null);
const uploadCreateChannel = useCreateChannel();

const handleChannelImageClick = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
e.preventDefault();
if (fileInputRef.current) {
fileInputRef.current.click();
}
};

const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
// 이전 파일 제거
setPreviewChannelImageUrl(null);
setProfileImageFile(null);

if (!e.target.files) return;
const file = e.target.files?.[0];
setProfileImageFile(file);

// 파일 미리보기
const fileUrl = URL.createObjectURL(file);
setPreviewChannelImageUrl(fileUrl);
};

const handleAddTagKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
e.preventDefault();
if (tagValue.trim() === '') {
return console.log('태그를 입력하세요.');
}
if (tagValue.length > 10) {
return console.log('태그는 10자 이내로 입력하세요.');
}
if (channelData.tags.length > 5) {
return console.log('태그는 5개까지만 입력할 수 있습니다.');
}
if (channelData.tags.includes(tagValue)) {
return console.log('이미 입력된 태그입니다.');
}

setChannelData({
...channelData,
tags: [...channelData.tags, tagValue],
});
setTagValue('');
}
};

const handleDeleteTag = (tag: string) => {
setChannelData({
...channelData,
tags: channelData.tags.filter((t) => t !== tag),
});
};

const handleChangeChannelData = (e: React.ChangeEvent<HTMLInputElement>) => {
setChannelData({
...channelData,
[e.target.name]: e.target.value,
});
};

const handleChangeTagValue = (e: React.ChangeEvent<HTMLInputElement>) => {
setTagValue(e.target.value);
};

const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
createChannel();
closeModal();
setChannelData({
name: '',
tags: [],
description: '',
image: '',
});
};

const createChannel = async () => {
const newChannel = new FormData();
if (channelData) {
newChannel.append('name', channelData.name);
// FormData의 경우 배열을 받을 수 없기 때문에 JSON.stringify를 사용하여 문자열로 변환하여 전송
// 이후 서버에서 JSON.parse를 통해 배열로 변환하고 사용
newChannel.append('tags', JSON.stringify(channelData.tags));
newChannel.append('description', channelData.description);
newChannel.append('ownerId', user?.id || '');
}
if (profileImageFile) {
newChannel.append('image', profileImageFile);
}
uploadCreateChannel.mutate({ channel: newChannel });
};

// logics
const hookData = useCreateChannelModal();

// useCreateChannelModal hook에서 null을 반환할 수 있으므로 null 체크
if (!hookData) return null;

const {
channelData,
tagValue,
previewChannelImageUrl,
fileInputRef,
handleChannelImageClick,
handleFileChange,
handleChangeChannelData,
handleChangeTagValue,
handleAddTagKeyDown,
handleDeleteTag,
handleSubmit,
closeModal,
} = hookData;

// view
return (
<Dimmed>
<Wrapper>
Expand Down
Loading

0 comments on commit 88ca959

Please sign in to comment.