diff --git a/.gitignore b/.gitignore index 4e8fb424..b2078be9 100644 --- a/.gitignore +++ b/.gitignore @@ -67,4 +67,6 @@ src/backend/**/generated/ src/backend/**/http/http-client.private.env.json # docker-compose env -src/backend/.env \ No newline at end of file +src/backend/.env + +cert/* \ No newline at end of file diff --git a/src/frontend/package.json b/src/frontend/package.json index 3aced719..eefc67b5 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -14,6 +14,7 @@ "test": "jest" }, "dependencies": { + "@stomp/stompjs": "^7.0.0", "@tanstack/react-query": "^5.66.0", "@tanstack/react-query-devtools": "^5.66.0", "axios": "^1.7.9", @@ -24,6 +25,8 @@ "react-icons": "^5.4.0", "react-router-dom": "^7.1.5", "styled-components": "^6.1.14", + "vite-plugin-mkcert": "^1.17.6", + "websocket": "^1.0.35", "zustand": "^5.0.3" }, "devDependencies": { diff --git a/src/frontend/src/components/friend/AddFriendForm/index.tsx b/src/frontend/src/components/friend/AddFriendForm/index.tsx new file mode 100644 index 00000000..7b10f893 --- /dev/null +++ b/src/frontend/src/components/friend/AddFriendForm/index.tsx @@ -0,0 +1,44 @@ +import { useState } from 'react'; + +import * as S from './styles'; + +export type ResultMessageType = 'success' | 'fail'; + +interface ResultMessage { + type: ResultMessageType; + content: string; +} + +const AddFriendForm = () => { + // TODO: 친구 검색 및 요청 보내기 로직 연동 + // TODO: 요청 보내기 결과에 따라 resultMessage를 설정하고 표시 + const [inputData, setInputData] = useState(''); + const [resultMessage, setResultMessage] = useState(null); + + const handleInputChange = (value: string) => { + setInputData(value); + }; + + const handleSendRequestButtonClick = () => { + console.log('친구 요청'); + }; + + return ( + + 친구 추가하기 + AsyncGate 이메일을 사용하여 친구를 추가할 수 있어요 + + handleInputChange(e.target.value)} + placeholder="AsyncGate 이메일을 사용하여 친구를 추가할 수 있어요." + /> + + 친구 요청 보내기 + + + {resultMessage && {resultMessage.content}} + + ); +}; + +export default AddFriendForm; diff --git a/src/frontend/src/components/friend/AddFriendForm/styles.ts b/src/frontend/src/components/friend/AddFriendForm/styles.ts new file mode 100644 index 00000000..1f0f0c37 --- /dev/null +++ b/src/frontend/src/components/friend/AddFriendForm/styles.ts @@ -0,0 +1,64 @@ +import styled from 'styled-components'; + +import { BodyRegularText, ChipText, TitleText2 } from '@/styles/Typography'; + +import { ResultMessageType } from '.'; + +export const AddFriendFormContainer = styled.div` + display: flex; + flex-direction: column; + width: 100%; + padding: 2rem 3rem; +`; + +export const FormTitle = styled(TitleText2)` + color: ${({ theme }) => theme.colors.white}; +`; + +export const FormSubtitle = styled(BodyRegularText)` + color: ${({ theme }) => theme.colors.dark[300]}; +`; + +export const FriendSearchInputWrapper = styled.div` + display: flex; + align-items: center; + + width: 100%; + height: 5rem; + margin-top: 1.6rem; + padding: 0 1.2rem; + border-radius: 0.8rem; + + background-color: ${({ theme }) => theme.colors.dark[800]}; +`; + +export const FriendSearchInput = styled.input` + display: flex; + flex-grow: 1; + align-items: center; + + height: 4rem; + border: none; + + color: ${({ theme }) => theme.colors.white}; + + background-color: ${({ theme }) => theme.colors.dark[800]}; + outline: none; +`; + +export const SendRequestButton = styled.button<{ $isDisabled: boolean }>` + cursor: ${({ $isDisabled }) => ($isDisabled ? 'not-allowed' : 'pointer')}; + + height: 3.2rem; + padding: 0.2rem 1.6rem; + border-radius: 0.4rem; + + font-weight: 600; + color: ${({ theme }) => theme.colors.dark[300]}; + + background-color: ${({ theme }) => theme.colors.blue}; +`; + +export const ResultMessage = styled(BodyRegularText)<{ $type: ResultMessageType }>` + color: ${({ theme, $type }) => ($type === 'success' ? theme.colors.green : theme.colors.red)}; +`; diff --git a/src/frontend/src/components/friend/DirectMessageCategory/index.tsx b/src/frontend/src/components/friend/DirectMessageCategory/index.tsx new file mode 100644 index 00000000..c964346e --- /dev/null +++ b/src/frontend/src/components/friend/DirectMessageCategory/index.tsx @@ -0,0 +1,14 @@ +import * as S from './styles'; + +const DirectMessageCategory = () => { + return ( + <> + + 다이렉트 메시지 + + {/* TODO: 친구 채팅방 목록 */} + + ); +}; + +export default DirectMessageCategory; diff --git a/src/frontend/src/components/friend/DirectMessageCategory/styles.ts b/src/frontend/src/components/friend/DirectMessageCategory/styles.ts new file mode 100644 index 00000000..2ee1f2f1 --- /dev/null +++ b/src/frontend/src/components/friend/DirectMessageCategory/styles.ts @@ -0,0 +1,18 @@ +import styled from 'styled-components'; + +import { TitleText2 } from '@/styles/Typography'; + +export const FriendTitle = styled.div` + display: flex; + align-items: center; + + height: 4.8rem; + padding: 1.2rem 1.6rem; + border-bottom: 0.1rem solid ${({ theme }) => theme.colors.dark[500]}; + + color: ${({ theme }) => theme.colors.dark[300]}; +`; + +export const TitleName = styled(TitleText2)` + color: ${({ theme }) => theme.colors.dark[300]}; +`; diff --git a/src/frontend/src/components/friend/DirectMessageChattingRoom/index.tsx b/src/frontend/src/components/friend/DirectMessageChattingRoom/index.tsx new file mode 100644 index 00000000..c22926f7 --- /dev/null +++ b/src/frontend/src/components/friend/DirectMessageChattingRoom/index.tsx @@ -0,0 +1,35 @@ +import { BiHash } from 'react-icons/bi'; +import { FaInbox } from 'react-icons/fa'; + +import MessageSection from '@/pages/FriendsPage/components/ChattingSection/components/MessageSection'; +import { useChannelInfoStore } from '@/stores/channelInfo'; + +import * as S from './styles'; + +// 디스코드에서는 채널 타입 아이콘 대신 상대방 프로필 사진이 뜸 +// 우선은 프로필 사진 자리에 아이콘 배치 +const DirectMessageChattingRoom = () => { + const { selectedDMChannel } = useChannelInfoStore(); + if (!selectedDMChannel) return null; + + return ( + <> + + + + + + {selectedDMChannel.name} + + + + + + + + + + ); +}; + +export default DirectMessageChattingRoom; diff --git a/src/frontend/src/components/friend/DirectMessageChattingRoom/styles.ts b/src/frontend/src/components/friend/DirectMessageChattingRoom/styles.ts new file mode 100644 index 00000000..7c516e93 --- /dev/null +++ b/src/frontend/src/components/friend/DirectMessageChattingRoom/styles.ts @@ -0,0 +1,37 @@ +import styled from 'styled-components'; + +import { BodyMediumText } from '@/styles/Typography'; + +export const ChattingHeader = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + + height: 4.8rem; + padding: 0.8rem; + border-bottom: 0.2rem solid ${({ theme }) => theme.colors.dark[700]}; + + color: ${({ theme }) => theme.colors.dark[300]}; +`; + +export const ChannelInfo = styled.div` + display: flex; + align-items: center; +`; + +export const ChannelImage = styled.div` + margin: 0 0.8rem; +`; + +export const ChannelTitle = styled(BodyMediumText)` + color: ${({ theme }) => theme.colors.dark[300]}; +`; + +export const ToolBar = styled.div` + display: flex; +`; + +export const IconWrapper = styled.div` + cursor: pointer; + margin: 0 0.8rem; +`; diff --git a/src/frontend/src/components/friend/FriendsList/index.tsx b/src/frontend/src/components/friend/FriendsList/index.tsx new file mode 100644 index 00000000..a024b694 --- /dev/null +++ b/src/frontend/src/components/friend/FriendsList/index.tsx @@ -0,0 +1,45 @@ +import * as S from './styles'; + +// TODO: API 문서에 맞게 수정 및 types 폴더로 이동 +export interface Friend { + name: string; + profileImageUrl: string; + isOnline: boolean; +} + +// TODO: useQuery로 친구 리스트 요청 +const friends: Friend[] = [ + { + name: '친구1', + profileImageUrl: '', + isOnline: true, + }, + { + name: '친구2', + profileImageUrl: '', + isOnline: true, + }, + { + name: '친구3', + profileImageUrl: '', + isOnline: false, + }, +]; + +const FriendsList = () => { + return ( + + 모든 친구 - {friends.length}명 + {friends.map((friend, index) => ( + + + + + {friend.name} + + ))} + + ); +}; + +export default FriendsList; diff --git a/src/frontend/src/components/friend/FriendsList/styles.ts b/src/frontend/src/components/friend/FriendsList/styles.ts new file mode 100644 index 00000000..ec2bb36f --- /dev/null +++ b/src/frontend/src/components/friend/FriendsList/styles.ts @@ -0,0 +1,72 @@ +import styled from 'styled-components'; + +import { BodyMediumText, ChipText } from '@/styles/Typography'; + +export const FriendsList = styled.ul` + display: flex; + flex-direction: column; + + width: 100%; + margin-top: 0.8rem; + padding: 0 2rem 0 3rem; + + color: ${({ theme }) => theme.colors.dark[300]}; +`; + +export const FriendCount = styled(ChipText)` + margin: 1.6rem 0 1rem; + color: ${({ theme }) => theme.colors.dark[300]}; +`; + +export const FriendItem = styled.li` + cursor: pointer; + + display: flex; + align-items: center; + + height: 6.2rem; + border-top: 0.1rem solid ${({ theme }) => theme.colors.dark[500]}; + border-radius: 0.4rem; + + &:hover { + background-color: ${({ theme }) => theme.colors.dark[500]}; + } +`; + +export const FriendProfileImage = styled.div<{ $imageUrl: string }>` + position: relative; + + width: 3.2rem; + height: 3.2rem; + border-radius: 50%; + + background-color: ${({ theme }) => theme.colors.white}; + background-image: url(${(props) => props.$imageUrl}); +`; + +export const FriendStatusMark = styled.div<{ $isOnline: boolean }>` + position: absolute; + right: 0; + bottom: 0; + + width: 1rem; + height: 1rem; + border: 0.1rem solid ${({ theme }) => theme.colors.dark[400]}; + border-radius: 50%; + + background-color: ${({ $isOnline }) => ($isOnline ? 'green' : 'black')}; +`; + +export const FriendName = styled(BodyMediumText)` + overflow: hidden; + + padding-left: 0.8rem; + + color: ${({ theme }) => theme.colors.white}; + text-overflow: ellipsis; + white-space: nowrap; +`; + +export const FriendStatus = styled(ChipText)` + color: ${({ theme }) => theme.colors.dark[300]}; +`; diff --git a/src/frontend/src/components/friend/FriendsManagement/index.tsx b/src/frontend/src/components/friend/FriendsManagement/index.tsx new file mode 100644 index 00000000..c8a473f1 --- /dev/null +++ b/src/frontend/src/components/friend/FriendsManagement/index.tsx @@ -0,0 +1,58 @@ +import { useState } from 'react'; +import { FaInbox } from 'react-icons/fa'; +import { MdPerson } from 'react-icons/md'; + +import AddFriendForm from '../AddFriendForm'; +import FriendsList from '../FriendsList'; +import PendingFriendsList from '../PendingFriendsList'; + +import * as S from './styles'; + +type NavBarMenu = 'all' | 'pending' | 'addFriend'; + +const FriendsManagement = () => { + const [selectedMenu, setSelectedMenu] = useState('all'); + + const handleMenuClick = (value: NavBarMenu) => { + setSelectedMenu(value); + }; + + return ( + <> + + + + + + 친구 + | + + + handleMenuClick('all')}> + 모두 + + handleMenuClick('pending')}> + 대기 중 + + handleMenuClick('addFriend')}> + 친구 추가하기 + + + + + + + + + + + + {selectedMenu === 'all' && } + {selectedMenu === 'pending' && } + {selectedMenu === 'addFriend' && } + + + ); +}; + +export default FriendsManagement; diff --git a/src/frontend/src/components/friend/FriendsManagement/styles.ts b/src/frontend/src/components/friend/FriendsManagement/styles.ts new file mode 100644 index 00000000..48a552ef --- /dev/null +++ b/src/frontend/src/components/friend/FriendsManagement/styles.ts @@ -0,0 +1,76 @@ +import styled from 'styled-components'; + +import { BodyMediumText } from '@/styles/Typography'; + +export const SectionHeader = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + + height: 4.8rem; + padding: 0.8rem; + border-bottom: 0.2rem solid ${({ theme }) => theme.colors.dark[700]}; + + color: ${({ theme }) => theme.colors.dark[300]}; +`; + +export const SectionInfo = styled.div` + display: flex; + align-items: center; +`; + +export const SectionIcon = styled.div` + margin: 0 0.8rem; +`; + +export const SectionTitle = styled(BodyMediumText)` + color: ${({ theme }) => theme.colors.dark[300]}; +`; + +export const Divider = styled.div` + margin: 0 1rem; + font-size: 2rem; + font-weight: 400; +`; + +export const FriendNavBarWrapper = styled.nav` + display: flex; +`; + +export const MenuContainer = styled.ul` + display: flex; + list-style: none; +`; + +export const MenuItem = styled.li<{ $isSelectedMenu: boolean }>` + cursor: pointer; + + margin: 0 0.7rem; + padding: 1rem; + border-radius: 0.4rem; + + font-size: 1.6rem; + font-weight: 500; + color: ${({ theme, $isSelectedMenu }) => ($isSelectedMenu ? theme.colors.white : theme.colors.dark[300])}; + text-decoration: none; + + background-color: ${({ theme, $isSelectedMenu }) => ($isSelectedMenu ? theme.colors.dark[500] : '')}; + + &:hover { + background-color: ${({ theme }) => theme.colors.dark[500]}; + } +`; + +export const ToolBar = styled.div` + display: flex; +`; + +export const IconWrapper = styled.div` + cursor: pointer; + margin: 0 0.8rem; +`; + +export const SectionContentsWrapper = styled.div` + display: flex; + width: 100%; +`; diff --git a/src/frontend/src/components/friend/PendingFriendsList/index.tsx b/src/frontend/src/components/friend/PendingFriendsList/index.tsx new file mode 100644 index 00000000..20cbcfb2 --- /dev/null +++ b/src/frontend/src/components/friend/PendingFriendsList/index.tsx @@ -0,0 +1,53 @@ +import { Friend } from '../FriendsList'; + +import * as S from './styles'; + +const friends: Friend[] = [ + { + name: '친구1', + profileImageUrl: '', + isOnline: true, + }, + { + name: '친구2', + profileImageUrl: '', + isOnline: true, + }, +]; + +const PendingFriendsList = () => { + // TODO: 받은 친구 및 보낸 친구 요청 리스트 보여주기 + // TODO: 받은 친구 요청인 경우 수락 및 거절 로직 구현 + // TODO: 보낸 친구 요청인 경우 요청 취소 로직 구현 + // 현재는 받은 친구 요청 UI만 구현 + + const handleAcceptButtonClick = () => { + console.log('친구 요청 수락'); + }; + + const handleRejectButtonClick = () => { + console.log('친구 요청 거절'); + }; + + return ( + + 대기 중인 친구 - {friends.length}명 + {friends.map((friend, index) => ( + + + + + + {friend.name} + + + 수락하기 + 거절하기 + + + ))} + + ); +}; + +export default PendingFriendsList; diff --git a/src/frontend/src/components/friend/PendingFriendsList/styles.ts b/src/frontend/src/components/friend/PendingFriendsList/styles.ts new file mode 100644 index 00000000..afa85081 --- /dev/null +++ b/src/frontend/src/components/friend/PendingFriendsList/styles.ts @@ -0,0 +1,105 @@ +import styled from 'styled-components'; + +import { BodyMediumText, ChipText } from '@/styles/Typography'; + +export const FriendsList = styled.ul` + display: flex; + flex-direction: column; + + width: 100%; + margin-top: 0.8rem; + padding: 0 2rem 0 3rem; + + color: ${({ theme }) => theme.colors.dark[300]}; +`; + +export const FriendCount = styled(ChipText)` + margin: 1.6rem 0 1rem; + color: ${({ theme }) => theme.colors.dark[300]}; +`; + +export const FriendItem = styled.li` + cursor: pointer; + + display: flex; + align-items: center; + justify-content: space-between; + + height: 6.2rem; + border-top: 0.1rem solid ${({ theme }) => theme.colors.dark[500]}; + border-radius: 0.4rem; + + &:hover { + background-color: ${({ theme }) => theme.colors.dark[500]}; + } +`; + +export const FriendInfoContainer = styled.div` + display: flex; + align-items: center; +`; + +export const FriendProfileImage = styled.div<{ $imageUrl: string }>` + position: relative; + + width: 3.2rem; + height: 3.2rem; + border-radius: 50%; + + background-color: ${({ theme }) => theme.colors.white}; + background-image: url(${(props) => props.$imageUrl}); +`; + +export const FriendStatusMark = styled.div<{ $isOnline: boolean }>` + position: absolute; + right: 0; + bottom: 0; + + width: 1rem; + height: 1rem; + border: 0.1rem solid ${({ theme }) => theme.colors.dark[400]}; + border-radius: 50%; + + background-color: ${({ $isOnline }) => ($isOnline ? 'green' : 'black')}; +`; + +export const FriendName = styled(BodyMediumText)` + overflow: hidden; + + padding-left: 0.8rem; + + color: ${({ theme }) => theme.colors.white}; + text-overflow: ellipsis; + white-space: nowrap; +`; + +export const FriendStatus = styled(ChipText)` + color: ${({ theme }) => theme.colors.dark[300]}; +`; + +export const ButtonContainer = styled.div` + display: flex; + gap: 0.5rem; +`; + +export const AcceptButton = styled.button` + width: 8rem; + height: 3.2rem; + border-radius: 0.4rem; + + font-weight: 500; + color: ${({ theme }) => theme.colors.white}; + + background-color: ${({ theme }) => theme.colors.green}; +`; + +export const RejectButton = styled.button` + width: 8rem; + height: 3.2rem; + border-radius: 0.4rem; + + font-weight: 500; + color: ${({ theme }) => theme.colors.white}; + + background-color: ${({ theme }) => theme.colors.red}; +`; diff --git a/src/frontend/src/components/guild/CategoriesList/index.tsx b/src/frontend/src/components/guild/GuildCategoriesList/index.tsx similarity index 62% rename from src/frontend/src/components/guild/CategoriesList/index.tsx rename to src/frontend/src/components/guild/GuildCategoriesList/index.tsx index 1e662cc0..8f5afb09 100644 --- a/src/frontend/src/components/guild/CategoriesList/index.tsx +++ b/src/frontend/src/components/guild/GuildCategoriesList/index.tsx @@ -2,10 +2,11 @@ import { BiHash } from 'react-icons/bi'; import { BsFillMicFill } from 'react-icons/bs'; import { TbPlus } from 'react-icons/tb'; +import { GuildChannelInfo, useChannelInfoStore } from '@/stores/channelInfo'; import { useGuildInfoStore } from '@/stores/guildInfo'; import useModalStore from '@/stores/modalStore'; import { BodyMediumText, BodyRegularText } from '@/styles/Typography'; -import { CategoryDataResult, ChannelResult } from '@/types/guilds'; +import { CategoryDataResult, ChannelResult, ChannelType } from '@/types/guilds'; import CreateChannelModal from '../CreateChannelModal'; @@ -15,13 +16,19 @@ export interface CategoriesListProps { categories?: CategoryDataResult[]; channels?: ChannelResult[]; } -const CategoriesList = ({ categories, channels }: CategoriesListProps) => { +const GuildCategoriesList = ({ categories, channels }: CategoriesListProps) => { const { openModal } = useModalStore(); const { guildId } = useGuildInfoStore(); + const { selectedChannel, setSelectedChannel } = useChannelInfoStore(); + const handleOpenModal = (categoryId: string, guildId: string) => { openModal('withFooter', ); }; + const handleChannelClick = (channelInfo: GuildChannelInfo) => { + setSelectedChannel({ id: channelInfo.id, name: channelInfo.name, type: channelInfo.type }); + }; + return ( {categories?.map((category) => ( @@ -34,7 +41,17 @@ const CategoriesList = ({ categories, channels }: CategoriesListProps) => { {channels ?.filter((channel) => category.categoryId === channel.categoryId) .map((channel) => ( - + + handleChannelClick({ + id: channel.channelId, + name: channel.name, + type: channel.channelType as ChannelType, + }) + } + $isSelected={selectedChannel?.id === channel.channelId} + > {channel.channelType === 'TEXT' ? : } {channel.name} @@ -46,4 +63,4 @@ const CategoriesList = ({ categories, channels }: CategoriesListProps) => { ); }; -export default CategoriesList; +export default GuildCategoriesList; diff --git a/src/frontend/src/components/guild/CategoriesList/styles.ts b/src/frontend/src/components/guild/GuildCategoriesList/styles.ts similarity index 60% rename from src/frontend/src/components/guild/CategoriesList/styles.ts rename to src/frontend/src/components/guild/GuildCategoriesList/styles.ts index 7f307b3f..553a0d6a 100644 --- a/src/frontend/src/components/guild/CategoriesList/styles.ts +++ b/src/frontend/src/components/guild/GuildCategoriesList/styles.ts @@ -6,6 +6,7 @@ export const CategoriesList = styled.div` gap: 2rem; margin-top: 1.5rem; + padding: 0 1.6rem; color: ${({ theme }) => theme.colors.dark[300]}; `; @@ -27,21 +28,37 @@ export const CategoryName = styled.div` svg { color: ${({ theme }) => theme.colors.dark[300]}; } + + &:hover { + * { + color: ${({ theme }) => theme.colors.white}; + } + } `; export const Channels = styled.div` display: flex; flex-direction: column; - gap: 0.6rem; + gap: 0.1rem; margin-top: 0.6rem; `; -export const ChannelName = styled.div` +export const ChannelName = styled.div<{ $isSelected: boolean }>` cursor: pointer; display: flex; gap: 0.5rem; align-items: center; + height: 3.2rem; padding-left: 1rem; + border-radius: 0.4rem; + + color: ${({ theme, $isSelected }) => $isSelected && theme.colors.white}; + + background-color: ${({ theme, $isSelected }) => $isSelected && theme.colors.dark[500]}; + + &:hover { + background-color: ${({ theme }) => theme.colors.dark[500]}; + } `; diff --git a/src/frontend/src/components/guild/GuildCategory/index.tsx b/src/frontend/src/components/guild/GuildCategory/index.tsx index cfab889d..daae97ba 100644 --- a/src/frontend/src/components/guild/GuildCategory/index.tsx +++ b/src/frontend/src/components/guild/GuildCategory/index.tsx @@ -8,7 +8,7 @@ import { useGuildInfoStore } from '@/stores/guildInfo'; import useModalStore from '@/stores/modalStore'; import { GuildResultData } from '@/types/guilds'; -import CategoriesList from '../CategoriesList'; +import GuildCategoriesList from '../GuildCategoriesList'; import * as S from './styles'; @@ -18,11 +18,14 @@ interface DropdownItem { onClick?: () => void; } -const GuildCategories = () => { - const { openModal } = useModalStore(); +const GuildCategory = () => { const { isOpened, dropdownRef, toggleDropdown } = useDropdown(); + const { openModal } = useModalStore(); const { guildId } = useGuildInfoStore(); - const { data } = useQuery({ queryKey: ['guildInfo', guildId], queryFn: () => getGuild(guildId) }); + const { data } = useQuery({ + queryKey: ['guildInfo', guildId], + queryFn: () => getGuild(guildId), + }); const dropdownItems: DropdownItem[] = [ { @@ -48,27 +51,23 @@ const GuildCategories = () => { ]; return ( - - {guildId && ( - <> - - {data?.guild.name} - {isOpened ? : } - - - {isOpened && ( - - {dropdownItems.map((item) => ( - - {item.text} - - ))} - - )} - + <> + + {data?.guild.name} + {isOpened ? : } + + + {isOpened && ( + + {dropdownItems.map((item) => ( + + {item.text} + + ))} + )} - + ); }; -export default GuildCategories; +export default GuildCategory; diff --git a/src/frontend/src/components/guild/GuildCategory/styles.ts b/src/frontend/src/components/guild/GuildCategory/styles.ts index 5eedbb14..65242cbc 100644 --- a/src/frontend/src/components/guild/GuildCategory/styles.ts +++ b/src/frontend/src/components/guild/GuildCategory/styles.ts @@ -1,12 +1,6 @@ import styled from 'styled-components'; -import { TitleText2, BodyRegularText } from '@/styles/Typography'; - -export const GuildCategories = styled.div` - width: 24rem; - padding: 1.6rem 1rem; - background-color: ${({ theme }) => theme.colors.dark[700]}; -`; +import { BodyRegularText, TitleText2 } from '@/styles/Typography'; export const GuildTitle = styled.div` cursor: pointer; @@ -14,9 +8,11 @@ export const GuildTitle = styled.div` position: relative; display: flex; + align-items: center; justify-content: space-between; - padding-bottom: 1.6rem; + height: 4.8rem; + padding: 1.2rem 1.6rem; border-bottom: 0.1rem solid ${({ theme }) => theme.colors.dark[500]}; svg { diff --git a/src/frontend/src/components/guild/GuildChattingRoom/index.tsx b/src/frontend/src/components/guild/GuildChattingRoom/index.tsx new file mode 100644 index 00000000..d2abd8c0 --- /dev/null +++ b/src/frontend/src/components/guild/GuildChattingRoom/index.tsx @@ -0,0 +1,40 @@ +import { BiHash } from 'react-icons/bi'; +import { BsFillMicFill, BsFillPeopleFill } from 'react-icons/bs'; +import { FaInbox } from 'react-icons/fa'; + +import MessageSection from '@/pages/FriendsPage/components/ChattingSection/components/MessageSection'; +import { useChannelInfoStore } from '@/stores/channelInfo'; + +import * as S from './styles'; + +const GuildChattingRoom = () => { + const { selectedChannel } = useChannelInfoStore(); + if (!selectedChannel) return null; + + const renderChannelIcon = () => { + if (selectedChannel.type === 'TEXT') return ; + if (selectedChannel.type === 'VOICE') return ; + }; + + return ( + <> + + + {renderChannelIcon()} + {selectedChannel.name} + + + + + + + + + + + + + ); +}; + +export default GuildChattingRoom; diff --git a/src/frontend/src/components/guild/GuildChattingRoom/styles.ts b/src/frontend/src/components/guild/GuildChattingRoom/styles.ts new file mode 100644 index 00000000..d2b76a5b --- /dev/null +++ b/src/frontend/src/components/guild/GuildChattingRoom/styles.ts @@ -0,0 +1,37 @@ +import styled from 'styled-components'; + +import { BodyMediumText } from '@/styles/Typography'; + +export const ChattingHeader = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + + height: 4.8rem; + padding: 0.8rem; + border-bottom: 0.2rem solid ${({ theme }) => theme.colors.dark[700]}; + + color: ${({ theme }) => theme.colors.dark[300]}; +`; + +export const ChannelInfo = styled.div` + display: flex; + align-items: center; +`; + +export const ChannelIcon = styled.div` + margin: 0 0.8rem; +`; + +export const ChannelTitle = styled(BodyMediumText)` + color: ${({ theme }) => theme.colors.dark[300]}; +`; + +export const ToolBar = styled.div` + display: flex; +`; + +export const IconWrapper = styled.div` + cursor: pointer; + margin: 0 0.8rem; +`; diff --git a/src/frontend/src/components/guild/GuildList/styles.ts b/src/frontend/src/components/guild/GuildList/styles.ts index 33d4cc37..d4ae867b 100644 --- a/src/frontend/src/components/guild/GuildList/styles.ts +++ b/src/frontend/src/components/guild/GuildList/styles.ts @@ -6,7 +6,7 @@ import styled from 'styled-components'; export const GuildList = styled.nav` scrollbar-width: none; - overflow-x: auto; + overflow-x: visible; display: flex; flex-direction: column; gap: 2rem; diff --git a/src/frontend/src/pages/FriendsPage/components/CategorySection/index.tsx b/src/frontend/src/pages/FriendsPage/components/CategorySection/index.tsx new file mode 100644 index 00000000..67f1fa84 --- /dev/null +++ b/src/frontend/src/pages/FriendsPage/components/CategorySection/index.tsx @@ -0,0 +1,27 @@ +import DirectMessageCategory from '@/components/friend/DirectMessageCategory'; +import GuildCategory from '@/components/guild/GuildCategory'; +import UserProfile from '@/pages/FriendsPage/components/UserProfile'; +import { useGuildInfoStore } from '@/stores/guildInfo'; + +import * as S from './styles'; + +const CategorySection = () => { + const { guildId } = useGuildInfoStore(); + + return ( + + {guildId ? : } + {}} + handleHeadsetToggle={() => {}} + /> + + ); +}; + +export default CategorySection; diff --git a/src/frontend/src/pages/FriendsPage/components/CategorySection/styles.ts b/src/frontend/src/pages/FriendsPage/components/CategorySection/styles.ts new file mode 100644 index 00000000..23319f88 --- /dev/null +++ b/src/frontend/src/pages/FriendsPage/components/CategorySection/styles.ts @@ -0,0 +1,14 @@ +import styled from 'styled-components'; + +export const CategorySectionContainer = styled.div` + display: flex; + flex-direction: column; + width: 24rem; + background-color: ${({ theme }) => theme.colors.dark[700]}; +`; + +export const CategoryItemWrapper = styled.div` + display: flex; + flex-direction: column; + flex-grow: 1; +`; diff --git a/src/frontend/src/pages/FriendsPage/components/ChattingSection/components/ChatTextarea/index.tsx b/src/frontend/src/pages/FriendsPage/components/ChattingSection/components/ChatTextarea/index.tsx new file mode 100644 index 00000000..e0843021 --- /dev/null +++ b/src/frontend/src/pages/FriendsPage/components/ChattingSection/components/ChatTextarea/index.tsx @@ -0,0 +1,27 @@ +import { ForwardedRef, forwardRef } from 'react'; + +import * as S from './styles'; + +interface ChatTextareaProps { + value: string; + handleChange: (e: React.ChangeEvent) => void; + handleKeyDown?: (e: React.KeyboardEvent) => void; + placeholder?: string; +} + +const ChatTextarea = ( + { value, handleChange, handleKeyDown, placeholder }: ChatTextareaProps, + ref: ForwardedRef, +) => { + return ( + + ); +}; + +export default forwardRef(ChatTextarea); diff --git a/src/frontend/src/pages/FriendsPage/components/ChattingSection/components/ChatTextarea/styles.ts b/src/frontend/src/pages/FriendsPage/components/ChattingSection/components/ChatTextarea/styles.ts new file mode 100644 index 00000000..4881b483 --- /dev/null +++ b/src/frontend/src/pages/FriendsPage/components/ChattingSection/components/ChatTextarea/styles.ts @@ -0,0 +1,32 @@ +import styled from 'styled-components'; + +export const ChatTextarea = styled.textarea` + resize: none; + + overflow-y: auto; + flex-grow: 1; + + width: 100%; + height: 4.4rem; + max-height: 50vh; + padding: 1.1rem 1rem; + border: none; + border-radius: 0.8rem; + + font-size: 1.6rem; + font-weight: 400; + color: ${({ theme }) => theme.colors.dark[300]}; + + background-color: ${({ theme }) => theme.colors.dark[500]}; + outline: none; + + &::-webkit-scrollbar-thumb { + border: 0.1rem solid ${({ theme }) => theme.colors.dark[600]}; + border-radius: 0.3rem; + background: ${({ theme }) => theme.colors.dark[800]}; + } + + &::-webkit-scrollbar { + width: 0.6rem; + } +`; diff --git a/src/frontend/src/pages/FriendsPage/components/ChattingSection/components/MessageDivider/index.tsx b/src/frontend/src/pages/FriendsPage/components/ChattingSection/components/MessageDivider/index.tsx new file mode 100644 index 00000000..e69de29b diff --git a/src/frontend/src/pages/FriendsPage/components/ChattingSection/components/MessageDivider/styles.ts b/src/frontend/src/pages/FriendsPage/components/ChattingSection/components/MessageDivider/styles.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/frontend/src/pages/FriendsPage/components/ChattingSection/components/MessageItem/index.tsx b/src/frontend/src/pages/FriendsPage/components/ChattingSection/components/MessageItem/index.tsx new file mode 100644 index 00000000..8126d576 --- /dev/null +++ b/src/frontend/src/pages/FriendsPage/components/ChattingSection/components/MessageItem/index.tsx @@ -0,0 +1,93 @@ +import { useEffect, useRef, useState } from 'react'; +import { FiEdit2, FiTrash2 } from 'react-icons/fi'; + +import ChatTextarea from '../ChatTextarea'; +import useChatTextarea from '../MessageSection/hooks/useChatTextarea'; + +import * as S from './styles'; + +// 실제 디스코드에서 시각적으로 보여지는 데이터 +// 이후 변경될 수 있음 +export interface MessageItemProps { + sentUserProfileUrl: string; + sentUserName: string; + sentDateTime: string; + messageContent: string; + isModified: boolean; +} + +const MessageItem = ({ + sentUserProfileUrl, + sentUserName, + sentDateTime, + messageContent, + isModified, +}: MessageItemProps) => { + const [isHovered, setIsHovered] = useState(false); + const [isEditing, setIsEditing] = useState(false); + + const handleEditSubmit = () => { + setIsEditing(false); + console.log('수정한 메시지:', chatInput); + }; + + const handleCancelEdit = () => { + setIsEditing(false); + }; + + const handleEditButtonClick = () => { + setIsEditing(true); + }; + + const { chatInput, textareaRef, handleTextareaChange, handleKeyDown } = useChatTextarea({ + initialValue: messageContent, + cancelEdit: handleCancelEdit, + sendMessage: handleEditSubmit, + }); + + useEffect(() => { + if (isEditing) { + setTimeout(() => textareaRef.current?.focus(), 0); + } + }, [isEditing]); + + return ( + setIsHovered(true)} onMouseLeave={() => setIsHovered(false)}> + {isHovered && ( + + + + + console.log('삭제하기')}> + + + + )} + + + + + {sentUserName} + {sentDateTime} + + {isEditing ? ( + + handleTextareaChange(e.target.value)} + handleKeyDown={handleKeyDown} + /> + + ) : ( + + {messageContent} + {isModified && (수정됨)} + + )} + + + ); +}; + +export default MessageItem; diff --git a/src/frontend/src/pages/FriendsPage/components/ChattingSection/components/MessageItem/styles.ts b/src/frontend/src/pages/FriendsPage/components/ChattingSection/components/MessageItem/styles.ts new file mode 100644 index 00000000..3cf98ead --- /dev/null +++ b/src/frontend/src/pages/FriendsPage/components/ChattingSection/components/MessageItem/styles.ts @@ -0,0 +1,93 @@ +import styled from 'styled-components'; + +import { BodyMediumText, BodyRegularText, SmallText } from '@/styles/Typography'; + +export const MessageItem = styled.li` + position: relative; + display: flex; + width: 100%; + margin-bottom: 1.6rem; +`; + +export const MessageContentContainer = styled.div` + width: 100%; + padding-right: 1.6rem; + padding-left: calc(4rem + 1.6rem + 1.6rem); +`; + +export const MessageToolbar = styled.div` + position: absolute; + top: -1rem; + right: 0; + + display: flex; + gap: 8px; + + padding: 6px 8px; + border-radius: 6px; + + opacity: 0.9; + background: ${({ theme }) => theme.colors.dark[600]}; + box-shadow: 0 2px 5px rgb(0 0 0 / 20%); +`; + +export const IconButton = styled.button` + cursor: pointer; + border: none; + color: ${({ theme }) => theme.colors.dark[300]}; + background: none; + + &:hover { + color: ${({ theme }) => theme.colors.white}; + } +`; + +export const SentUserProfileImage = styled.img` + cursor: pointer; + + position: absolute; + left: 1.6rem; + + overflow: hidden; + + width: 4rem; + height: 4rem; + margin-top: 0.275rem; + border-radius: 50%; + + background-color: ${({ theme }) => theme.colors.dark[300]}; +`; + +export const MessageTitle = styled.div` + display: flex; + gap: 1rem; + align-items: flex-end; +`; + +export const EditTextareaWrapper = styled.div` + width: 100%; +`; + +export const SentUserName = styled(BodyMediumText)` + overflow: hidden; + + max-width: 50%; + + line-height: 1.8rem; + color: ${({ theme }) => theme.colors.white}; + text-overflow: ellipsis; + white-space: nowrap; +`; + +export const SentDateTime = styled(SmallText)` + color: ${({ theme }) => theme.colors.dark[300]}; +`; + +export const MessageContent = styled(BodyRegularText)` + color: ${({ theme }) => theme.colors.white}; +`; + +export const ModifiedMark = styled(SmallText)` + margin-left: 0.5rem; + color: ${({ theme }) => theme.colors.dark[300]}; +`; diff --git a/src/frontend/src/pages/FriendsPage/components/ChattingSection/components/MessageSection/hooks/useChat.ts b/src/frontend/src/pages/FriendsPage/components/ChattingSection/components/MessageSection/hooks/useChat.ts new file mode 100644 index 00000000..894aff8d --- /dev/null +++ b/src/frontend/src/pages/FriendsPage/components/ChattingSection/components/MessageSection/hooks/useChat.ts @@ -0,0 +1,78 @@ +import { useEffect, useState } from 'react'; + +import useStompClient from './useStompClient'; + +export interface ChatMessage { + channelId: string; + profileImage: string; + name: string; + content: string; +} + +interface UseChatProps { + channelId: string; +} + +const useChat = ({ channelId }: UseChatProps) => { + const { client, isConnected } = useStompClient({ channelId }); + const [messages, setMessages] = useState([]); + + const VITE_TOKEN = import.meta.env.VITE_TOKEN; + + useEffect(() => { + if (!client || !isConnected) return; + + console.log(`✅ 채팅 구독 시작: /topic/direct-message/${channelId}`); + + // 채널 구독 + const subscription = client.subscribe(`/topic/direct-message/${channelId}`, (message) => { + const parsedMessage: ChatMessage = JSON.parse(message.body); + setMessages((prev) => [...prev, parsedMessage]); + console.log('📩 받은 메시지:', parsedMessage); + }); + + return () => { + if (client.connected) { + console.log('연결 해제 요청'); + // 토큰을 담아 명시적으로 diconnect 요청 + client.publish({ + destination: '/disconnect', + body: JSON.stringify({ message: 'DISCONNECT 요청' }), + headers: { Authorization: `Bearer ${VITE_TOKEN}` }, + }); + + console.log('📤 DISCONNECT 프레임 전송 완료!'); + } + + subscription.unsubscribe(); + console.log(`🚪 채팅 구독 해제: /topic/direct-message/${channelId}`); + }; + }, [client, isConnected, channelId]); + + // 메시지 전송 + const sendMessage = (content: string) => { + if (!client?.connected) { + console.error('❌ STOMP 연결이 끊어져서 메시지를 전송할 수 없습니다.'); + return; + } + + const message: ChatMessage = { + channelId, + profileImage: 'https://asyncgate5.s3.ap-northeast-2.amazonaws.com/user/default_profile.png', + name: '철민', + content, + }; + + client.publish({ + destination: '/kafka/chat/direct/send-message', + body: JSON.stringify(message), + headers: { Authorization: `Bearer ${VITE_TOKEN}` }, + }); + + console.log(`📤 메시지 전송 완료: ${content}`); + }; + + return { messages, sendMessage }; +}; + +export default useChat; diff --git a/src/frontend/src/pages/FriendsPage/components/ChattingSection/components/MessageSection/hooks/useChatTextarea.ts b/src/frontend/src/pages/FriendsPage/components/ChattingSection/components/MessageSection/hooks/useChatTextarea.ts new file mode 100644 index 00000000..d799e5b5 --- /dev/null +++ b/src/frontend/src/pages/FriendsPage/components/ChattingSection/components/MessageSection/hooks/useChatTextarea.ts @@ -0,0 +1,44 @@ +import { useRef, useState } from 'react'; + +interface UseChatTextareaProps { + initialValue?: string; + // globalFocusTarget?: 'messageSection' | 'messageItem'; + cancelEdit?: () => void; + sendMessage: (value: string) => void; +} + +const useChatTextarea = ({ initialValue = '', cancelEdit, sendMessage }: UseChatTextareaProps) => { + const [chatInput, setChatInput] = useState(initialValue); + const textareaRef = useRef(null); + + const handleTextareaChange = (value: string) => { + setChatInput(value); + }; + + const handleKeyDown = (event: React.KeyboardEvent) => { + // 활성화된 요소가 textarea인지 확인 + if (document.activeElement !== textareaRef.current) return; + if (event.key === 'Enter' && !event.shiftKey) { + event.preventDefault(); // 기본 줄바꿈 동작 방지 + if (chatInput.trim().length > 0) { + sendMessage(chatInput); + setChatInput(''); + } + return; + } + + if (event.key === 'Escape' && cancelEdit) { + event.preventDefault(); // 기본 동작 방지 + cancelEdit(); + } + }; + + return { + chatInput, + textareaRef, + handleTextareaChange, + handleKeyDown, + }; +}; + +export default useChatTextarea; diff --git a/src/frontend/src/pages/FriendsPage/components/ChattingSection/components/MessageSection/hooks/useStompClient.ts b/src/frontend/src/pages/FriendsPage/components/ChattingSection/components/MessageSection/hooks/useStompClient.ts new file mode 100644 index 00000000..832e9017 --- /dev/null +++ b/src/frontend/src/pages/FriendsPage/components/ChattingSection/components/MessageSection/hooks/useStompClient.ts @@ -0,0 +1,75 @@ +import { Client, Frame } from '@stomp/stompjs'; +import { useEffect, useRef, useState } from 'react'; + +import { ChatMessage } from './useChat'; + +interface UseStompClientProps { + channelId: string; +} + +const useStompClient = ({ channelId }: UseStompClientProps) => { + const [isConnected, setIsConnected] = useState(false); + const clientRef = useRef(null); + + const VITE_CHATTING_WS_URL = import.meta.env.VITE_CHATTING_WS_URL; + const VITE_TOKEN = import.meta.env.VITE_TOKEN; + + useEffect(() => { + const client = new Client({ + webSocketFactory: () => new WebSocket(VITE_CHATTING_WS_URL, ['v10.stomp', VITE_TOKEN]), + connectHeaders: { Authorization: `Bearer ${VITE_TOKEN}` }, + debug: (msg) => console.log('STOMP DEBUG:', msg), + reconnectDelay: 5000, + heartbeatIncoming: 10000, + heartbeatOutgoing: 10000, + + onConnect: (frame: Frame) => { + console.log('✅ STOMP 연결 성공!', frame); + + // 연결 성공 시 subscribe + client.subscribe(`/topic/direct-message/${channelId}`, (message) => { + const parsedMessage: ChatMessage = JSON.parse(message.body); + // setMessages((prev) => [...prev, parsedMessage]); + console.log('📩 받은 메시지:', parsedMessage); + }); + + setIsConnected(true); + }, + + onWebSocketError: (error: Error) => { + console.log('WebSocket 에러', error); + }, + + onStompError: (frame) => { + console.error('❌ STOMP 오류 발생!', frame); + }, + }); + + client.activate(); + clientRef.current = client; + + return () => { + if (client.connected) { + console.log('🚪 WebSocket 연결 해제 요청'); + + // 토큰을 담아 명시적으로 diconnect 요청 + client.publish({ + destination: '/disconnect', + body: JSON.stringify({ message: 'DISCONNECT 요청' }), + headers: { Authorization: `Bearer ${VITE_TOKEN}` }, + }); + + console.log('📤 DISCONNECT 프레임 전송 완료!'); + } + + client.deactivate(); + clientRef.current = null; + setIsConnected(false); + console.log('✅ WebSocket 연결 해제됨'); + }; + }, [channelId]); + + return { client: clientRef.current, isConnected }; +}; + +export default useStompClient; diff --git a/src/frontend/src/pages/FriendsPage/components/ChattingSection/components/MessageSection/index.tsx b/src/frontend/src/pages/FriendsPage/components/ChattingSection/components/MessageSection/index.tsx new file mode 100644 index 00000000..181908b5 --- /dev/null +++ b/src/frontend/src/pages/FriendsPage/components/ChattingSection/components/MessageSection/index.tsx @@ -0,0 +1,128 @@ +import { useRef, useState } from 'react'; +import { BsFillPlusCircleFill } from 'react-icons/bs'; +import { IoClose } from 'react-icons/io5'; +import { MdEmojiEmotions } from 'react-icons/md'; + +import ChatTextarea from '../ChatTextarea'; +import MessageItem from '../MessageItem'; + +import useChat, { ChatMessage } from './hooks/useChat'; +import useChatTextarea from './hooks/useChatTextarea'; +import * as S from './styles'; + +interface MessageSectionProps { + channelId: string; +} + +const MessageSection = ({ channelId }: MessageSectionProps) => { + const { messages, sendMessage } = useChat({ channelId }); + const MOCKDATA: ChatMessage[] = [ + { + profileImage: '', + channelId, + name: '철민', + content: '테스트용 메시지', + }, + { + profileImage: '', + channelId, + name: '철민', + content: '테스트용 메시지테스트용 메시지테스트용 메시지테스트용 메시지', + }, + { + profileImage: '', + channelId, + name: '철민', + content: '테스트용 메시지테스트용 메시지', + }, + { + profileImage: '', + channelId, + name: '철민', + content: '테스트용 메시지테스트용 메시지테스트용 메시지테스트용 메시지테스트용 메시지테스트용 메시지', + }, + { + profileImage: '', + channelId, + name: '철민', + content: '테스트용 메시지테스트용 메시지테스트용 메시지테스트용 메시지테스트용 메시지', + }, + ]; + // TODO: 이전 채팅 기록 불러오는 로직 추가 예정 + const { chatInput, textareaRef, handleTextareaChange, handleKeyDown } = useChatTextarea({ + sendMessage, + }); + const [selectedFile, setSelectedFile] = useState(null); + + const fileInputRef = useRef(null); + + const handleFileAttachButtonClick = () => { + fileInputRef.current?.click(); + }; + + const handleFileChange = async (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + + console.log('선택한 파일:', file); + setSelectedFile(file); + + // 파일 업로드 로직 + }; + + const handleFileRemove = () => { + setSelectedFile(null); + if (fileInputRef.current) { + fileInputRef.current.value = ''; + } + }; + + return ( + + + + {MOCKDATA.map((message, index) => ( + + ))} + + + + {selectedFile && ( + + {selectedFile.name} + + + + + )} + + + + + + handleTextareaChange(e.target.value)} + handleKeyDown={handleKeyDown} + /> + + + + + + + + + ); +}; + +export default MessageSection; diff --git a/src/frontend/src/pages/FriendsPage/components/ChattingSection/components/MessageSection/styles.ts b/src/frontend/src/pages/FriendsPage/components/ChattingSection/components/MessageSection/styles.ts new file mode 100644 index 00000000..6249bc78 --- /dev/null +++ b/src/frontend/src/pages/FriendsPage/components/ChattingSection/components/MessageSection/styles.ts @@ -0,0 +1,104 @@ +import styled from 'styled-components'; + +export const MessageSection = styled.section` + display: flex; + flex-direction: column; + justify-content: flex-end; + height: 100%; +`; + +export const MessageContainer = styled.div` + display: flex; + flex-direction: column; + flex-grow: 1; + justify-content: flex-end; +`; + +export const MessageItemList = styled.ol` + display: flex; + flex-direction: column; + justify-content: flex-end; + height: 100%; +`; + +export const BottomBarWrapper = styled.div` + position: relative; + width: 100%; + padding: 0 1.6rem; + background-color: ${({ theme }) => theme.colors.dark[600]}; +`; + +export const BottomBarContainer = styled.div` + display: flex; + align-items: flex-start; + + margin-bottom: 2.4rem; + border-radius: 0.8rem; + + background-color: ${({ theme }) => theme.colors.dark[500]}; +`; + +export const AttachButton = styled.button` + height: 4.4rem; + padding: 1rem 1.6rem; + color: ${({ theme }) => theme.colors.dark[300]}; +`; + +export const FileAttachInput = styled.input` + display: none; +`; + +export const FilePreview = styled.div` + position: absolute; + bottom: 7rem; + + display: flex; + align-items: center; + justify-content: space-between; + + margin-bottom: 0.5rem; + padding: 0.8rem 1.2rem; + border-radius: 0.6rem; + + background-color: ${({ theme }) => theme.colors.dark[500]}; +`; + +export const FileName = styled.span` + font-size: 1.4rem; + color: ${({ theme }) => theme.colors.dark[300]}; +`; + +export const FileRemoveButton = styled.button` + cursor: pointer; + + margin-left: 1rem; + border: none; + + color: ${({ theme }) => theme.colors.dark[400]}; + + background: none; + + &:hover { + color: ${({ theme }) => theme.colors.dark[300]}; + } +`; + +export const ToolBarContainer = styled.div` + display: flex; + align-items: center; + + height: 4.4rem; + padding-right: 0.4rem; + + color: ${({ theme }) => theme.colors.dark[300]}; +`; + +export const IconWrapper = styled.div` + cursor: pointer; + + display: flex; + align-items: center; + justify-content: center; + + width: 4rem; +`; diff --git a/src/frontend/src/pages/FriendsPage/components/ChattingSection/index.tsx b/src/frontend/src/pages/FriendsPage/components/ChattingSection/index.tsx new file mode 100644 index 00000000..73c05314 --- /dev/null +++ b/src/frontend/src/pages/FriendsPage/components/ChattingSection/index.tsx @@ -0,0 +1,30 @@ +import DirectMessageChattingRoom from '@/components/friend/DirectMessageChattingRoom'; +import FriendsManagement from '@/components/friend/FriendsManagement'; +import GuildChattingRoom from '@/components/guild/GuildChattingRoom'; +import { useChannelInfoStore } from '@/stores/channelInfo'; +import { useGuildInfoStore } from '@/stores/guildInfo'; + +import * as S from './styles'; + +type ChattingSectionView = 'guildChat' | 'directChat' | 'friendsManagement'; + +const ChattingSection = () => { + const { guildId } = useGuildInfoStore(); + const { selectedDMChannel } = useChannelInfoStore(); + + const component = { + guildChat: , + directChat: , + friendsManagement: , + }; + + const getCurrentView = (): ChattingSectionView => { + if (guildId) return 'guildChat'; + if (selectedDMChannel) return 'directChat'; + return 'friendsManagement'; + }; + + return {component[getCurrentView()]}; +}; + +export default ChattingSection; diff --git a/src/frontend/src/pages/FriendsPage/components/ChattingSection/styles.ts b/src/frontend/src/pages/FriendsPage/components/ChattingSection/styles.ts new file mode 100644 index 00000000..d68c96f6 --- /dev/null +++ b/src/frontend/src/pages/FriendsPage/components/ChattingSection/styles.ts @@ -0,0 +1,7 @@ +import styled from 'styled-components'; + +export const ChattingSectionContainer = styled.div` + display: flex; + flex-direction: column; + flex-grow: 1; +`; diff --git a/src/frontend/src/pages/FriendsPage/components/CustomizeGuildModal/index.tsx b/src/frontend/src/pages/FriendsPage/components/CustomizeGuildModal/index.tsx index eba5f6b7..b27c28ac 100644 --- a/src/frontend/src/pages/FriendsPage/components/CustomizeGuildModal/index.tsx +++ b/src/frontend/src/pages/FriendsPage/components/CustomizeGuildModal/index.tsx @@ -64,12 +64,12 @@ const CustomizeGuildModal = ({ -