Skip to content
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

[FE] 어드민 페이지에 아티클 수정 기능을 추가한다. #1081

Merged
merged 6 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions frontend/src/apis/article.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,8 @@ export const postArticle = async (article: ArticlePostForm) => {
const response = await fetcher.post({ url: BASE_URL + ENDPOINT.ARTICLES, body: article });
return response;
};

export const putArticle = async ({ article, articleId }: { article: ArticlePostForm; articleId: number }) => {
const response = await fetcher.put({ url: BASE_URL + ENDPOINT.ARTICLE_ID(articleId), body: article });
return response;
};
1 change: 0 additions & 1 deletion frontend/src/apis/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export const ENDPOINT = {
ROOM_COMPARE: (roomId1: number, roomId2: number) => `/v1/checklists/comparison?id=${roomId1}&id=${roomId2}`,
ROOM_CATEGORY_DETAIL: (roomId: number, categoryId: number) =>
`/v1/comparison/checklists/${roomId}/categories/${categoryId}/questions`,

// like
LIKE: (id: number | ':id') => `/checklists/${id}/like`,
// category
Expand Down
83 changes: 83 additions & 0 deletions frontend/src/components/Admin/Article/AdminArticleCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import styled from '@emotion/styled';
import { useNavigate } from 'react-router-dom';

import { ROUTE_PATH } from '@/constants/routePath';
import { boxShadow, flexColumn, title3 } from '@/styles/common';
import { Article } from '@/types/article';
import formattedDate from '@/utils/formattedDate';
import getSeqColor from '@/utils/getSeqColor';

interface Props {
article: Article;
}

const AdminArticleCard = ({ article }: Props) => {
const navigate = useNavigate();
const { articleId, keyword, title, summary, createdAt } = article;

const ARTICLE_KEYWORDS = ['방끗 활용법', '동네 추천', '우테코 생활', '자취 꿀팁'];
const currentColorIndex = ARTICLE_KEYWORDS.findIndex(keyword => keyword === article.keyword);
const { color500 } = getSeqColor(currentColorIndex);
const { color500: defaultColor500 } = getSeqColor(articleId);

const handleClick = () => {
navigate(ROUTE_PATH.articleEditOne(articleId));
};

return (
<S.Container onClick={handleClick} tabIndex={1}>
<S.Keyword bgColor={color500 ?? defaultColor500}> {keyword}</S.Keyword>
<S.Title>{title}</S.Title>
<S.Label>{summary}</S.Label>
<S.Label>{formattedDate(createdAt)}</S.Label>
</S.Container>
);
};

export default AdminArticleCard;

const S = {
Container: styled.div`
${flexColumn}

gap: 0.8rem;
width: auto;
box-sizing: border-box;
padding: 1.6rem;

border-radius: 1.6rem;

background-color: ${({ theme }) => theme.palette.white};
${boxShadow};
cursor: pointer;

:hover {
background-color: ${({ theme }) => theme.palette.grey200};
}
`,
Keyword: styled.span<{ bgColor: string }>`
padding: 0.4rem 0.8rem;

background-color: ${({ bgColor }) => bgColor};

color: ${({ theme }) => theme.palette.white};
font-size: ${({ theme }) => theme.text.size.xSmall};
align-self: flex-start;

box-sizing: content-box;
border-radius: 0.6rem;
`,
Title: styled.div`
${title3}
margin-top: .8rem;
word-break: keep-all;
`,
Label: styled.div`
width: 90%;

color: ${({ theme }) => theme.palette.grey500};
font-size: ${({ theme }) => theme.text.size.xSmall};

word-break: keep-all;
`,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import styled from '@emotion/styled';

import AdminArticleCard from '@/components/Admin/Article/AdminArticleCard';
import SkArticleList from '@/components/skeleton/Article/SkArticleList';
import useGetArticleListQuery from '@/hooks/query/useGetArticleListQuery';
import { flexColumn } from '@/styles/common';
import { Article } from '@/types/article';

const AdminArticleListContainer = () => {
const { data: articles, isLoading } = useGetArticleListQuery();

if (isLoading) return <SkArticleList />;

return (
<S.ListContainer>
{articles?.map((article: Article) => <AdminArticleCard key={article.articleId} article={article} />)}
</S.ListContainer>
);
};

export default AdminArticleListContainer;

const S = {
ListContainer: styled.section`
${flexColumn}
gap: 1.2rem;
margin-top: 1.6rem;
`,
};
5 changes: 4 additions & 1 deletion frontend/src/constants/routePath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,8 @@ export const ROUTE_PATH = {
location: '/location',
myPage: '/my-page',
admin: '/admin',
articleEditor: '/isHaileyGod',
articleListAdmin: '/admin/article',
articleNew: '/admin/article/new',
articleEdit: '/admin/article/:articleId',
articleEditOne: (id: number) => `/admin/article/${id}`,
};
22 changes: 22 additions & 0 deletions frontend/src/hooks/query/usePutArticleQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useMutation } from '@tanstack/react-query';
import { useNavigate } from 'react-router-dom';

import { putArticle } from '@/apis/article';
import { ROUTE_PATH } from '@/constants/routePath';
import useToast from '@/hooks/useToast';
import { ArticlePostForm } from '@/types/article';

const usePutArticleQuery = () => {
const { showToast } = useToast();
const navigate = useNavigate();

return useMutation<Response, Error, { article: ArticlePostForm; articleId: number }>({
mutationFn: putArticle,
onSuccess: () => {
showToast({ message: '아티클 수정 완료!' });
navigate(ROUTE_PATH.admin);
},
});
};

export default usePutArticleQuery;
31 changes: 31 additions & 0 deletions frontend/src/hooks/useArticleForm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import useInputs from '@/hooks/useInputs';
import { ArticlePostForm } from '@/types/article';

const useArticleForm = () => {
const {
values: form,
onChange,
setValues,
} = useInputs<ArticlePostForm>({
title: '',
keyword: '',
summary: '',
thumbnail: '',
content: '# 여기에 아티클을 작성해주세요',
});

const setForm = (field: keyof ArticlePostForm, value: string | number) => {
setValues(prev => ({
...prev,
[field]: value,
}));
};

return {
form,
onChange,
setForm,
};
};

export default useArticleForm;
48 changes: 48 additions & 0 deletions frontend/src/pages/AdminArticleListPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import styled from '@emotion/styled';
import { ErrorBoundary } from 'react-error-boundary';

import ListErrorFallback from '@/components/_common/errorBoundary/ListErrorFallback';
import Layout from '@/components/_common/layout/Layout';
import AdminArticleListContainer from '@/components/Admin/Article/AdminArticleListContainer';
import { flexRow, flexSpaceBetween, title2 } from '@/styles/common';
import theme from '@/styles/theme';

const AdminArticleListPage = () => {
return (
<>
<S.Header>
<S.HeaderContents>
<S.Title>아티클</S.Title>
</S.HeaderContents>
</S.Header>
<Layout bgColor={theme.palette.background} withHeader withFooter>
<ErrorBoundary FallbackComponent={ListErrorFallback}>
<AdminArticleListContainer />
</ErrorBoundary>
</Layout>
</>
);
};

export default AdminArticleListPage;

const S = {
Header: styled.header`
${flexRow}
justify-content: center;
width: 100vw;
height: 50px;
border-bottom: 1px solid ${({ theme }) => theme.palette.grey200};
box-sizing: border-box;
margin-bottom: 20px;
`,
HeaderContents: styled.div`
width: 120rem;
${flexRow}
${flexSpaceBetween}
align-items: center;
`,
Title: styled.h1`
${title2}
`,
};
12 changes: 6 additions & 6 deletions frontend/src/pages/AdminPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ const AdminPage = () => {
return (
<S.PageWrapper>
<S.QuestionBox>
<S.QuestionText>방끗의 마스코트는?</S.QuestionText>
<S.QuestionText>아티클을 작성하러 오셨나요?</S.QuestionText>
<S.ButtonWrapper>
<S.Button color={'red'}>몰라요</S.Button>
<Link to={ROUTE_PATH.articleEditor}>
<S.Button>방방이</S.Button>
<Link to={ROUTE_PATH.articleListAdmin}>
<S.Button color={'red'}>수정하려고 왔습니다</S.Button>
</Link>
<Link to={ROUTE_PATH.articleNew}>
<S.Button>추가하려고 왔습니다</S.Button>
</Link>
</S.ButtonWrapper>
</S.QuestionBox>
Expand All @@ -45,7 +47,6 @@ const S = {

background-color: ${({ theme }) => theme.palette.white};
`,

QuestionBox: styled.div`
padding: 2rem;

Expand All @@ -55,7 +56,6 @@ const S = {
border-radius: 10px;
${boxShadowSpread}
`,

QuestionText: styled.h2`
margin-bottom: 2rem;

Expand Down
Loading
Loading