From 721bc34b17505737dcda01a4460fb03db6a4d4d0 Mon Sep 17 00:00:00 2001 From: YouD0313 <102004480+YouD0313@users.noreply.github.com> Date: Wed, 4 Jun 2025 15:40:57 +0900 Subject: [PATCH 01/10] chore: before merge, cherry pick commit --- .../admin/adminNotice}/AdminNoticeList.styled.ts | 2 +- .../admin/adminNotice}/AdminNoticeList.tsx | 12 ++++++------ .../adminNotice}/AdminNoticeWrite.styled.ts | 4 ++-- .../admin/adminNotice}/AdminNoticeWrite.tsx | 14 +++++++------- .../admin/searchBar/SearchBar.styled.ts | 0 .../{ => common}/admin/searchBar/SearchBar.tsx | 8 ++++---- ...otice.styled.ts => CommonAdminPage.styled.ts} | 2 +- src/pages/admin/CommonAdminPage.tsx | 16 ++++++++++++++++ .../AdminFAQ.styled.ts} | 0 src/pages/admin/adminFAQ/AdminFAQ.tsx | 0 src/pages/admin/adminNotice/AdminNotice.tsx | 11 ++--------- .../adminNoticeList/AdminNoticeListPage.tsx | 5 +++++ .../adminNoticeWrite/AdminNoticeWritePage.tsx | 5 +++++ src/routes/AdminRoutes.tsx | 5 +++-- 14 files changed, 52 insertions(+), 32 deletions(-) rename src/{pages/admin/adminNotice/adminNoticeList => components/admin/adminNotice}/AdminNoticeList.styled.ts (68%) rename src/{pages/admin/adminNotice/adminNoticeList => components/admin/adminNotice}/AdminNoticeList.tsx (76%) rename src/{pages/admin/adminNotice/adminNoticeWrite => components/admin/adminNotice}/AdminNoticeWrite.styled.ts (83%) rename src/{pages/admin/adminNotice/adminNoticeWrite => components/admin/adminNotice}/AdminNoticeWrite.tsx (86%) rename src/components/{ => common}/admin/searchBar/SearchBar.styled.ts (100%) rename src/components/{ => common}/admin/searchBar/SearchBar.tsx (90%) rename src/pages/admin/{adminNotice/AdminNotice.styled.ts => CommonAdminPage.styled.ts} (74%) create mode 100644 src/pages/admin/CommonAdminPage.tsx rename src/pages/admin/{adminNoticeDetail/AdminNoticeDetail.styled.ts => adminFAQ/AdminFAQ.styled.ts} (100%) create mode 100644 src/pages/admin/adminFAQ/AdminFAQ.tsx create mode 100644 src/pages/admin/adminNotice/adminNoticeList/AdminNoticeListPage.tsx create mode 100644 src/pages/admin/adminNotice/adminNoticeWrite/AdminNoticeWritePage.tsx diff --git a/src/pages/admin/adminNotice/adminNoticeList/AdminNoticeList.styled.ts b/src/components/admin/adminNotice/AdminNoticeList.styled.ts similarity index 68% rename from src/pages/admin/adminNotice/adminNoticeList/AdminNoticeList.styled.ts rename to src/components/admin/adminNotice/AdminNoticeList.styled.ts index c0db96bb..d4c6daf6 100644 --- a/src/pages/admin/adminNotice/adminNoticeList/AdminNoticeList.styled.ts +++ b/src/components/admin/adminNotice/AdminNoticeList.styled.ts @@ -1,5 +1,5 @@ import styled from 'styled-components'; -import { SpinnerWrapperStyled } from '../../../../components/user/mypage/Spinner.styled'; +import { SpinnerWrapperStyled } from '../../user/mypage/Spinner.styled'; export const SpinnerWrapper = styled(SpinnerWrapperStyled)``; diff --git a/src/pages/admin/adminNotice/adminNoticeList/AdminNoticeList.tsx b/src/components/admin/adminNotice/AdminNoticeList.tsx similarity index 76% rename from src/pages/admin/adminNotice/adminNoticeList/AdminNoticeList.tsx rename to src/components/admin/adminNotice/AdminNoticeList.tsx index 62378d87..3bb10740 100644 --- a/src/pages/admin/adminNotice/adminNoticeList/AdminNoticeList.tsx +++ b/src/components/admin/adminNotice/AdminNoticeList.tsx @@ -1,12 +1,12 @@ import { useEffect, useState } from 'react'; -import SearchBar from '../../../../components/admin/searchBar/SearchBar'; -import NoticeItem from '../../../user/customerService/notice/noticeItem/NoticeItem'; +import SearchBar from '../../../components/common/admin/searchBar/SearchBar'; import * as S from './AdminNoticeList.styled'; -import type { NoticeSearch } from '../../../../models/customerService'; -import { useGetNotice } from '../../../../hooks/user/useGetNotice'; +import type { NoticeSearch } from '../../../models/customerService'; +import { useGetNotice } from '../../../hooks/user/useGetNotice'; import { useSearchParams } from 'react-router-dom'; -import { Spinner } from '../../../../components/common/loadingSpinner/LoadingSpinner.styled'; -import Pagination from '../../../../components/common/pagination/Pagination'; +import Pagination from '../../../components/common/pagination/Pagination'; +import Spinner from '../../../components/user/mypage/Spinner'; +import NoticeItem from '../../../pages/user/customerService/notice/noticeItem/NoticeItem'; export default function AdminNoticeList() { const [noticeSearch, setNoticeSearch] = useState({ diff --git a/src/pages/admin/adminNotice/adminNoticeWrite/AdminNoticeWrite.styled.ts b/src/components/admin/adminNotice/AdminNoticeWrite.styled.ts similarity index 83% rename from src/pages/admin/adminNotice/adminNoticeWrite/AdminNoticeWrite.styled.ts rename to src/components/admin/adminNotice/AdminNoticeWrite.styled.ts index 96d78368..9d774685 100644 --- a/src/pages/admin/adminNotice/adminNoticeWrite/AdminNoticeWrite.styled.ts +++ b/src/components/admin/adminNotice/AdminNoticeWrite.styled.ts @@ -9,8 +9,8 @@ import { Content, SendButtonWrapper, SendButton, -} from '../../../../components/user/customerService/inquiry/Inquiry.styled'; -import { SpinnerWrapperStyled } from '../../../../components/user/mypage/Spinner.styled'; +} from '../../../components/user/customerService/inquiry/Inquiry.styled'; +import { SpinnerWrapperStyled } from '../../../components/user/mypage/Spinner.styled'; export const SpinnerWrapper = styled(SpinnerWrapperStyled)``; diff --git a/src/pages/admin/adminNotice/adminNoticeWrite/AdminNoticeWrite.tsx b/src/components/admin/adminNotice/AdminNoticeWrite.tsx similarity index 86% rename from src/pages/admin/adminNotice/adminNoticeWrite/AdminNoticeWrite.tsx rename to src/components/admin/adminNotice/AdminNoticeWrite.tsx index 7c8789df..e5c29ec9 100644 --- a/src/pages/admin/adminNotice/adminNoticeWrite/AdminNoticeWrite.tsx +++ b/src/components/admin/adminNotice/AdminNoticeWrite.tsx @@ -1,13 +1,13 @@ -import { INQUIRY_MESSAGE } from '../../../../constants/user/customerService'; +import { INQUIRY_MESSAGE } from '../../../constants/user/customerService'; import * as S from './AdminNoticeWrite.styled'; import React, { useEffect, useState } from 'react'; import { useLocation, useParams } from 'react-router-dom'; -import { useModal } from '../../../../hooks/useModal'; -import Modal from '../../../../components/common/modal/Modal'; -import type { WriteBody } from '../../../../models/customerService'; -import { useAdminNotice } from '../../../../hooks/admin/useAdminNotice'; -import { useGetNoticeDetail } from '../../../../hooks/user/useGetNoticeDetail'; -import Spinner from '../../../../components/user/mypage/Spinner'; +import { useModal } from '../../../hooks/useModal'; +import Modal from '../../../components/common/modal/Modal'; +import type { WriteBody } from '../../../models/customerService'; +import { useAdminNotice } from '../../../hooks/admin/useAdminNotice'; +import { useGetNoticeDetail } from '../../../hooks/user/useGetNoticeDetail'; +import Spinner from '../../../components/user/mypage/Spinner'; export default function AdminNoticeWrite() { const location = useLocation(); diff --git a/src/components/admin/searchBar/SearchBar.styled.ts b/src/components/common/admin/searchBar/SearchBar.styled.ts similarity index 100% rename from src/components/admin/searchBar/SearchBar.styled.ts rename to src/components/common/admin/searchBar/SearchBar.styled.ts diff --git a/src/components/admin/searchBar/SearchBar.tsx b/src/components/common/admin/searchBar/SearchBar.tsx similarity index 90% rename from src/components/admin/searchBar/SearchBar.tsx rename to src/components/common/admin/searchBar/SearchBar.tsx index cd5babc2..3b07c8ce 100644 --- a/src/components/admin/searchBar/SearchBar.tsx +++ b/src/components/common/admin/searchBar/SearchBar.tsx @@ -1,11 +1,11 @@ import { XMarkIcon } from '@heroicons/react/24/outline'; -import { MODAL_MESSAGE_CUSTOMER_SERVICE } from '../../../constants/user/customerService'; +import { MODAL_MESSAGE_CUSTOMER_SERVICE } from '../../../../constants/user/customerService'; import * as S from './SearchBar.styled'; import { useState } from 'react'; import { useLocation, useSearchParams } from 'react-router-dom'; -import { useModal } from '../../../hooks/useModal'; -import Modal from '../../common/modal/Modal'; -import { ADMIN_ROUTE } from '../../../constants/routes'; +import { useModal } from '../../../../hooks/useModal'; +import Modal from '../../modal/Modal'; +import { ADMIN_ROUTE } from '../../../../constants/routes'; interface SearchBarProps { onGetKeyword: (keyword: string) => void; diff --git a/src/pages/admin/adminNotice/AdminNotice.styled.ts b/src/pages/admin/CommonAdminPage.styled.ts similarity index 74% rename from src/pages/admin/adminNotice/AdminNotice.styled.ts rename to src/pages/admin/CommonAdminPage.styled.ts index 1bbefc66..f98efd8a 100644 --- a/src/pages/admin/adminNotice/AdminNotice.styled.ts +++ b/src/pages/admin/CommonAdminPage.styled.ts @@ -1,5 +1,5 @@ import styled from 'styled-components'; -import { SpinnerWrapperStyled } from '../../../components/user/mypage/Spinner.styled'; +import { SpinnerWrapperStyled } from '../../components/user/mypage/Spinner.styled'; export const SpinnerWrapper = styled(SpinnerWrapperStyled)``; diff --git a/src/pages/admin/CommonAdminPage.tsx b/src/pages/admin/CommonAdminPage.tsx new file mode 100644 index 00000000..dd00220f --- /dev/null +++ b/src/pages/admin/CommonAdminPage.tsx @@ -0,0 +1,16 @@ +import { Outlet } from 'react-router-dom'; +import AdminTitle from '../../components/common/admin/title/AdminTitle'; +import * as S from './CommonAdminPage.styled'; + +interface CommonAdminPageProps { + title: string; +} + +export default function CommonAdminPage({ title }: CommonAdminPageProps) { + return ( + + + + + ); +} diff --git a/src/pages/admin/adminNoticeDetail/AdminNoticeDetail.styled.ts b/src/pages/admin/adminFAQ/AdminFAQ.styled.ts similarity index 100% rename from src/pages/admin/adminNoticeDetail/AdminNoticeDetail.styled.ts rename to src/pages/admin/adminFAQ/AdminFAQ.styled.ts diff --git a/src/pages/admin/adminFAQ/AdminFAQ.tsx b/src/pages/admin/adminFAQ/AdminFAQ.tsx new file mode 100644 index 00000000..e69de29b diff --git a/src/pages/admin/adminNotice/AdminNotice.tsx b/src/pages/admin/adminNotice/AdminNotice.tsx index a3639e8c..8e456481 100644 --- a/src/pages/admin/adminNotice/AdminNotice.tsx +++ b/src/pages/admin/adminNotice/AdminNotice.tsx @@ -1,12 +1,5 @@ -import { Outlet } from 'react-router-dom'; -import AdminTitle from '../../../components/common/admin/title/AdminTitle'; -import * as S from './AdminNotice.styled'; +import CommonAdminPage from '../commonAdminPage'; export default function AdminNotice() { - return ( - - - - - ); + return ; } diff --git a/src/pages/admin/adminNotice/adminNoticeList/AdminNoticeListPage.tsx b/src/pages/admin/adminNotice/adminNoticeList/AdminNoticeListPage.tsx new file mode 100644 index 00000000..e66dc3cc --- /dev/null +++ b/src/pages/admin/adminNotice/adminNoticeList/AdminNoticeListPage.tsx @@ -0,0 +1,5 @@ +import AdminNoticeList from '../../../../components/admin/adminNotice/AdminNoticeList'; + +export default function AdminNoticeListPage() { + return ; +} diff --git a/src/pages/admin/adminNotice/adminNoticeWrite/AdminNoticeWritePage.tsx b/src/pages/admin/adminNotice/adminNoticeWrite/AdminNoticeWritePage.tsx new file mode 100644 index 00000000..d5e831bf --- /dev/null +++ b/src/pages/admin/adminNotice/adminNoticeWrite/AdminNoticeWritePage.tsx @@ -0,0 +1,5 @@ +import AdminNoticeWrite from '../../../../components/admin/adminNotice/AdminNoticeWrite'; + +export default function AdminNoticeWritePage() { + return ; +} diff --git a/src/routes/AdminRoutes.tsx b/src/routes/AdminRoutes.tsx index 539ce3cd..7ec5e7da 100644 --- a/src/routes/AdminRoutes.tsx +++ b/src/routes/AdminRoutes.tsx @@ -9,10 +9,11 @@ const Sidebar = lazy( const Main = lazy(() => import('../pages/admin/adminMain/AdminMain')); const Notice = lazy(() => import('../pages/admin/adminNotice/AdminNotice')); const NoticeList = lazy( - () => import('../pages/admin/adminNotice/adminNoticeList/AdminNoticeList') + () => import('../pages/admin/adminNotice/adminNoticeList/AdminNoticeListPage') ); const NoticeWrite = lazy( - () => import('../pages/admin/adminNotice/adminNoticeWrite/AdminNoticeWrite') + () => + import('../pages/admin/adminNotice/adminNoticeWrite/AdminNoticeWritePage') ); const NoticeDetail = lazy( () => import('../pages/admin/adminNoticeDetail/AdminNoticeDetail') From 5f0740ca0514b5b3e0b125c563d1c6c8e97312d5 Mon Sep 17 00:00:00 2001 From: YouD0313 <102004480+YouD0313@users.noreply.github.com> Date: Wed, 4 Jun 2025 18:14:24 +0900 Subject: [PATCH 02/10] =?UTF-8?q?feat:=20FAQ=20=EC=82=AC=EC=9D=B4=EB=93=9C?= =?UTF-8?q?=EB=B0=94=20=EC=B6=94=EA=B0=80,=20routes=20=EC=84=A4=EC=A0=95,?= =?UTF-8?q?=20=EA=B8=B0=EB=B3=B8=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=84=B8?= =?UTF-8?q?=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/admin/sidebar/sidebarList/AdminSidebarList.tsx | 2 ++ src/constants/admin/sidebar.ts | 5 +++++ src/constants/routes.ts | 1 + src/pages/admin/adminFAQ/AdminFAQ.tsx | 5 +++++ src/routes/AdminRoutes.tsx | 5 +++++ 5 files changed, 18 insertions(+) diff --git a/src/components/common/admin/sidebar/sidebarList/AdminSidebarList.tsx b/src/components/common/admin/sidebar/sidebarList/AdminSidebarList.tsx index 2544768d..19caf835 100644 --- a/src/components/common/admin/sidebar/sidebarList/AdminSidebarList.tsx +++ b/src/components/common/admin/sidebar/sidebarList/AdminSidebarList.tsx @@ -5,6 +5,7 @@ import { EnvelopeIcon, ExclamationTriangleIcon, HomeIcon, + LightBulbIcon, MegaphoneIcon, PhotoIcon, TagIcon, @@ -15,6 +16,7 @@ const iconMap = { mainPage: , movedSite: , notice: , + faq: , banner: , tags: , allUser: , diff --git a/src/constants/admin/sidebar.ts b/src/constants/admin/sidebar.ts index 55535b2a..146d4e83 100644 --- a/src/constants/admin/sidebar.ts +++ b/src/constants/admin/sidebar.ts @@ -19,6 +19,11 @@ export const SIDEBAR_LIST = { title: '공지사항', router: ADMIN_ROUTE.notice, }, + { + name: 'faq', + title: 'FAQ', + router: ADMIN_ROUTE.faq, + }, { name: 'banner', title: '배너관리', diff --git a/src/constants/routes.ts b/src/constants/routes.ts index f65de3b6..d27463c3 100644 --- a/src/constants/routes.ts +++ b/src/constants/routes.ts @@ -35,6 +35,7 @@ export const ADMIN_ROUTE = { admin: '/admin', devPals: '/main', notice: 'notice', + faq: 'faq', banner: 'banner', tags: 'tags', allUser: 'all-user', diff --git a/src/pages/admin/adminFAQ/AdminFAQ.tsx b/src/pages/admin/adminFAQ/AdminFAQ.tsx index e69de29b..92bf2f3a 100644 --- a/src/pages/admin/adminFAQ/AdminFAQ.tsx +++ b/src/pages/admin/adminFAQ/AdminFAQ.tsx @@ -0,0 +1,5 @@ +import CommonAdminPage from '../commonAdminPage'; + +export default function AdminFAQ() { + return ; +} diff --git a/src/routes/AdminRoutes.tsx b/src/routes/AdminRoutes.tsx index 7ec5e7da..ebf09458 100644 --- a/src/routes/AdminRoutes.tsx +++ b/src/routes/AdminRoutes.tsx @@ -18,6 +18,7 @@ const NoticeWrite = lazy( const NoticeDetail = lazy( () => import('../pages/admin/adminNoticeDetail/AdminNoticeDetail') ); +const FAQ = lazy(() => import('../pages/admin/adminFAQ/AdminFAQ')); const Banner = lazy(() => import('../pages/admin/adminBanner/AdminBanner')); const Tags = lazy(() => import('../pages/admin/adminTags/AdminTags')); const AllUser = lazy(() => import('../pages/admin/adminAllUser/AdminAllUser')); @@ -60,6 +61,10 @@ export const AdminRoutes = () => { }, ], }, + { + path: ADMIN_ROUTE.faq, + element: , + }, { path: ADMIN_ROUTE.banner, element: , From f2b38958220ca67c7252a4e706c74eaf55fc3ba8 Mon Sep 17 00:00:00 2001 From: YouD0313 <102004480+YouD0313@users.noreply.github.com> Date: Thu, 5 Jun 2025 16:20:44 +0900 Subject: [PATCH 03/10] =?UTF-8?q?feat:=20FAQ=20=EA=B4=80=EB=A6=AC=EC=9E=90?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=80=EC=97=90=EC=84=9C=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=20=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/admin/customerService/FAQ.api.ts | 36 +++++++++++ .../admin/adminFAQ/AdminFAQList.styled.ts | 4 ++ .../admin/adminFAQ/AdminFAQList.tsx | 32 ++++++++++ .../admin/adminFAQ}/AdminFAQWrite.tsx | 0 .../admin/adminNotice/AdminNoticeList.tsx | 2 +- .../customerService/faq/FAQItem.styled.ts | 62 +++++++++++++++++++ .../user/customerService/faq/FAQItem.tsx | 49 +++++++++++++++ .../faq/{ => faqContent}/FAQContent.styled.ts | 0 .../faq/{ => faqContent}/FAQContent.tsx | 2 +- .../notice/noticeItem/NoticeItem.styled.ts | 0 .../notice/noticeItem/NoticeItem.tsx | 6 +- src/hooks/admin/useAdminFAQ.ts | 39 ++++++++++++ src/pages/admin/adminFAQ/AdminFAQ.styled.ts | 0 .../adminFAQ/adminFAQList/AdminFAQList.tsx | 16 ----- .../adminFAQList/AdminFAQListPage.tsx | 5 ++ .../adminFAQWrite/AdminFAQWritePage.tsx | 5 ++ src/pages/admin/adminNotice/AdminNotice.tsx | 2 +- .../user/customerService/faq/FAQ.styled.ts | 58 ----------------- src/pages/user/customerService/faq/FAQ.tsx | 37 +---------- .../user/customerService/notice/Notice.tsx | 2 +- src/routes/AdminRoutes.tsx | 4 +- 21 files changed, 243 insertions(+), 118 deletions(-) create mode 100644 src/api/admin/customerService/FAQ.api.ts create mode 100644 src/components/admin/adminFAQ/AdminFAQList.styled.ts create mode 100644 src/components/admin/adminFAQ/AdminFAQList.tsx rename src/{pages/admin/adminFAQ/adminFAQWrite => components/admin/adminFAQ}/AdminFAQWrite.tsx (100%) create mode 100644 src/components/user/customerService/faq/FAQItem.styled.ts create mode 100644 src/components/user/customerService/faq/FAQItem.tsx rename src/components/user/customerService/faq/{ => faqContent}/FAQContent.styled.ts (100%) rename src/components/user/customerService/faq/{ => faqContent}/FAQContent.tsx (93%) rename src/{pages => components}/user/customerService/notice/noticeItem/NoticeItem.styled.ts (100%) rename src/{pages => components}/user/customerService/notice/noticeItem/NoticeItem.tsx (83%) create mode 100644 src/hooks/admin/useAdminFAQ.ts delete mode 100644 src/pages/admin/adminFAQ/AdminFAQ.styled.ts delete mode 100644 src/pages/admin/adminFAQ/adminFAQList/AdminFAQList.tsx create mode 100644 src/pages/admin/adminFAQ/adminFAQList/AdminFAQListPage.tsx create mode 100644 src/pages/admin/adminFAQ/adminFAQWrite/AdminFAQWritePage.tsx diff --git a/src/api/admin/customerService/FAQ.api.ts b/src/api/admin/customerService/FAQ.api.ts new file mode 100644 index 00000000..255eee11 --- /dev/null +++ b/src/api/admin/customerService/FAQ.api.ts @@ -0,0 +1,36 @@ +import { ApiCommonBasicType } from '../../../models/apiCommon'; +import type { WriteBody } from '../../../models/customerService'; +import { httpClient } from '../../http.api'; + +export const postFAQ = async (formData: WriteBody) => { + try { + await httpClient.post(`faq`, formData); + } catch (e) { + console.error(e); + throw e; + } +}; + +export const putFAQ = async ({ + id, + formData, +}: { + id: number; + formData: WriteBody; +}) => { + try { + await httpClient.put(`faq/${id}`, formData); + } catch (e) { + console.error(e); + throw e; + } +}; + +export const deleteFAQ = async (id: number) => { + try { + await httpClient.delete(`faq/${id}`); + } catch (e) { + console.error(e); + throw e; + } +}; diff --git a/src/components/admin/adminFAQ/AdminFAQList.styled.ts b/src/components/admin/adminFAQ/AdminFAQList.styled.ts new file mode 100644 index 00000000..fa1db571 --- /dev/null +++ b/src/components/admin/adminFAQ/AdminFAQList.styled.ts @@ -0,0 +1,4 @@ +import styled from 'styled-components'; +import { SpinnerWrapperStyled } from '../../user/mypage/Spinner.styled'; + +export const SpinnerWrapper = styled(SpinnerWrapperStyled)``; diff --git a/src/components/admin/adminFAQ/AdminFAQList.tsx b/src/components/admin/adminFAQ/AdminFAQList.tsx new file mode 100644 index 00000000..f98447ae --- /dev/null +++ b/src/components/admin/adminFAQ/AdminFAQList.tsx @@ -0,0 +1,32 @@ +import { useState } from 'react'; +import * as S from './AdminFAQList.styled'; +import SearchBar from '../../../components/common/admin/searchBar/SearchBar'; +import FAQItem from '../../user/customerService/faq/FAQItem'; +import { useGetFAQ } from '../../../hooks/user/useGetFAQ'; +import Spinner from '../../user/mypage/Spinner'; + +export default function AdminFAQList() { + const [keyword, setKeyword] = useState(''); + const { faqData, isLoading } = useGetFAQ({ keyword }); + + const handleGetKeyword = (keyword: string) => { + setKeyword(keyword); + }; + + if (isLoading) { + return ( + + + + ); + } + + if (!faqData) return; + + return ( + <> + + + + ); +} diff --git a/src/pages/admin/adminFAQ/adminFAQWrite/AdminFAQWrite.tsx b/src/components/admin/adminFAQ/AdminFAQWrite.tsx similarity index 100% rename from src/pages/admin/adminFAQ/adminFAQWrite/AdminFAQWrite.tsx rename to src/components/admin/adminFAQ/AdminFAQWrite.tsx diff --git a/src/components/admin/adminNotice/AdminNoticeList.tsx b/src/components/admin/adminNotice/AdminNoticeList.tsx index 3bb10740..39086da4 100644 --- a/src/components/admin/adminNotice/AdminNoticeList.tsx +++ b/src/components/admin/adminNotice/AdminNoticeList.tsx @@ -6,7 +6,7 @@ import { useGetNotice } from '../../../hooks/user/useGetNotice'; import { useSearchParams } from 'react-router-dom'; import Pagination from '../../../components/common/pagination/Pagination'; import Spinner from '../../../components/user/mypage/Spinner'; -import NoticeItem from '../../../pages/user/customerService/notice/noticeItem/NoticeItem'; +import NoticeItem from '../../user/customerService/notice/noticeItem/NoticeItem'; export default function AdminNoticeList() { const [noticeSearch, setNoticeSearch] = useState({ diff --git a/src/components/user/customerService/faq/FAQItem.styled.ts b/src/components/user/customerService/faq/FAQItem.styled.ts new file mode 100644 index 00000000..4992e57d --- /dev/null +++ b/src/components/user/customerService/faq/FAQItem.styled.ts @@ -0,0 +1,62 @@ +import styled from 'styled-components'; +import { SpinnerWrapperStyled } from '../../../../components/user/mypage/Spinner.styled'; + +export const SpinnerWrapper = styled(SpinnerWrapperStyled)``; + +export const Container = styled.section` + margin-top: 2rem; + margin-bottom: 5rem; + width: 100%; + display: flex; + justify-content: center; +`; + +export const Wrapper = styled.div<{ $isAdmin: boolean }>` + width: ${({ $isAdmin }) => ($isAdmin ? '90%' : '75%')}; + display: flex; + flex-direction: column; +`; + +export const ToggleWrapper = styled.div``; + +export const ShowMoreFAQWrapper = styled.div``; + +export const ShowMoreFAQ = styled.button` + width: 100%; + display: flex; + justify-content: center; + align-items: center; + font-weight: bold; + font-size: 1rem; + + svg { + width: 1rem; + } + + &:hover { + background: ${({ theme }) => theme.color.lightgrey}; + } +`; + +export const ShowMoreSpan = styled.span` + width: 100%; + padding: 1.2rem 0; + display: flex; + justify-content: center; + gap: 0.5rem; + @keyframes bounce { + 0% { + transform: translateY(0); + } + 50% { + transform: translateY(-3px); + } + 100% { + transform: translateY(0); + } + } + + &:hover { + animation: bounce 0.4s infinite; + } +`; diff --git a/src/components/user/customerService/faq/FAQItem.tsx b/src/components/user/customerService/faq/FAQItem.tsx new file mode 100644 index 00000000..6c2b0b73 --- /dev/null +++ b/src/components/user/customerService/faq/FAQItem.tsx @@ -0,0 +1,49 @@ +import { useState } from 'react'; +import * as S from './FAQItem.styled'; +import type { FAQ } from '../../../../models/customerService'; +import FAQContent from './faqContent/FAQContent'; +import ContentBorder from '../../../common/contentBorder/ContentBorder'; +import NoResult from '../../../common/noResult/NoResult'; +import { ChevronDownIcon } from '@heroicons/react/24/outline'; + +interface FAQItemProps { + faqData: FAQ[]; + $isAdmin?: boolean; +} + +export default function FAQItem({ faqData, $isAdmin = false }: FAQItemProps) { + const [showFAQ, setShowFAQ] = useState(10); + + return ( + + + {faqData.length > 0 ? ( + faqData + .filter((_, index) => index < showFAQ) + .map((list) => ( + + + + + )) + ) : ( + + )} + {faqData.length > showFAQ && ( + <> + setShowFAQ((prev) => prev + 10)} + > + + 더보기 + + + + + + )} + + + ); +} diff --git a/src/components/user/customerService/faq/FAQContent.styled.ts b/src/components/user/customerService/faq/faqContent/FAQContent.styled.ts similarity index 100% rename from src/components/user/customerService/faq/FAQContent.styled.ts rename to src/components/user/customerService/faq/faqContent/FAQContent.styled.ts diff --git a/src/components/user/customerService/faq/FAQContent.tsx b/src/components/user/customerService/faq/faqContent/FAQContent.tsx similarity index 93% rename from src/components/user/customerService/faq/FAQContent.tsx rename to src/components/user/customerService/faq/faqContent/FAQContent.tsx index bf972785..2d00a8f6 100644 --- a/src/components/user/customerService/faq/FAQContent.tsx +++ b/src/components/user/customerService/faq/faqContent/FAQContent.tsx @@ -1,5 +1,5 @@ import { ChevronRightIcon, PlusIcon } from '@heroicons/react/24/outline'; -import type { FAQ } from '../../../../models/customerService'; +import type { FAQ } from '../../../../../models/customerService'; import * as S from './FAQContent.styled'; import { useState } from 'react'; diff --git a/src/pages/user/customerService/notice/noticeItem/NoticeItem.styled.ts b/src/components/user/customerService/notice/noticeItem/NoticeItem.styled.ts similarity index 100% rename from src/pages/user/customerService/notice/noticeItem/NoticeItem.styled.ts rename to src/components/user/customerService/notice/noticeItem/NoticeItem.styled.ts diff --git a/src/pages/user/customerService/notice/noticeItem/NoticeItem.tsx b/src/components/user/customerService/notice/noticeItem/NoticeItem.tsx similarity index 83% rename from src/pages/user/customerService/notice/noticeItem/NoticeItem.tsx rename to src/components/user/customerService/notice/noticeItem/NoticeItem.tsx index 1806031a..7716f399 100644 --- a/src/pages/user/customerService/notice/noticeItem/NoticeItem.tsx +++ b/src/components/user/customerService/notice/noticeItem/NoticeItem.tsx @@ -1,8 +1,8 @@ import * as S from './NoticeItem.styled'; -import ContentBorder from '../../../../../components/common/contentBorder/ContentBorder'; +import ContentBorder from '../../../../common/contentBorder/ContentBorder'; import { ADMIN_ROUTE, ROUTES } from '../../../../../constants/routes'; -import NoticeList from '../../../../../components/user/customerService/notice/NoticeList'; -import NoResult from '../../../../../components/common/noResult/NoResult'; +import NoticeList from '../NoticeList'; +import NoResult from '../../../../common/noResult/NoResult'; import type { NoticeList as TNoticeList } from '../../../../../models/customerService'; import { useLocation } from 'react-router-dom'; diff --git a/src/hooks/admin/useAdminFAQ.ts b/src/hooks/admin/useAdminFAQ.ts new file mode 100644 index 00000000..f1d2a6a7 --- /dev/null +++ b/src/hooks/admin/useAdminFAQ.ts @@ -0,0 +1,39 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { + deleteFAQ, + postFAQ, + putFAQ, +} from '../../api/admin/customerService/FAQ.api'; +import type { WriteBody } from '../../models/customerService'; +import { AxiosError } from 'axios'; + +export const useAdminFAQ = () => { + const queryClient = useQueryClient(); + + const postFAQMutate = useMutation({ + mutationFn: (formData: WriteBody) => postFAQ(formData), + onSuccess: () => { + queryClient.invalidateQueries(); + }, + }); + + const putFAQMutate = useMutation< + void, + AxiosError, + { id: number; formData: WriteBody } + >({ + mutationFn: ({ id, formData }) => putFAQ({ id, formData }), + onSuccess: () => { + queryClient.invalidateQueries(); + }, + }); + + const deleteFAQMutate = useMutation({ + mutationFn: (id: number) => deleteFAQ(id), + onSuccess: () => { + queryClient.invalidateQueries(); + }, + }); + + return { postFAQMutate, putFAQMutate, deleteFAQMutate }; +}; diff --git a/src/pages/admin/adminFAQ/AdminFAQ.styled.ts b/src/pages/admin/adminFAQ/AdminFAQ.styled.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/pages/admin/adminFAQ/adminFAQList/AdminFAQList.tsx b/src/pages/admin/adminFAQ/adminFAQList/AdminFAQList.tsx deleted file mode 100644 index f320ca38..00000000 --- a/src/pages/admin/adminFAQ/adminFAQList/AdminFAQList.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { useState } from 'react'; -import SearchBar from '../../../../components/common/admin/searchBar/SearchBar'; - -export default function AdminFAQList() { - const [value, setValue] = useState(''); - - const handleGetKeyword = (keyword: string) => { - setValue(keyword); - }; - - return ( - <> - - - ); -} diff --git a/src/pages/admin/adminFAQ/adminFAQList/AdminFAQListPage.tsx b/src/pages/admin/adminFAQ/adminFAQList/AdminFAQListPage.tsx new file mode 100644 index 00000000..0dc4134b --- /dev/null +++ b/src/pages/admin/adminFAQ/adminFAQList/AdminFAQListPage.tsx @@ -0,0 +1,5 @@ +import AdminFAQList from '../../../../components/admin/adminFAQ/AdminFAQList'; + +export default function AdminFAQListPage() { + return ; +} diff --git a/src/pages/admin/adminFAQ/adminFAQWrite/AdminFAQWritePage.tsx b/src/pages/admin/adminFAQ/adminFAQWrite/AdminFAQWritePage.tsx new file mode 100644 index 00000000..2d19dcc3 --- /dev/null +++ b/src/pages/admin/adminFAQ/adminFAQWrite/AdminFAQWritePage.tsx @@ -0,0 +1,5 @@ +import AdminFAQWrite from '../../../../components/admin/adminFAQ/AdminFAQWrite'; + +export default function AdminFAQWritePage() { + return ; +} diff --git a/src/pages/admin/adminNotice/AdminNotice.tsx b/src/pages/admin/adminNotice/AdminNotice.tsx index 8e456481..aa5519ca 100644 --- a/src/pages/admin/adminNotice/AdminNotice.tsx +++ b/src/pages/admin/adminNotice/AdminNotice.tsx @@ -1,4 +1,4 @@ -import CommonAdminPage from '../commonAdminPage'; +import CommonAdminPage from '../CommonAdminPage'; export default function AdminNotice() { return ; diff --git a/src/pages/user/customerService/faq/FAQ.styled.ts b/src/pages/user/customerService/faq/FAQ.styled.ts index 5cdddc6b..477b7e92 100644 --- a/src/pages/user/customerService/faq/FAQ.styled.ts +++ b/src/pages/user/customerService/faq/FAQ.styled.ts @@ -2,61 +2,3 @@ import styled from 'styled-components'; import { SpinnerWrapperStyled } from '../../../../components/user/mypage/Spinner.styled'; export const SpinnerWrapper = styled(SpinnerWrapperStyled)``; - -export const Container = styled.section` - margin-top: 2rem; - margin-bottom: 5rem; - width: 100%; - display: flex; - justify-content: center; -`; - -export const Wrapper = styled.div` - width: 75%; - display: flex; - flex-direction: column; -`; - -export const ToggleWrapper = styled.div``; - -export const ShowMoreFAQWrapper = styled.div``; - -export const ShowMoreFAQ = styled.button` - width: 100%; - display: flex; - justify-content: center; - align-items: center; - font-weight: bold; - font-size: 1rem; - - svg { - width: 1rem; - } - - &:hover { - background: ${({ theme }) => theme.color.lightgrey}; - } -`; - -export const ShowMoreSpan = styled.span` - width: 100%; - padding: 1.2rem 0; - display: flex; - justify-content: center; - gap: 0.5rem; - @keyframes bounce { - 0% { - transform: translateY(0); - } - 50% { - transform: translateY(-3px); - } - 100% { - transform: translateY(0); - } - } - - &:hover { - animation: bounce 0.4s infinite; - } -`; diff --git a/src/pages/user/customerService/faq/FAQ.tsx b/src/pages/user/customerService/faq/FAQ.tsx index f064924d..7f45cec7 100644 --- a/src/pages/user/customerService/faq/FAQ.tsx +++ b/src/pages/user/customerService/faq/FAQ.tsx @@ -1,18 +1,14 @@ import { useState } from 'react'; import * as S from './FAQ.styled'; -import { ChevronDownIcon } from '@heroicons/react/24/outline'; import type { SearchKeyword } from '../../../../models/customerService'; import { useGetFAQ } from '../../../../hooks/user/useGetFAQ'; import { Spinner } from '../../../../components/common/loadingSpinner/LoadingSpinner.styled'; import CustomerServiceHeader from '../../../../components/user/customerService/CustomerServiceHeader'; -import FAQContent from '../../../../components/user/customerService/faq/FAQContent'; -import ContentBorder from '../../../../components/common/contentBorder/ContentBorder'; -import NoResult from '../../../../components/common/noResult/NoResult'; +import FAQItem from '../../../../components/user/customerService/faq/FAQItem'; export default function FAQ() { const [keyword, setKeyword] = useState({ keyword: '' }); const [value, setValue] = useState(''); - const [showFAQ, setShowFAQ] = useState(10); const { faqData, isLoading } = useGetFAQ(keyword); const handleGetKeyword = (keyword: string) => { @@ -37,36 +33,7 @@ export default function FAQ() { keyword={value} onGetKeyword={handleGetKeyword} /> - - - {faqData.length > 0 ? ( - faqData - .filter((_, index) => index < showFAQ) - .map((list) => ( - - - - - )) - ) : ( - - )} - {faqData.length > showFAQ && ( - <> - setShowFAQ((prev) => prev + 10)} - > - - 더보기 - - - - - - )} - - + ); } diff --git a/src/pages/user/customerService/notice/Notice.tsx b/src/pages/user/customerService/notice/Notice.tsx index e78bf6cc..0d0ef219 100644 --- a/src/pages/user/customerService/notice/Notice.tsx +++ b/src/pages/user/customerService/notice/Notice.tsx @@ -6,7 +6,7 @@ import { Spinner } from '../../../../components/common/loadingSpinner/LoadingSpi import CustomerServiceHeader from '../../../../components/user/customerService/CustomerServiceHeader'; import Pagination from '../../../../components/common/pagination/Pagination'; import { useSearchParams } from 'react-router-dom'; -import NoticeItem from './noticeItem/NoticeItem'; +import NoticeItem from '../../../../components/user/customerService/notice/noticeItem/NoticeItem'; export default function Notice() { const [noticeSearch, setNoticeSearch] = useState({ diff --git a/src/routes/AdminRoutes.tsx b/src/routes/AdminRoutes.tsx index ce53d4ed..b8db82a0 100644 --- a/src/routes/AdminRoutes.tsx +++ b/src/routes/AdminRoutes.tsx @@ -20,10 +20,10 @@ const NoticeDetail = lazy( ); const FAQ = lazy(() => import('../pages/admin/adminFAQ/AdminFAQ')); const FAQList = lazy( - () => import('../pages/admin/adminFAQ/adminFAQList/AdminFAQList') + () => import('../pages/admin/adminFAQ/adminFAQList/AdminFAQListPage') ); const FAQWrite = lazy( - () => import('../pages/admin/adminFAQ/adminFAQWrite/AdminFAQWrite') + () => import('../pages/admin/adminFAQ/adminFAQWrite/AdminFAQWritePage') ); const Banner = lazy(() => import('../pages/admin/adminBanner/AdminBanner')); const Tags = lazy(() => import('../pages/admin/adminTags/AdminTags')); From 1d6380a0454ee94cee9ee1c098c760b23cfb57b0 Mon Sep 17 00:00:00 2001 From: YouD0313 <102004480+YouD0313@users.noreply.github.com> Date: Thu, 5 Jun 2025 16:25:28 +0900 Subject: [PATCH 04/10] design: margin-bottom 5rem --- src/components/user/customerService/faq/FAQItem.styled.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/user/customerService/faq/FAQItem.styled.ts b/src/components/user/customerService/faq/FAQItem.styled.ts index 4992e57d..b1fe555d 100644 --- a/src/components/user/customerService/faq/FAQItem.styled.ts +++ b/src/components/user/customerService/faq/FAQItem.styled.ts @@ -5,7 +5,6 @@ export const SpinnerWrapper = styled(SpinnerWrapperStyled)``; export const Container = styled.section` margin-top: 2rem; - margin-bottom: 5rem; width: 100%; display: flex; justify-content: center; @@ -15,6 +14,7 @@ export const Wrapper = styled.div<{ $isAdmin: boolean }>` width: ${({ $isAdmin }) => ($isAdmin ? '90%' : '75%')}; display: flex; flex-direction: column; + margin-bottom: 5rem; `; export const ToggleWrapper = styled.div``; From dfad6978022287589ae9d9074ba52b07835fc7c9 Mon Sep 17 00:00:00 2001 From: YouD0313 <102004480+YouD0313@users.noreply.github.com> Date: Thu, 5 Jun 2025 16:32:46 +0900 Subject: [PATCH 05/10] =?UTF-8?q?disign:=20=EC=82=AC=EC=9D=B4=EB=93=9C?= =?UTF-8?q?=EB=B0=94=20=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EA=B3=A0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/admin/sidebar/AdminSidebar.styled.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/common/admin/sidebar/AdminSidebar.styled.ts b/src/components/common/admin/sidebar/AdminSidebar.styled.ts index 726be486..1cfbaae6 100644 --- a/src/components/common/admin/sidebar/AdminSidebar.styled.ts +++ b/src/components/common/admin/sidebar/AdminSidebar.styled.ts @@ -9,9 +9,11 @@ export const LayoutContainer = styled.div` export const ContainerArea = styled.section` flex: 1; padding: 2rem; + margin-left: 15rem; `; export const SidebarContainer = styled.section` + position: fixed; padding: 1rem; width: 15rem; border-right: 1px solid ${({ theme }) => theme.color.grey}; From 5fd6482888b0cb5f8527873ef6f47ae68898beae Mon Sep 17 00:00:00 2001 From: YouD0313 <102004480+YouD0313@users.noreply.github.com> Date: Thu, 5 Jun 2025 17:14:41 +0900 Subject: [PATCH 06/10] design: adminTitle fixed, FAQ searchBar fixed --- src/components/admin/adminFAQ/AdminFAQList.styled.ts | 7 +++++++ src/components/admin/adminFAQ/AdminFAQList.tsx | 8 ++++++-- .../admin/adminNotice/AdminNoticeList.styled.ts | 1 + .../common/admin/searchBar/SearchBar.styled.ts | 11 ++++++++++- .../common/admin/sidebar/AdminSidebar.styled.ts | 1 + .../common/admin/title/AdminTitle.styled.ts | 7 ++++++- src/pages/admin/CommonAdminPage.styled.ts | 7 ++++--- src/pages/admin/CommonAdminPage.tsx | 8 ++++++-- 8 files changed, 41 insertions(+), 9 deletions(-) diff --git a/src/components/admin/adminFAQ/AdminFAQList.styled.ts b/src/components/admin/adminFAQ/AdminFAQList.styled.ts index fa1db571..c9333e0a 100644 --- a/src/components/admin/adminFAQ/AdminFAQList.styled.ts +++ b/src/components/admin/adminFAQ/AdminFAQList.styled.ts @@ -1,4 +1,11 @@ import styled from 'styled-components'; import { SpinnerWrapperStyled } from '../../user/mypage/Spinner.styled'; +import { SearchBarFixedWrapperStyled } from '../../common/admin/searchBar/SearchBar.styled'; export const SpinnerWrapper = styled(SpinnerWrapperStyled)``; + +export const SearchBarFixedWrapper = styled(SearchBarFixedWrapperStyled)``; + +export const FAQItemWrapper = styled.div` + margin-top: 11rem; +`; diff --git a/src/components/admin/adminFAQ/AdminFAQList.tsx b/src/components/admin/adminFAQ/AdminFAQList.tsx index f98447ae..23025e1e 100644 --- a/src/components/admin/adminFAQ/AdminFAQList.tsx +++ b/src/components/admin/adminFAQ/AdminFAQList.tsx @@ -25,8 +25,12 @@ export default function AdminFAQList() { return ( <> - - + + + + + + ); } diff --git a/src/components/admin/adminNotice/AdminNoticeList.styled.ts b/src/components/admin/adminNotice/AdminNoticeList.styled.ts index 41e66ab5..7459c257 100644 --- a/src/components/admin/adminNotice/AdminNoticeList.styled.ts +++ b/src/components/admin/adminNotice/AdminNoticeList.styled.ts @@ -8,4 +8,5 @@ export const SpinnerWrapper = styled(SpinnerWrapperStyled)` export const NoticeItemWrapper = styled.section` display: flex; justify-content: center; + margin-top: 2rem; `; diff --git a/src/components/common/admin/searchBar/SearchBar.styled.ts b/src/components/common/admin/searchBar/SearchBar.styled.ts index 9c432908..f6499b7c 100644 --- a/src/components/common/admin/searchBar/SearchBar.styled.ts +++ b/src/components/common/admin/searchBar/SearchBar.styled.ts @@ -5,7 +5,7 @@ export const AdminSearchBarContainer = styled.form` width: 100%; display: flex; justify-content: space-evenly; - margin-bottom: 2rem; + margin-bottom: 1rem; `; export const AdminSearchBarWrapper = styled.div` @@ -60,3 +60,12 @@ export const WriteLink = styled(Link)` color: ${({ theme }) => theme.color.navy}; } `; + +export const SearchBarFixedWrapperStyled = styled.div` + width: calc(100vw - 20rem); + position: fixed; + top: 0; + padding-top: 9rem; + background: ${({ theme }) => theme.color.white}; + z-index: 10; +`; diff --git a/src/components/common/admin/sidebar/AdminSidebar.styled.ts b/src/components/common/admin/sidebar/AdminSidebar.styled.ts index 1cfbaae6..70bd5356 100644 --- a/src/components/common/admin/sidebar/AdminSidebar.styled.ts +++ b/src/components/common/admin/sidebar/AdminSidebar.styled.ts @@ -8,6 +8,7 @@ export const LayoutContainer = styled.div` export const ContainerArea = styled.section` flex: 1; + width: 100%; padding: 2rem; margin-left: 15rem; `; diff --git a/src/components/common/admin/title/AdminTitle.styled.ts b/src/components/common/admin/title/AdminTitle.styled.ts index fcaa1c05..a2a6ef56 100644 --- a/src/components/common/admin/title/AdminTitle.styled.ts +++ b/src/components/common/admin/title/AdminTitle.styled.ts @@ -1,7 +1,12 @@ import styled from 'styled-components'; export const TitleContainer = styled.header` - margin-bottom: 1rem; + width: calc(100vw - 20rem); + position: fixed; + top: 0; + padding: 2.5rem 0 0 0; + background: ${({ theme }) => theme.color.white}; + z-index: 100; `; export const TitleWrapper = styled.div` diff --git a/src/pages/admin/CommonAdminPage.styled.ts b/src/pages/admin/CommonAdminPage.styled.ts index f98efd8a..286792b0 100644 --- a/src/pages/admin/CommonAdminPage.styled.ts +++ b/src/pages/admin/CommonAdminPage.styled.ts @@ -5,7 +5,8 @@ export const SpinnerWrapper = styled(SpinnerWrapperStyled)``; export const AdminNoticeContainer = styled.div``; -export const NoticeItemWrapper = styled.section` - display: flex; - justify-content: center; +export const FixedTitle = styled.div``; + +export const OutletWrapper = styled.div` + margin-top: 7rem; `; diff --git a/src/pages/admin/CommonAdminPage.tsx b/src/pages/admin/CommonAdminPage.tsx index dd00220f..2d71a100 100644 --- a/src/pages/admin/CommonAdminPage.tsx +++ b/src/pages/admin/CommonAdminPage.tsx @@ -9,8 +9,12 @@ interface CommonAdminPageProps { export default function CommonAdminPage({ title }: CommonAdminPageProps) { return ( - - + + + + + + ); } From da63bd623ad648b379de993ded51a51f8b5b87a7 Mon Sep 17 00:00:00 2001 From: YouD0313 <102004480+YouD0313@users.noreply.github.com> Date: Thu, 5 Jun 2025 22:57:23 +0900 Subject: [PATCH 07/10] =?UTF-8?q?feat:=20faq=20=EC=9E=91=EC=84=B1,=20?= =?UTF-8?q?=EC=88=98=EC=A0=95,=20=EC=82=AD=EC=A0=9C=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/admin/customerService/FAQ.api.ts | 17 ++- .../admin/adminFAQ/AdminFAQWrite.tsx | 123 +++++++++++++++++- .../user/customerService/faq/FAQItem.tsx | 2 +- .../faq/faqContent/FAQContent.styled.ts | 30 +++++ .../faq/faqContent/FAQContent.tsx | 50 ++++++- src/hooks/admin/useAdminFAQ.ts | 91 +++++++++++-- src/models/customerService.ts | 8 ++ 7 files changed, 304 insertions(+), 17 deletions(-) diff --git a/src/api/admin/customerService/FAQ.api.ts b/src/api/admin/customerService/FAQ.api.ts index 255eee11..85bf8995 100644 --- a/src/api/admin/customerService/FAQ.api.ts +++ b/src/api/admin/customerService/FAQ.api.ts @@ -1,7 +1,18 @@ import { ApiCommonBasicType } from '../../../models/apiCommon'; -import type { WriteBody } from '../../../models/customerService'; +import type { ApiFAQDetail, WriteBody } from '../../../models/customerService'; import { httpClient } from '../../http.api'; +export const getFAQDetail = async (id: string) => { + try { + const response = await httpClient.get(`/faq/${id}`); + + return response.data.data; + } catch (e) { + console.error(e); + throw e; + } +}; + export const postFAQ = async (formData: WriteBody) => { try { await httpClient.post(`faq`, formData); @@ -15,7 +26,7 @@ export const putFAQ = async ({ id, formData, }: { - id: number; + id: string; formData: WriteBody; }) => { try { @@ -26,7 +37,7 @@ export const putFAQ = async ({ } }; -export const deleteFAQ = async (id: number) => { +export const deleteFAQ = async (id: string) => { try { await httpClient.delete(`faq/${id}`); } catch (e) { diff --git a/src/components/admin/adminFAQ/AdminFAQWrite.tsx b/src/components/admin/adminFAQ/AdminFAQWrite.tsx index 40f74d94..7a15de64 100644 --- a/src/components/admin/adminFAQ/AdminFAQWrite.tsx +++ b/src/components/admin/adminFAQ/AdminFAQWrite.tsx @@ -1,3 +1,124 @@ +import { INQUIRY_MESSAGE } from '../../../constants/user/customerService'; +import * as S from './../../admin/adminNotice/AdminNoticeWrite.styled'; +import React, { useEffect, useState } from 'react'; +import { useLocation } from 'react-router-dom'; +import { useModal } from '../../../hooks/useModal'; +import Modal from '../../../components/common/modal/Modal'; +import type { WriteBody } from '../../../models/customerService'; +import Spinner from '../../../components/user/mypage/Spinner'; +import { useAdminFAQ } from '../../../hooks/admin/useAdminFAQ'; + export default function AdminFAQWrite() { - return
; + const location = useLocation(); + const { + isOpen: isModalOpen, + message, + handleModalOpen, + handleModalClose, + } = useModal(); + const pathname = location.state.from || ''; + const id = location.state.id || ''; + + const formDefault = () => { + setForm({ + title: '', + content: '', + }); + }; + + const { getFAQDetailData, postFAQMutate, putFAQMutate } = useAdminFAQ({ + handleModalOpen, + formDefault, + pathname, + id, + }); + const [form, setForm] = useState({ + title: '', + content: '', + }); + const { data: FAQDetailData, isLoading } = getFAQDetailData; + + useEffect(() => { + if (!FAQDetailData) return; + setForm({ title: FAQDetailData.title, content: FAQDetailData.content }); + }, [FAQDetailData]); + + const handleSubmitInquiry = (e: React.FormEvent) => { + e.preventDefault(); + + const isValid = { + title: form.title.trim() !== '', + content: form.content.trim() !== '', + }; + + if (!isValid.title) { + return handleModalOpen(INQUIRY_MESSAGE.writeTitle); + } + if (!isValid.content) { + return handleModalOpen(INQUIRY_MESSAGE.writeContent); + } + + const formData = new FormData(e.currentTarget as HTMLFormElement); + + const formDataObj: WriteBody = { + title: formData.get('title') as string, + content: formData.get('content') as string, + }; + + if (!id) { + return postFAQMutate.mutate(formDataObj); + } else { + return putFAQMutate.mutate({ id, formDataObj }); + } + }; + + if (isLoading) { + return ( + + + + ); + } + + return ( + + + + + + setForm((prev) => ({ ...prev, title: e.target.value })) + } + /> + + + + setForm((prev) => ({ ...prev, content: e.target.value })) + } + > + + + + 제출 + + + + + + {message} + + + ); } diff --git a/src/components/user/customerService/faq/FAQItem.tsx b/src/components/user/customerService/faq/FAQItem.tsx index 6c2b0b73..86bd5841 100644 --- a/src/components/user/customerService/faq/FAQItem.tsx +++ b/src/components/user/customerService/faq/FAQItem.tsx @@ -22,7 +22,7 @@ export default function FAQItem({ faqData, $isAdmin = false }: FAQItemProps) { .filter((_, index) => index < showFAQ) .map((list) => ( - + )) diff --git a/src/components/user/customerService/faq/faqContent/FAQContent.styled.ts b/src/components/user/customerService/faq/faqContent/FAQContent.styled.ts index 2d7026f5..3b92db03 100644 --- a/src/components/user/customerService/faq/faqContent/FAQContent.styled.ts +++ b/src/components/user/customerService/faq/faqContent/FAQContent.styled.ts @@ -1,4 +1,9 @@ import styled, { css } from 'styled-components'; +import { + AdminDropdownWrapper, + AdminLink, + AdminLinkWrapper, +} from '../../noticeDetail/content/NoticeDetailContent.styled'; export const ListContainer = styled.div` width: 100%; @@ -33,6 +38,7 @@ export const ListPlusIcon = styled.div<{ $isOpen: boolean }>` `; export const ListContentWrapper = styled.div<{ $isShowContent: boolean }>` + position: relative; max-height: 0; overflow: hidden; @@ -74,3 +80,27 @@ export const ListContent = styled.div` padding-right: 1.5rem; white-space: pre-wrap; `; + +export const AdminFAQDropdownWrapper = styled(AdminDropdownWrapper)` + position: absolute; + right: 1.5rem; + width: fit-content; + height: fit-content; +`; + +export const AdminFAQAuthButton = styled.button` + width: fit-content; + height: fit-content; + svg { + width: 1rem; + height: 1rem; + } +`; + +export const AdminFAQLinkWrapper = styled(AdminLinkWrapper)` + top: -2.5rem; + left: -6.7rem; + flex-direction: row; +`; + +export const AdminFAQLink = styled(AdminLink)``; diff --git a/src/components/user/customerService/faq/faqContent/FAQContent.tsx b/src/components/user/customerService/faq/faqContent/FAQContent.tsx index 2d00a8f6..37ed3bd2 100644 --- a/src/components/user/customerService/faq/faqContent/FAQContent.tsx +++ b/src/components/user/customerService/faq/faqContent/FAQContent.tsx @@ -1,14 +1,34 @@ -import { ChevronRightIcon, PlusIcon } from '@heroicons/react/24/outline'; +import { + ChevronRightIcon, + EllipsisVerticalIcon, + PlusIcon, +} from '@heroicons/react/24/outline'; import type { FAQ } from '../../../../../models/customerService'; import * as S from './FAQContent.styled'; import { useState } from 'react'; +import { ADMIN_ROUTE } from '../../../../../constants/routes'; +import DropDown from '../../../../common/dropDown/DropDown'; +import { useAdminFAQ } from '../../../../../hooks/admin/useAdminFAQ'; +import { useLocation } from 'react-router-dom'; +import { useModal } from '../../../../../hooks/useModal'; +import Modal from '../../../../common/modal/Modal'; interface FAQContentProps { list: FAQ; + isAdmin: boolean; } -export default function FAQContent({ list }: FAQContentProps) { +export default function FAQContent({ list, isAdmin }: FAQContentProps) { const [isFAQContentOpen, setIsFAQContentOpen] = useState(false); + const location = useLocation(); + const id = String(list.id) || ''; + const pathname = location.pathname; + const { isOpen, message, handleModalOpen, handleModalClose } = useModal(); + const { deleteFAQMutate } = useAdminFAQ({ handleModalOpen, pathname }); + + const handleClickDeleteFAQ = () => { + deleteFAQMutate.mutate(id); + }; return ( @@ -26,7 +46,33 @@ export default function FAQContent({ list }: FAQContentProps) { {list.content} + {isAdmin && ( + + + + + } + > + + + 수정 + + + 삭제 + + + + + )} + + {message} + ); } diff --git a/src/hooks/admin/useAdminFAQ.ts b/src/hooks/admin/useAdminFAQ.ts index f1d2a6a7..b364999c 100644 --- a/src/hooks/admin/useAdminFAQ.ts +++ b/src/hooks/admin/useAdminFAQ.ts @@ -1,39 +1,110 @@ -import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { deleteFAQ, + getFAQDetail, postFAQ, putFAQ, } from '../../api/admin/customerService/FAQ.api'; import type { WriteBody } from '../../models/customerService'; import { AxiosError } from 'axios'; +import { useNavigate } from 'react-router-dom'; +import { ADMIN_MODAL_MESSAGE } from '../../constants/admin/adminModal'; +import { CustomerService } from '../queries/user/keys'; -export const useAdminFAQ = () => { +type State = 'success' | 'fail'; + +export const useAdminFAQ = ({ + handleModalOpen, + formDefault, + pathname, + id = '', +}: { + handleModalOpen: (message: string) => void; + formDefault?: () => void; + pathname: string; + id?: string; +}) => { const queryClient = useQueryClient(); + const navigate = useNavigate(); + + const handleButtonState = (state: State, isDeleteApi: boolean = false) => { + switch (state) { + case 'success': + if (!isDeleteApi) { + handleModalOpen(ADMIN_MODAL_MESSAGE.writeSuccess); + formDefault?.(); + } else { + handleModalOpen(ADMIN_MODAL_MESSAGE.writeDeleteSuccess); + } + setTimeout(() => { + if (pathname) { + return navigate(pathname); + } else { + return navigate(-1); + } + }, 1000); + break; + case 'fail': + if (!isDeleteApi) { + handleModalOpen(ADMIN_MODAL_MESSAGE.writeFail); + } else { + handleModalOpen(ADMIN_MODAL_MESSAGE.writeDeleteFail); + } + break; + default: + handleModalOpen(ADMIN_MODAL_MESSAGE.writeError); + break; + } + }; + + const getFAQDetailData = useQuery({ + queryKey: [CustomerService.faq, id], + queryFn: () => getFAQDetail(id), + enabled: !!id, + }); const postFAQMutate = useMutation({ mutationFn: (formData: WriteBody) => postFAQ(formData), onSuccess: () => { - queryClient.invalidateQueries(); + queryClient.invalidateQueries({ + queryKey: [CustomerService.faq], + }); + handleButtonState('success'); + }, + onError: () => { + handleButtonState('fail'); }, }); const putFAQMutate = useMutation< void, AxiosError, - { id: number; formData: WriteBody } + { id: string; formDataObj: WriteBody } >({ - mutationFn: ({ id, formData }) => putFAQ({ id, formData }), + mutationFn: ({ id, formDataObj: formData }) => putFAQ({ id, formData }), onSuccess: () => { - queryClient.invalidateQueries(); + queryClient.invalidateQueries({ + queryKey: [CustomerService.faq], + }); + handleButtonState('success'); + }, + onError: () => { + handleButtonState('fail'); }, }); - const deleteFAQMutate = useMutation({ - mutationFn: (id: number) => deleteFAQ(id), + const deleteFAQMutate = useMutation({ + mutationFn: (id: string) => deleteFAQ(id), onSuccess: () => { - queryClient.invalidateQueries(); + queryClient.invalidateQueries({ + queryKey: [CustomerService.faq], + }); + handleButtonState('success'); + }, + onError: () => { + handleButtonState('fail'); }, }); - return { postFAQMutate, putFAQMutate, deleteFAQMutate }; + return { getFAQDetailData, postFAQMutate, putFAQMutate, deleteFAQMutate }; }; diff --git a/src/models/customerService.ts b/src/models/customerService.ts index 6f5cf87e..c7ded677 100644 --- a/src/models/customerService.ts +++ b/src/models/customerService.ts @@ -10,6 +10,14 @@ export interface ApiFAQ extends ApiCommonType { data: FAQ[]; } +export interface FAQDetail extends WriteBody { + id: number; +} + +export interface ApiFAQDetail extends ApiCommonType { + data: FAQDetail; +} + export interface NoticeList extends OtherNotice { content: string; } From b52d182b19256bf05e685d0b2013aaabb17acfb9 Mon Sep 17 00:00:00 2001 From: YouD0313 <102004480+YouD0313@users.noreply.github.com> Date: Fri, 6 Jun 2025 00:58:00 +0900 Subject: [PATCH 08/10] =?UTF-8?q?refactor:=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/admin/adminFAQ/AdminFAQWrite.tsx | 4 ++-- src/hooks/admin/useAdminFAQ.ts | 2 +- src/pages/admin/adminFAQ/AdminFAQ.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/admin/adminFAQ/AdminFAQWrite.tsx b/src/components/admin/adminFAQ/AdminFAQWrite.tsx index 7a15de64..1b0ad550 100644 --- a/src/components/admin/adminFAQ/AdminFAQWrite.tsx +++ b/src/components/admin/adminFAQ/AdminFAQWrite.tsx @@ -16,8 +16,8 @@ export default function AdminFAQWrite() { handleModalOpen, handleModalClose, } = useModal(); - const pathname = location.state.from || ''; - const id = location.state.id || ''; + const pathname = location.state?.from || ''; + const id = location.state?.id || ''; const formDefault = () => { setForm({ diff --git a/src/hooks/admin/useAdminFAQ.ts b/src/hooks/admin/useAdminFAQ.ts index b364999c..adfdb6b0 100644 --- a/src/hooks/admin/useAdminFAQ.ts +++ b/src/hooks/admin/useAdminFAQ.ts @@ -99,7 +99,7 @@ export const useAdminFAQ = ({ queryClient.invalidateQueries({ queryKey: [CustomerService.faq], }); - handleButtonState('success'); + handleButtonState('success', true); }, onError: () => { handleButtonState('fail'); diff --git a/src/pages/admin/adminFAQ/AdminFAQ.tsx b/src/pages/admin/adminFAQ/AdminFAQ.tsx index 92bf2f3a..5a131d56 100644 --- a/src/pages/admin/adminFAQ/AdminFAQ.tsx +++ b/src/pages/admin/adminFAQ/AdminFAQ.tsx @@ -1,4 +1,4 @@ -import CommonAdminPage from '../commonAdminPage'; +import CommonAdminPage from '../CommonAdminPage'; export default function AdminFAQ() { return ; From a7a7d99295b51590eeb7462651574761ae018cb5 Mon Sep 17 00:00:00 2001 From: YouD0313 <102004480+YouD0313@users.noreply.github.com> Date: Fri, 6 Jun 2025 09:57:37 +0900 Subject: [PATCH 09/10] =?UTF-8?q?refactor:=20=EA=B3=B5=EB=B0=B1=20?= =?UTF-8?q?=EC=B0=A8=EC=A7=80=20constant=EC=97=90=EC=84=9C=20=EC=A0=95?= =?UTF-8?q?=EC=9D=98=20=ED=9B=84=20=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/adminFAQ/AdminFAQList.styled.ts | 3 +- .../adminNotice/AdminNoticeList.styled.ts | 13 +++++++-- .../admin/adminNotice/AdminNoticeList.tsx | 28 +++++++++++-------- .../admin/searchBar/SearchBar.styled.ts | 5 ++-- src/constants/admin/adminGap.ts | 5 ++++ src/pages/admin/CommonAdminPage.styled.ts | 3 +- 6 files changed, 39 insertions(+), 18 deletions(-) create mode 100644 src/constants/admin/adminGap.ts diff --git a/src/components/admin/adminFAQ/AdminFAQList.styled.ts b/src/components/admin/adminFAQ/AdminFAQList.styled.ts index c9333e0a..307f52df 100644 --- a/src/components/admin/adminFAQ/AdminFAQList.styled.ts +++ b/src/components/admin/adminFAQ/AdminFAQList.styled.ts @@ -1,11 +1,12 @@ import styled from 'styled-components'; import { SpinnerWrapperStyled } from '../../user/mypage/Spinner.styled'; import { SearchBarFixedWrapperStyled } from '../../common/admin/searchBar/SearchBar.styled'; +import { GAP_HEIGHT } from '../../../constants/admin/adminGap'; export const SpinnerWrapper = styled(SpinnerWrapperStyled)``; export const SearchBarFixedWrapper = styled(SearchBarFixedWrapperStyled)``; export const FAQItemWrapper = styled.div` - margin-top: 11rem; + margin-top: calc(${GAP_HEIGHT.headerTitleTop} + ${GAP_HEIGHT.sectionTop}); `; diff --git a/src/components/admin/adminNotice/AdminNoticeList.styled.ts b/src/components/admin/adminNotice/AdminNoticeList.styled.ts index 7459c257..e1ff8360 100644 --- a/src/components/admin/adminNotice/AdminNoticeList.styled.ts +++ b/src/components/admin/adminNotice/AdminNoticeList.styled.ts @@ -1,12 +1,21 @@ +import { GAP_HEIGHT } from './../../../constants/admin/adminGap'; import styled from 'styled-components'; import { SpinnerWrapperStyled } from '../../user/mypage/Spinner.styled'; +import { SearchBarFixedWrapperStyled } from '../../common/admin/searchBar/SearchBar.styled'; export const SpinnerWrapper = styled(SpinnerWrapperStyled)` width: 100%; `; -export const NoticeItemWrapper = styled.section` +export const SearchBarFixedWrapper = styled(SearchBarFixedWrapperStyled)``; + +export const NoticeItemContainer = styled.section` + margin-top: calc( + ${GAP_HEIGHT.headerTitleTop} + ${GAP_HEIGHT.sectionTop} + 1rem + ); +`; + +export const NoticeItemWrapper = styled.div` display: flex; justify-content: center; - margin-top: 2rem; `; diff --git a/src/components/admin/adminNotice/AdminNoticeList.tsx b/src/components/admin/adminNotice/AdminNoticeList.tsx index c3e78c5b..fa5f91d4 100644 --- a/src/components/admin/adminNotice/AdminNoticeList.tsx +++ b/src/components/admin/adminNotice/AdminNoticeList.tsx @@ -25,19 +25,23 @@ export default function AdminNoticeList() { return ( <> - - - + + + + + + + - - + ); } diff --git a/src/components/common/admin/searchBar/SearchBar.styled.ts b/src/components/common/admin/searchBar/SearchBar.styled.ts index f6499b7c..e6d1549e 100644 --- a/src/components/common/admin/searchBar/SearchBar.styled.ts +++ b/src/components/common/admin/searchBar/SearchBar.styled.ts @@ -1,5 +1,6 @@ import { Link } from 'react-router-dom'; import styled from 'styled-components'; +import { GAP_HEIGHT } from '../../../../constants/admin/adminGap'; export const AdminSearchBarContainer = styled.form` width: 100%; @@ -62,10 +63,10 @@ export const WriteLink = styled(Link)` `; export const SearchBarFixedWrapperStyled = styled.div` - width: calc(100vw - 20rem); + width: calc(100vw - 19rem); position: fixed; top: 0; - padding-top: 9rem; + padding-top: ${GAP_HEIGHT.headerTitleTop}; background: ${({ theme }) => theme.color.white}; z-index: 10; `; diff --git a/src/constants/admin/adminGap.ts b/src/constants/admin/adminGap.ts new file mode 100644 index 00000000..27f7cc2a --- /dev/null +++ b/src/constants/admin/adminGap.ts @@ -0,0 +1,5 @@ +export const GAP_HEIGHT = { + outletTop: '7rem', + headerTitleTop: '9rem', + sectionTop: '2rem', +} as const; diff --git a/src/pages/admin/CommonAdminPage.styled.ts b/src/pages/admin/CommonAdminPage.styled.ts index 286792b0..30d11b22 100644 --- a/src/pages/admin/CommonAdminPage.styled.ts +++ b/src/pages/admin/CommonAdminPage.styled.ts @@ -1,5 +1,6 @@ import styled from 'styled-components'; import { SpinnerWrapperStyled } from '../../components/user/mypage/Spinner.styled'; +import { GAP_HEIGHT } from '../../constants/admin/adminGap'; export const SpinnerWrapper = styled(SpinnerWrapperStyled)``; @@ -8,5 +9,5 @@ export const AdminNoticeContainer = styled.div``; export const FixedTitle = styled.div``; export const OutletWrapper = styled.div` - margin-top: 7rem; + margin-top: ${GAP_HEIGHT.outletTop}; `; From a5a7055d4e35caab1ccaddf94efa43ae92478eed Mon Sep 17 00:00:00 2001 From: YouD0313 <102004480+YouD0313@users.noreply.github.com> Date: Fri, 6 Jun 2025 10:30:43 +0900 Subject: [PATCH 10/10] =?UTF-8?q?design:=20searchBar=20=EB=84=88=EB=B9=84?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/admin/searchBar/SearchBar.styled.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/common/admin/searchBar/SearchBar.styled.ts b/src/components/common/admin/searchBar/SearchBar.styled.ts index e6d1549e..eb20e79a 100644 --- a/src/components/common/admin/searchBar/SearchBar.styled.ts +++ b/src/components/common/admin/searchBar/SearchBar.styled.ts @@ -63,6 +63,7 @@ export const WriteLink = styled(Link)` `; export const SearchBarFixedWrapperStyled = styled.div` + max-width: calc(1440px - 19rem); width: calc(100vw - 19rem); position: fixed; top: 0;