-
Notifications
You must be signed in to change notification settings - Fork 0
공지사항 ui, api 연결 (#isuue 276) #277
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
664778d
bfa66e0
829c70a
18490c3
71068ce
e9a730f
c9cffa0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| import styled from 'styled-components'; | ||
|
|
||
| export const Container = styled.nav` | ||
| width: 100%; | ||
| `; | ||
|
|
||
| export const Wrapper = styled.button` | ||
| width: 100%; | ||
| padding: 1rem; | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: space-between; | ||
|
|
||
| &:hover { | ||
| background: ${({ theme }) => theme.color.lightgrey}; | ||
| } | ||
| `; | ||
|
|
||
| export const Title = styled.span` | ||
| font-size: 1.2rem; | ||
| font-weight: 700; | ||
| `; | ||
|
|
||
| export const Date = styled.span` | ||
| font-size: 1.1rem; | ||
| `; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import type { Notice } from '../../../models/customerService'; | ||
| import { formatDate } from '../../../util/format'; | ||
| import * as S from './NoticeList.styled'; | ||
|
|
||
| interface NoticeProps { | ||
| list: Notice; | ||
| } | ||
|
|
||
| export default function NoticeList({ list }: NoticeProps) { | ||
| return ( | ||
| <S.Container> | ||
| <S.Wrapper> | ||
| <S.Title>{list.title}</S.Title> | ||
| <S.Date>{formatDate(list.createdAt)}</S.Date> | ||
| </S.Wrapper> | ||
| </S.Container> | ||
| ); | ||
| } | ||
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| import styled from 'styled-components'; | ||
|
|
||
| export const SpinnerWrapper = styled.div` | ||
| height: 60vh; | ||
| `; | ||
|
|
||
| export const Container = styled.div` | ||
| width: 75%; | ||
| margin: 0 auto; | ||
| `; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,40 @@ | ||||||||||||||||||||||||||||||||||||||||||||||
| import { useLocation, useParams } from 'react-router-dom'; | ||||||||||||||||||||||||||||||||||||||||||||||
| import { useGetNoticeDetail } from '../../../hooks/useGetNoticeDetail'; | ||||||||||||||||||||||||||||||||||||||||||||||
| import * as S from './NoticeDetailBundle.styled'; | ||||||||||||||||||||||||||||||||||||||||||||||
| import NoticeDetailBottom from './bottom/NoticeDetailBottom'; | ||||||||||||||||||||||||||||||||||||||||||||||
| import NoticeDetailContent from './content/NoticeDetailContent'; | ||||||||||||||||||||||||||||||||||||||||||||||
| import NoticeDetailHeader from './header/NoticeDetailHeader'; | ||||||||||||||||||||||||||||||||||||||||||||||
| import Spinner from '../../mypage/Spinner'; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| export default function NoticeDetailBundle() { | ||||||||||||||||||||||||||||||||||||||||||||||
| const location = useLocation(); | ||||||||||||||||||||||||||||||||||||||||||||||
| const { noticeId } = useParams(); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const id = noticeId || String(location.state && location.state.id); | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const { noticeDetailData, isLoading } = useGetNoticeDetail(id); | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
| const id = noticeId || String(location.state && location.state.id); | |
| const { noticeDetailData, isLoading } = useGetNoticeDetail(id); | |
| const location = useLocation(); | |
| const { noticeId } = useParams(); | |
| - const id = noticeId || String(location.state && location.state.id); | |
| + const stateId = location.state?.id; | |
| + const id = noticeId || (stateId ? String(stateId) : ''); | |
| + | |
| + // ID가 없는 경우 처리 | |
| + if (!id) { | |
| + return ( | |
| + <S.ErrorWrapper> | |
| + <p>공지사항 ID가 유효하지 않습니다.</p> | |
| + <S.BackLink to="/customer-service/notice"> | |
| + 목록으로 돌아가기 | |
| + </S.BackLink> | |
| + </S.ErrorWrapper> | |
| + ); | |
| + } | |
| const { noticeDetailData, isLoading } = useGetNoticeDetail(id); |
🧰 Tools
🪛 Biome (1.9.4)
[error] 13-13: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ID의 유효성 검사와 오류 처리가 필요합니다.
현재 코드에서는 id가 없는 경우나 API 오류 발생 시 처리 로직이 누락되어 있습니다. 사용자가 URL을 통해 직접 접근할 경우 state가 undefined일 수 있으며, API 호출 중 오류가 발생할 수 있습니다.
다음과 같이 ID 유효성 검사 및 오류 처리 로직을 추가하세요:
const { noticeId } = useParams();
- const id = noticeId || String(location.state && location.state.id);
+ const stateId = location.state?.id;
+ const id = noticeId || (stateId ? String(stateId) : '');
- const { noticeDetailData, isLoading } = useGetNoticeDetail(id);
+ const { noticeDetailData, isLoading, isError, error } = useGetNoticeDetail(id);
+
+ // ID가 없는 경우 처리
+ if (!id) {
+ return (
+ <S.ErrorWrapper>
+ <p>공지사항 ID가 유효하지 않습니다.</p>
+ <S.BackLink to="/customer-service/notice">목록으로 돌아가기</S.BackLink>
+ </S.ErrorWrapper>
+ );
+ }
+
+ // API 오류 처리
+ if (isError) {
+ return (
+ <S.ErrorWrapper>
+ <p>공지사항을 불러오는 중 오류가 발생했습니다.</p>
+ <p>{error instanceof Error ? error.message : '다시 시도해주세요.'}</p>
+ <S.BackLink to="/customer-service/notice">목록으로 돌아가기</S.BackLink>
+ </S.ErrorWrapper>
+ );
+ }관련 스타일 컴포넌트를 NoticeDetailBundle.styled.ts에 추가해야 합니다:
export const ErrorWrapper = styled.div`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 300px;
gap: 1rem;
`;
export const BackLink = styled(Link)`
padding: 0.5rem 1rem;
background-color: #f0f0f0;
border-radius: 4px;
text-decoration: none;
color: #333;
&:hover {
background-color: #e0e0e0;
}
`;🧰 Tools
🪛 Biome (1.9.4)
[error] 12-12: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
오류 상태 처리가 누락되었습니다.
데이터가 없거나 로딩 중인 상태는 처리되어 있으나, API 요청 중 오류가 발생한 경우에 대한 처리가 없습니다.
다음과 같이 오류 상태를 처리하는 코드를 추가하세요:
-const { noticeDetailData, isLoading } = useGetNoticeDetail(String(id));
+const { noticeDetailData, isLoading, isError, error } = useGetNoticeDetail(String(id));
if (!noticeDetailData) return;
+if (isError) {
+ return (
+ <S.ErrorWrapper>
+ <p>공지사항을 불러오는 중 오류가 발생했습니다.</p>
+ <p>{error instanceof Error ? error.message : '다시 시도해주세요.'}</p>
+ </S.ErrorWrapper>
+ );
+}
+
if (isLoading) {
return (
<S.SpinnerWrapper>그리고 관련 스타일 컴포넌트를 NoticeDetailBundle.styled.ts에 추가해야 합니다.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| import { Link } from 'react-router-dom'; | ||
| import styled from 'styled-components'; | ||
|
|
||
| export const Container = styled.nav``; | ||
|
|
||
| export const NotOtherNotice = styled.div` | ||
| padding: 0.5rem 1rem; | ||
| font-size: 0.9rem; | ||
| `; | ||
|
|
||
| export const ListWrapper = styled.div` | ||
| width: 100%; | ||
| display: flex; | ||
| justify-content: center; | ||
| margin-top: 1.5rem; | ||
| `; | ||
|
|
||
| export const ListLink = styled(Link)` | ||
| display: flex; | ||
| justify-content: center; | ||
| align-items: center; | ||
| font-size: 0.9rem; | ||
| background: ${({ theme }) => theme.color.navy}; | ||
| border-radius: ${({ theme }) => theme.borderRadius.large}; | ||
| color: ${({ theme }) => theme.color.white}; | ||
| border: 1px solid ${({ theme }) => theme.color.navy}; | ||
| padding: 0.5rem 1rem; | ||
|
|
||
| &:hover { | ||
| background: ${({ theme }) => theme.color.lightgrey}; | ||
| color: ${({ theme }) => theme.color.navy}; | ||
| border: 1px solid ${({ theme }) => theme.color.navy}; | ||
| transition: all 0.3s ease-in-out; | ||
| } | ||
| `; | ||
|
|
||
| export const ListTitle = styled.span``; | ||
|
|
||
| export const ContentBorder = styled.div` | ||
| width: 100%; | ||
| height: 0.5px; | ||
| background: ${({ theme }) => theme.color.placeholder}; | ||
| position: relative; | ||
| z-index: 1; | ||
| `; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| import { ROUTES } from '../../../../constants/routes'; | ||
| import type { OtherNotice } from '../../../../models/customerService'; | ||
| import OtherNoticeButton from './button/OtherNoticeButton'; | ||
| import * as S from './NoticeDetailBottom.styled'; | ||
|
|
||
| interface NoticeDetailBottomProps { | ||
| prev: OtherNotice | null; | ||
| next: OtherNotice | null; | ||
| } | ||
|
|
||
| export default function NoticeDetailBottom({ | ||
| prev, | ||
| next, | ||
| }: NoticeDetailBottomProps) { | ||
| return ( | ||
| <S.Container> | ||
| <S.ContentBorder></S.ContentBorder> | ||
| {prev !== null ? ( | ||
| <OtherNoticeButton | ||
| navigation='이전' | ||
| id={prev.id} | ||
| title={prev.title} | ||
| createdAt={prev.createdAt} | ||
YouD0313 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| /> | ||
| ) : ( | ||
| <S.NotOtherNotice>이전 공지사항이 없습니다.</S.NotOtherNotice> | ||
| )} | ||
| <S.ContentBorder></S.ContentBorder> | ||
| {next !== null ? ( | ||
| <OtherNoticeButton | ||
| navigation='다음' | ||
| id={next.id} | ||
| title={next.title} | ||
| createdAt={next.createdAt} | ||
YouD0313 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| /> | ||
| ) : ( | ||
| <S.NotOtherNotice>다음 공지사항이 없습니다.</S.NotOtherNotice> | ||
| )} | ||
| <S.ContentBorder></S.ContentBorder> | ||
| <S.ListWrapper> | ||
| <S.ListLink to={`${ROUTES.customerService}/${ROUTES.notice}`}> | ||
| <S.ListTitle>목록</S.ListTitle> | ||
| </S.ListLink> | ||
| </S.ListWrapper> | ||
| </S.Container> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| import { Link } from 'react-router-dom'; | ||
| import styled from 'styled-components'; | ||
|
|
||
| export const OtherNoticeLink = styled(Link)` | ||
| width: 100%; | ||
| display: flex; | ||
| padding: 0.5rem 1rem; | ||
| justify-content: space-between; | ||
| align-items: center; | ||
| font-size: 0.9rem; | ||
|
|
||
| &:hover { | ||
| background: ${({ theme }) => theme.color.lightgrey}; | ||
| } | ||
| `; | ||
|
|
||
| export const OtherNoticeWrapper = styled.div` | ||
| display: flex; | ||
| gap: 1rem; | ||
| `; | ||
|
|
||
| export const OtherNotice = styled.span` | ||
| font-weight: 600; | ||
| `; | ||
|
|
||
| export const OtherNoticeTitle = styled.span``; | ||
|
|
||
| export const OtherNoticeDate = styled.span` | ||
| font-size: 0.8rem; | ||
| color: ${({ theme }) => theme.color.placeholder}; | ||
| `; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| import { ROUTES } from '../../../../../constants/routes'; | ||
| import { OtherNotice } from '../../../../../models/customerService'; | ||
| import { formatDate } from '../../../../../util/format'; | ||
| import * as S from './OtherNoticeButton.styled'; | ||
|
|
||
| interface OtherNoticeButtonProps extends OtherNotice { | ||
| navigation: string; | ||
| } | ||
|
|
||
| export default function OtherNoticeButton({ | ||
| navigation, | ||
| id, | ||
| title, | ||
| createdAt, | ||
| }: OtherNoticeButtonProps) { | ||
| return ( | ||
| <S.OtherNoticeLink | ||
| to={`${ROUTES.customerService}/${ROUTES.noticeDetail}/${id}`} | ||
| state={{ id }} | ||
| > | ||
YouD0313 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| <S.OtherNoticeWrapper> | ||
| <S.OtherNotice>{navigation}</S.OtherNotice> | ||
| <S.OtherNoticeTitle>{title}</S.OtherNoticeTitle> | ||
| </S.OtherNoticeWrapper> | ||
| <S.OtherNoticeDate>{formatDate(createdAt)}</S.OtherNoticeDate> | ||
| </S.OtherNoticeLink> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| import styled from 'styled-components'; | ||
|
|
||
| export const Container = styled.section` | ||
| width: 100%; | ||
| margin: 2rem 0; | ||
| `; | ||
|
|
||
| export const TitleWrapper = styled.div` | ||
| padding: 1rem; | ||
| `; | ||
|
|
||
| export const Title = styled.h2``; | ||
|
|
||
| export const AdminWrapper = styled.div` | ||
| padding: 0.5rem 0; | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 0.2rem; | ||
| `; | ||
|
|
||
| export const AdminImg = styled.img` | ||
| width: 2rem; | ||
| height: 2rem; | ||
| `; | ||
|
|
||
| export const Admin = styled.span` | ||
| font-size: 1.1rem; | ||
| `; | ||
|
|
||
| export const InfoWrapper = styled.div` | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 1rem; | ||
| `; | ||
|
|
||
| export const NoticeContentDate = styled.span` | ||
| font-size: 0.8rem; | ||
| `; | ||
|
|
||
| export const ViewWrapper = styled.div``; | ||
|
|
||
| export const ViewIcon = styled.div` | ||
| display: flex; | ||
| svg { | ||
| width: 1rem; | ||
| height: 1rem; | ||
| } | ||
| `; | ||
|
|
||
| export const ViewCount = styled.span` | ||
| font-size: 0.8rem; | ||
| `; | ||
|
|
||
| export const ContentWrapper = styled.div` | ||
| padding: 1.5rem 1rem; | ||
| `; | ||
|
|
||
| export const Content = styled.p``; | ||
|
|
||
| export const ContentBorder = styled.div` | ||
| width: 100%; | ||
| height: 0.5px; | ||
| background: ${({ theme }) => theme.color.placeholder}; | ||
| position: relative; | ||
| z-index: 1; | ||
| `; |
Uh oh!
There was an error while loading. Please reload this page.