diff --git a/src/assets/svgs/hamburgerIcon.svg b/src/assets/svgs/hamburgerIcon.svg new file mode 100644 index 00000000..1682b89f --- /dev/null +++ b/src/assets/svgs/hamburgerIcon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/svgs/index.tsx b/src/assets/svgs/index.tsx index 48202095..022d9662 100644 --- a/src/assets/svgs/index.tsx +++ b/src/assets/svgs/index.tsx @@ -94,3 +94,6 @@ export { default as GroupCuriousIc } from './groupCuriousIcn.svg?react'; export { default as GroupViewIc } from './groupViewIcn.svg?react'; // export { default as LoadingIc } from './loadingSvg.svg?react'; + +export { default as LinkIc } from './linkIcon.svg?react'; +export { default as HamburgerIc } from './hamburgerIcon.svg?react'; diff --git a/src/assets/svgs/linkIcon.svg b/src/assets/svgs/linkIcon.svg new file mode 100644 index 00000000..53fb9343 --- /dev/null +++ b/src/assets/svgs/linkIcon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/commons/Header.tsx b/src/components/commons/Header.tsx index a23e87a8..91f8b464 100644 --- a/src/components/commons/Header.tsx +++ b/src/components/commons/Header.tsx @@ -2,9 +2,9 @@ import styled from '@emotion/styled'; import { useEffect, useState } from 'react'; import Button from './Button'; -import LogInOutBtn from './LogInOutBtn'; +import { LogInOutBtn } from './HeaderButton'; -import { HeaderLogoIc } from '../../assets/svgs'; +import { HamburgerIc, HeaderLogoIc, LinkIc } from '../../assets/svgs'; import { default as useNavigateHome, default as useNavigateToHome, @@ -16,6 +16,14 @@ import MyGroupDropDown from '../../pages/groupFeed/components/MyGroupDropDown'; import { useFetchHeaderGroup } from '../../pages/groupFeed/hooks/queries'; import logout from '../../utils/logout'; +import { MOBILE_MEDIA_QUERY } from '../../styles/mediaQuery'; +import Responsive from './Responsive/Responsive'; +import { MobileUnAuthorizedSidebar, MobileAuthorizedSidebar } from './MyMobileSidebar'; + +import { useParams } from 'react-router-dom'; +import { useFetchInvitationLink } from '../../pages/admin/hooks/queries'; +import { copyLink } from '../../utils/copyLink'; + interface onClickEditProps { onClickEditSave: () => void; } @@ -33,25 +41,54 @@ interface OnClickTempExistProps { export const AuthorizationHeader = () => { const [moims, setMoims] = useState([]); const { navigateToHome } = useNavigateToHome(); - const { data } = useFetchHeaderGroup(); + const { moimsData } = useFetchHeaderGroup(); const handleLogOut = () => { logout(); navigateToHome(); }; useEffect(() => { - if (data?.data?.moims) setMoims(data?.data.moims); - }, [data?.data?.moims]); + if (moimsData) setMoims(moimsData); + }, [moimsData]); + + const [isSidebarOpen, setIsSidebarOpen] = useState(false); return ( - - - - - - - 로그아웃 - - - + <> + + + + + + + + + + 로그아웃 + + + + + + + + + { + setIsSidebarOpen(true); + }} + /> + + + {isSidebarOpen && ( + { + setIsSidebarOpen(false); + }} + groupCount={moimsData?.length ?? 0} + groupData={moims ?? []} + /> + )} + + ); }; @@ -60,11 +97,38 @@ export const UnAuthorizationHeader = () => { const { navigateToHome } = useNavigateHome(); const { navigateToLogin } = useNavigateLoginWithPath(); + const [isSidebarOpen, setIsSidebarOpen] = useState(false); + return ( - - - 로그인 - + <> + + + + + 로그인 + + + + + + + + { + setIsSidebarOpen(true); + }} + /> + + + {isSidebarOpen && ( + { + setIsSidebarOpen(false); + }} + /> + )} + + ); }; @@ -124,6 +188,61 @@ export const DefaultHeader = () => { ); }; +// 관리자 헤더 +export const AdminHeader = () => { + const { navigateToHome } = useNavigateHome(); + const [isSidebarOpen, setIsSidebarOpen] = useState(false); + const [moims, setMoims] = useState([]); + const { moimsData } = useFetchHeaderGroup(); + const { groupId } = useParams(); + const { invitationCode } = useFetchInvitationLink(groupId); + + const handleCopyLink = (invitationCode: string) => { + copyLink(import.meta.env.VITE_INVITE_URL + `group/${invitationCode}/groupInvite`); + }; + + const handleInviteBtnClick = () => { + handleCopyLink(invitationCode?.invitationCode || ''); + }; + + useEffect(() => { + if (moimsData) setMoims(moimsData); + }, [moimsData]); + return ( + <> + + + + + + + + + { + setIsSidebarOpen(true); + }} + /> + + + {isSidebarOpen && ( + { + setIsSidebarOpen(false); + }} + groupCount={moimsData?.length ?? 0} + groupData={moims ?? []} + /> + )} + + + ); +}; + +const LogInWrapper = styled.div` + width: 8.1rem; +`; + const HeaderWrapper = styled.div` position: fixed; top: 0; @@ -138,6 +257,26 @@ const HeaderWrapper = styled.div` background-color: ${({ theme }) => theme.colors.white}; border-bottom: 1px solid ${({ theme }) => theme.colors.gray30}; + + @media ${MOBILE_MEDIA_QUERY} { + width: 100%; + height: 5.6rem; + padding: 0 2rem; + } +`; + +const LinkIcon = styled(LinkIc)` + cursor: pointer; +`; +const HamburgerIcon = styled(HamburgerIc)` + cursor: pointer; +`; + +const MobileHeaderButtons = styled.div` + display: flex; + gap: 1.6rem; + align-items: center; + ${({ theme }) => theme.fonts.mSubtitle4}; `; const HeaderLayout = styled.div` @@ -158,4 +297,13 @@ const HeaderLogoIcon = styled(HeaderLogoIc)` flex-shrink: 0; cursor: pointer; + + @media ${MOBILE_MEDIA_QUERY} { + width: 7rem; + height: 1.68rem; + } +`; + +const CreateGroupWrapper = styled.div` + width: 13.6rem; `; diff --git a/src/components/commons/HeaderButton.tsx b/src/components/commons/HeaderButton.tsx new file mode 100644 index 00000000..c2e88157 --- /dev/null +++ b/src/components/commons/HeaderButton.tsx @@ -0,0 +1,88 @@ +import styled from '@emotion/styled'; +import { css } from '@emotion/react'; + +const basicCSS = css` + display: flex; + align-items: center; + justify-content: center; + height: 4rem; + padding: 1rem 1.6rem; + + white-space: nowrap; + + cursor: pointer; + border-radius: 0.8rem; + + transition: + transform 0.5s, + background-color 0.5s, + color 0.5s; +`; + +// 글모임 만들기 버튼 +export const CreateGroupBtnWrapper = styled.div` + ${basicCSS}; + gap: 0.6rem; + width: 100%; + + color: ${({ theme }) => theme.colors.white}; + + background-color: ${({ theme }) => theme.colors.black}; + + ${({ theme }) => theme.fonts.button3}; + + :hover { + color: ${({ theme }) => theme.colors.mainViolet}; + + background-color: ${({ theme }) => theme.colors.white}; + box-shadow: 0 4px 8px 0 rgb(0 0 0 / 16%); + transform: scale(0.95); + } + + :active { + transform: scale(1.1); + } +`; + +// 로그인/로그아웃 버튼 +export const LogInOutBtn = styled.button` + ${basicCSS}; + width: 100%; + + color: ${({ theme }) => theme.colors.gray70}; + + ${({ theme }) => theme.fonts.button3}; + + :hover { + color: ${({ theme }) => theme.colors.mainViolet}; + + background-color: ${({ theme }) => theme.colors.gray10}; + transform: scale(0.95); + } + + :active { + transform: scale(1.1); + } +`; + +// 내 글 모임 버튼 +export const MyGroupBtn = styled.button` + ${basicCSS}; + width: 9.5rem; + + color: ${({ theme }) => theme.colors.gray70}; + + ${({ theme }) => theme.fonts.subtitle6}; + + :hover { + color: ${({ theme }) => theme.colors.mainViolet}; + + background-color: ${({ theme }) => theme.colors.gray10}; + transform: scale(0.95); + border-radius: 0.8rem; + } + + :active { + transform: scale(1.1); + } +`; diff --git a/src/components/commons/LogInOutBtn.tsx b/src/components/commons/LogInOutBtn.tsx deleted file mode 100644 index d6ae5394..00000000 --- a/src/components/commons/LogInOutBtn.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import styled from '@emotion/styled'; -import { ReactNode } from 'react'; - -type LogInOutPropTypes = { - children: ReactNode; - onClick?: () => void; -}; -const LogInOutBtn = ({ children, onClick }: LogInOutPropTypes) => { - return ( - - {children} - - ); -}; - -export default LogInOutBtn; - -const LogInOutWrapper = styled.button` - height: 4rem; - padding: 1rem 1.6rem; - - color: ${({ theme }) => theme.colors.gray70}; - white-space: nowrap; - - cursor: pointer; - border-radius: 8px; - - ${({ theme }) => theme.fonts.button3}; - - :hover { - color: ${({ theme }) => theme.colors.mainViolet}; - - background-color: ${({ theme }) => theme.colors.gray10}; - transform: scale(0.95); - - transition: 0.5s; - } - - :active { - transform: scale(1.1); - - transition: 0.5s; - } -`; diff --git a/src/components/commons/MyMobileSidebar.tsx b/src/components/commons/MyMobileSidebar.tsx new file mode 100644 index 00000000..a646bf15 --- /dev/null +++ b/src/components/commons/MyMobileSidebar.tsx @@ -0,0 +1,147 @@ +import styled from '@emotion/styled'; +import useClickOutside from '../../hooks/useClickOutside'; +import logout from '../../utils/logout'; +import useNavigateToHome from '../../hooks/useNavigateHome'; + +import { useRef } from 'react'; +import { LogInOutBtn } from './HeaderButton'; +import CreateGroupBtn from '../../pages/groupFeed/components/CreateGroupBtn'; +import useNavigateLoginWithPath from '../../hooks/useNavigateLoginWithPath'; +import { Moim } from '../../pages/groupFeed/apis/fetchHeaderGroup'; +import { useNavigate } from 'react-router-dom'; + +export const MobileUnAuthorizedSidebar = ({ onClose }: { onClose: () => void }) => { + const sidebarRef = useRef(null); + const { navigateToLogin } = useNavigateLoginWithPath(); + + useClickOutside(sidebarRef, () => { + //헤더 이외부분 클릭시 닫히게 + if (sidebarRef.current) onClose(); + }); + + return ( + <> + + 로그인 + + + ); +}; + +interface AuthorizedPropTypes { + onClose: () => void; + groupCount: number; + groupData: Moim[] | []; +} + +export const MobileAuthorizedSidebar = ({ + onClose, + groupCount, + groupData, +}: AuthorizedPropTypes) => { + const sidebarRef = useRef(null); + const { navigateToHome } = useNavigateToHome(); + const navigate = useNavigate(); + + useClickOutside(sidebarRef, () => { + //헤더 이외부분 클릭시 닫히게 + if (sidebarRef.current) onClose(); + }); + const handleLogOut = () => { + logout(); + navigateToHome(); + }; + + const handleRoutingGroupFeed = (groupId: string) => { + navigate(`/group/${groupId}`); + }; + + return ( + <> + + + 로그아웃 + + + 내 글 모임 + + {groupData?.length > 0 ? ( + groupData.map(({ moimId, moimName }: Moim) => ( + handleRoutingGroupFeed(moimId)}> + {moimName} + + )) + ) : ( +
{`가입한 글 모임이\n 없습니다.`}
+ )} +
+
+ + ); +}; + +const GroupList = styled.div` + display: flex; + flex-direction: column; + gap: 0.8rem; +`; + +const GroupData = styled.div` + display: flex; + align-items: center; + justify-content: center; + width: 15.9rem; + height: 4.4rem; + + color: ${({ theme }) => theme.colors.gray70}; + + cursor: pointer; + border-radius: 0.8rem; + ${({ theme }) => theme.fonts.mSubtitle2}; + + :hover { + color: black; + + background-color: ${({ theme }) => theme.colors.gray20}; + } +`; + +const MyGroup = styled.p` + display: flex; + justify-content: center; + ${({ theme }) => theme.fonts.mButton1}; +`; + +const Background = styled.div` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; +`; + +const CustomLine = styled.hr` + width: 161px; + height: 2px; + margin: 0.4rem 0; + + background-color: ${({ theme }) => theme.colors.gray20}; + border: none; + border-radius: 1px; /* 선의 끝을 둥글게 설정 */ +`; + +const SideBarLayout = styled.div` + position: fixed; + right: 0; + z-index: 3; + display: flex; + flex-direction: column; + gap: 1.2rem; + align-items: center; + width: 19.1rem; + height: 100vh; + padding: 1.2rem 1.6rem; + + background-color: ${({ theme }) => theme.colors.white}; + box-shadow: 0 4px 8px 0 rgb(0 0 0 / 16%); +`; diff --git a/src/pages/admin/Admin.tsx b/src/pages/admin/Admin.tsx index 0f182703..74a33e2a 100644 --- a/src/pages/admin/Admin.tsx +++ b/src/pages/admin/Admin.tsx @@ -1,13 +1,11 @@ import styled from '@emotion/styled'; import { useNavigate, useParams } from 'react-router-dom'; - import { useGroupInfo } from '../groupFeed/hooks/queries'; import Loading from '../loading/Loading'; import RenderAdminContent from './components/RenderAdminContent'; import { useFetchInvitationLink } from './hooks/queries'; - import { AdminHomeIc } from '../../assets/svgs'; -import { AuthorizationHeader } from '../../components/commons/Header'; +import { AdminHeader } from '../../components/commons/Header'; import Responsive from '../../components/commons/Responsive/Responsive'; import Spacing from '../../components/commons/Spacing'; import { MOBILE_MEDIA_QUERY } from '../../styles/mediaQuery'; @@ -43,7 +41,7 @@ const Admin = () => { } return ( - {accessToken && } + {accessToken && } diff --git a/src/pages/createGroup/CreateGroup.tsx b/src/pages/createGroup/CreateGroup.tsx index a828e7f2..e8f6a046 100644 --- a/src/pages/createGroup/CreateGroup.tsx +++ b/src/pages/createGroup/CreateGroup.tsx @@ -156,81 +156,85 @@ const CreateGroup = () => { }; return ( - + {localStorage.getItem('accessToken') ? : } - {currentPage === 'GroupInfoPage' && ( - - )} - {currentPage === 'GroupLeaderInfoPage' && ( - - )} - {currentPage === 'GroupLeaderInfoPage' && ( - - - 생성하기 - - - 뒤로가기 - - - )} - - - - - - {/* 페이지 이탈 모달 */} - - - - + + {currentPage === 'GroupInfoPage' && ( + + )} + {currentPage === 'GroupLeaderInfoPage' && ( + <> + + + + 생성하기 + + + 뒤로가기 + + + + )} + + + + + {/* 페이지 이탈 모달 */} + + + + + ); }; export default CreateGroup; +const GroupWrapper = styled.div` + width: 100%; +`; const BackPageBtn = styled.button` display: flex; align-items: center; diff --git a/src/pages/groupFeed/components/CreateGroupBtn.tsx b/src/pages/groupFeed/components/CreateGroupBtn.tsx index 8468698a..cdcbd3c4 100644 --- a/src/pages/groupFeed/components/CreateGroupBtn.tsx +++ b/src/pages/groupFeed/components/CreateGroupBtn.tsx @@ -1,4 +1,3 @@ -import styled from '@emotion/styled'; import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; @@ -6,6 +5,7 @@ import { MakeGroupPlusBtn, MakeGroupPlusHoverBtn } from '../../../assets/svgs'; import { FullModal, FullModalBtn } from '../../../components/commons/modal/FullModal'; import useModal from '../../../hooks/useModal'; import { MODAL } from '../constants/modalContent'; +import { CreateGroupBtnWrapper } from '../../../components/commons/HeaderButton'; interface groupCountProps { groupCount: number; @@ -52,37 +52,3 @@ const CreateGroupBtn = ({ groupCount }: groupCountProps) => { }; export default CreateGroupBtn; - -const CreateGroupBtnWrapper = styled.button` - display: flex; - gap: 0.6rem; - align-items: center; - justify-content: center; - width: auto; - height: 4rem; - padding: 1rem 1.6rem; - - color: ${({ theme }) => theme.colors.white}; - white-space: nowrap; - - background-color: ${({ theme }) => theme.colors.black}; - cursor: pointer; - border-radius: 8px; - - :hover { - color: ${({ theme }) => theme.colors.mainViolet}; - - background-color: ${({ theme }) => theme.colors.white}; - box-shadow: 0 4px 8px 0 rgb(0 0 0 / 16%); - transform: scale(0.95); - - transition: 0.5s; - } - ${({ theme }) => theme.fonts.button3}; - - :active { - transform: scale(1.1); - - transition: 0.5s; - } -`; diff --git a/src/pages/groupFeed/components/MyGroupDropDown.tsx b/src/pages/groupFeed/components/MyGroupDropDown.tsx index ec8a1e03..06bbfd7f 100644 --- a/src/pages/groupFeed/components/MyGroupDropDown.tsx +++ b/src/pages/groupFeed/components/MyGroupDropDown.tsx @@ -1,7 +1,7 @@ import styled from '@emotion/styled'; import { useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; - +import { MyGroupBtn } from '../../../components/commons/HeaderButton'; import { Moim } from '../apis/fetchHeaderGroup'; import useClickOutside from '../../../hooks/useClickOutside'; @@ -29,7 +29,7 @@ const MyGroupDropDown = ({ groupData }: CreateGroupBtnProps) => { return ( - 내 글 모임 + 내 글 모임 {groupData?.length > 0 ? ( groupData.map(({ moimId, moimName }: Moim) => ( @@ -59,39 +59,6 @@ const MyGroupDropDownWrapper = styled.section` align-items: center; `; -const MyGroupBtnLayout = styled.button` - display: flex; - align-items: center; - justify-content: center; - width: 9.5rem; - height: 4rem; - padding: 1rem 1.6rem; - - color: ${({ theme }) => theme.colors.gray70}; - white-space: nowrap; - text-align: center; - - cursor: pointer; - - ${({ theme }) => theme.fonts.subtitle6} - - :hover { - color: ${({ theme }) => theme.colors.mainViolet}; - - background-color: ${({ theme }) => theme.colors.gray10}; - transform: scale(0.95); - border-radius: 0.8rem; - - transition: 0.5s; - } - - :active { - transform: scale(1.1); - - transition: 0.5s; - } -`; - const MyGroupListLayout = styled.div<{ $isOpen: boolean }>` position: absolute; top: 6rem; diff --git a/src/pages/groupFeed/hooks/queries.ts b/src/pages/groupFeed/hooks/queries.ts index 9c8fdb5d..7584cc94 100644 --- a/src/pages/groupFeed/hooks/queries.ts +++ b/src/pages/groupFeed/hooks/queries.ts @@ -119,7 +119,9 @@ export const useFetchHeaderGroup = () => { queryFn: () => fetchHeaderGroup(), retry: 3, }); - return { data }; + + const moimsData = data?.data?.moims; + return { moimsData }; }; export const useFetchWriterInfo = ( diff --git a/src/pages/groupInvite/GroupInvite.tsx b/src/pages/groupInvite/GroupInvite.tsx index 953b7fc7..1ce8d067 100644 --- a/src/pages/groupInvite/GroupInvite.tsx +++ b/src/pages/groupInvite/GroupInvite.tsx @@ -25,7 +25,7 @@ const GroupInvite = () => { useGetGroupInfo(groupId); // 글모임 5개 가입 제한 - const { data } = useFetchHeaderGroup(); + const { moimsData } = useFetchHeaderGroup(); useEffect(() => { if (isError && isAxiosError(error)) { @@ -53,15 +53,15 @@ const GroupInvite = () => { }, [error, isError, groupId]); useEffect(() => { - if (data?.data.moims && data?.data.moims.length >= 5) { + if (moimsData && moimsData?.length >= 5) { handleShowModal(); } - }, [data?.data.moims.length]); + }, [moimsData?.length]); return ( - <> + + - <Spacing marginBottom="4.8" /> @@ -79,7 +79,6 @@ const GroupInvite = () => { <Spacing marginBottom="7.7" /> </GroupInviteWrapper> - <FullModal isModalOpen={isModalOpen} content={MODAL.ALERT_GROUP_LIMIT}> <FullModalBtn isPrimary={false} @@ -90,16 +89,22 @@ const GroupInvite = () => { }} /> </FullModal> - </> + </InviteWrapper> ); }; export default GroupInvite; +const InviteWrapper = styled.div` + display: flex; + width: 100%; +`; + const GroupInviteWrapper = styled.div` display: flex; flex-direction: column; align-items: center; justify-content: center; width: 82.6rem; + margin: 0 auto; `; diff --git a/src/pages/groupJoinCongrats/GroupJoinCongrats.tsx b/src/pages/groupJoinCongrats/GroupJoinCongrats.tsx index 014579db..a6160db4 100644 --- a/src/pages/groupJoinCongrats/GroupJoinCongrats.tsx +++ b/src/pages/groupJoinCongrats/GroupJoinCongrats.tsx @@ -17,34 +17,43 @@ const GroupJoinCongrats = () => { navigate(`/group/${groupId}`); }; return ( - <GroupJoinCongratsWrapper> + <JoinWrapper> <DefaultHeader /> - <Spacing marginBottom="15.6" /> - <GroupJoinCongratsContainer> - <GroupJoinTitleWrapper> - <GroupJoinTitle>{location?.state?.moimTitle} 가입을 축하해요!</GroupJoinTitle> - <GroupJoinText>글 모임에서 당신의 소중한 이야기를 들려주세요.</GroupJoinText> - </GroupJoinTitleWrapper> - <Spacing marginBottom="4.8" /> - <picture> - <source srcSet={groupJoinCongratsWebp} /> - <img src={groupJoinCongratsIlust} /> - </picture> - <Spacing marginBottom="4.8" /> - <GoToGroupFeedBtn onClick={onClickGoToGroupFeedBtn}>모임 페이지 보러가기</GoToGroupFeedBtn> - </GroupJoinCongratsContainer> - </GroupJoinCongratsWrapper> + <GroupJoinCongratsWrapper> + <Spacing marginBottom="15.6" /> + <GroupJoinCongratsContainer> + <GroupJoinTitleWrapper> + <GroupJoinTitle>{location?.state?.moimTitle} 가입을 축하해요!</GroupJoinTitle> + <GroupJoinText>글 모임에서 당신의 소중한 이야기를 들려주세요.</GroupJoinText> + </GroupJoinTitleWrapper> + <Spacing marginBottom="4.8" /> + <picture> + <source srcSet={groupJoinCongratsWebp} /> + <img src={groupJoinCongratsIlust} alt="가입 축하 일러스트" /> + </picture> + <Spacing marginBottom="4.8" /> + <GoToGroupFeedBtn onClick={onClickGoToGroupFeedBtn}> + 모임 페이지 보러가기 + </GoToGroupFeedBtn> + </GroupJoinCongratsContainer> + </GroupJoinCongratsWrapper> + </JoinWrapper> ); }; export default GroupJoinCongrats; +const JoinWrapper = styled.div` + display: flex; + width: 100%; +`; + const GroupJoinCongratsWrapper = styled.div` display: flex; flex-direction: column; align-items: center; justify-content: center; - width: 82.6rem; + width: 100%; `; const GroupJoinCongratsContainer = styled.main`