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] 카테고리 기능 구현 #431

Merged
merged 23 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f4629aa
✨ 레퍼런스 추가 폼에 카테고리 기능 추가
dle234 Aug 18, 2024
2c56c41
💄 레퍼런스 추가 폼 스타일 구현
dle234 Aug 18, 2024
b149bf2
🐛 Fetcher header 추가 및 서버 메세지 출력
dle234 Aug 18, 2024
d3ea84d
✨ 카테고리 필터링 기능 구현
dle234 Aug 18, 2024
31c1c6c
✨ 카테고리 관리 컴포넌트 구현
dle234 Aug 18, 2024
4c7679e
✨ 카테고리 관련 api 및 쿼리티, 메세지 추가
dle234 Aug 18, 2024
e307582
✨ 카테고리 박스 아이콘 및 체크 박스 구현
dle234 Aug 18, 2024
7d20f60
♻️ 드롭다운 공통 컴포넌트 수정
dle234 Aug 18, 2024
832830f
♻️ UseInput 훅 resetValue 수정
dle234 Aug 18, 2024
44e9663
✨ 레퍼런스 카드에 카테고리 추가
dle234 Aug 18, 2024
563f9a5
💄 북마크 삭제버튼 스타일 수정
dle234 Aug 18, 2024
4561a35
♻️ Modal header 컴포넌트 수정
dle234 Aug 18, 2024
d34994f
Merge branch 'FE/dev' of https://github.com/woowacourse-teams/2024-co…
dle234 Aug 18, 2024
03c9db6
💄 Todo, reference card 애니메이션 추가
dle234 Aug 18, 2024
b09576e
💄 Asset import 순서 수정
dle234 Aug 18, 2024
4773925
🐛 카테고리 추가 버튼 아무값도 입력하지 않았을 때 버튼 disabled
dle234 Aug 19, 2024
384f5f7
💄 북마크 삭제버튼 스타일 변경
dle234 Aug 19, 2024
39f3f6e
♻️ 서버 에러 메시지 없는 경우 props 로 넣어준 에러 나타나도록 수정
dle234 Aug 19, 2024
1dfabad
🐛 선택된 카테고리로 navigate -> 카테고리 선택 지역변수로 관리하도록 수정
dle234 Aug 19, 2024
f53c287
♻️ 카테고리 네이밍 수정
dle234 Aug 19, 2024
0b60b8c
♻️ 상수 사용 및 필요없는 주석 제거
dle234 Aug 19, 2024
e8553ef
♻️ 불필요한 Fragment 제거
dle234 Aug 19, 2024
0f8ba30
Merge branch 'FE/dev' into FE/feature/#349
dle234 Aug 19, 2024
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
16 changes: 11 additions & 5 deletions frontend/src/apis/fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ import * as Sentry from '@sentry/react';
interface RequestProps {
url: string;
method: 'GET' | 'POST' | 'DELETE' | 'PATCH' | 'PUT';
errorMessage: string;
errorMessage?: string;
//TODO: errorMessage 제거
body?: string;
headers?: Record<string, string>;
}

type FetchProps = Omit<RequestProps, 'method'>;

const fetcher = {
async request({ url, method, errorMessage, body, headers }: RequestProps): Promise<Response> {
async request({ url, method, body, headers, errorMessage }: RequestProps): Promise<Response> {
try {
const response = await fetch(url, {
method,
Expand All @@ -20,10 +21,15 @@ const fetcher = {
credentials: 'include',
});

if (!response.ok) throw new Error(errorMessage);
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || errorMessage);
}

return response;
} catch (error) {
if (!(error instanceof Error)) throw new Error(errorMessage);
if (!(error instanceof Error)) (error as { message: string }).message;

Sentry.captureException(error);
throw error;
}
Expand All @@ -40,7 +46,7 @@ const fetcher = {
});
},
delete(props: FetchProps) {
return this.request({ ...props, method: 'DELETE' });
return this.request({ ...props, method: 'DELETE', headers: { 'Content-Type': 'application/json' } });
},
patch(props: FetchProps) {
return this.request({
Expand Down
61 changes: 61 additions & 0 deletions frontend/src/apis/referenceLink/category.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import fetcher from '@/apis/fetcher';

import { ERROR_MESSAGES } from '@/constants/message';

const API_URL = process.env.REACT_APP_API_URL;

interface GetCategoriesResponse {
value: string;
id: string;
}

export const getCategories = async (accessCode: string): Promise<GetCategoriesResponse[]> => {
const response = await fetcher.get({
url: `${API_URL}/${accessCode}/category`,
errorMessage: ERROR_MESSAGES.GET_CATEGORIES,
});

return await response.json();
};

interface AddCategoryRequest {
accessCode: string;
category: string;
}
export const addCategory = async ({ category, accessCode }: AddCategoryRequest) => {
const response = await fetcher.post({
url: `${API_URL}/${accessCode}/category`,
body: JSON.stringify({ value: category }),
errorMessage: ERROR_MESSAGES.ADD_CATEGORY,
});

return await response.json();
};

interface DeleteCategoryRequest {
accessCode: string;
categoryName: string;
}

export const deleteCategory = async ({ categoryName, accessCode }: DeleteCategoryRequest) => {
await fetcher.delete({
url: `${API_URL}/${accessCode}/category/${categoryName}`,
});
};

interface UpdateCategoryRequest {
accessCode: string;
previousCategoryName: string;
updatedCategoryName: string;
}

export const updateCategory = async ({
previousCategoryName,
updatedCategoryName,
accessCode,
}: UpdateCategoryRequest) => {
await fetcher.patch({
url: `${API_URL}/${accessCode}/category`,
body: JSON.stringify({ previousCategoryName, updatedCategoryName }),
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,20 @@ export interface Link {
openGraphTitle: string;
description: string;
image: string;
categoryName: string;
}

interface GetReferenceLinksRequest {
accessCode: string;
currentCategory: string;
}

export const getReferenceLinks = async ({ accessCode }: GetReferenceLinksRequest): Promise<Link[]> => {
export const getReferenceLinks = async ({ accessCode, currentCategory }: GetReferenceLinksRequest): Promise<Link[]> => {
const categoryName = encodeURIComponent(currentCategory);
const categoryParamsUrl = currentCategory === '전체' ? `` : `?categoryName=${categoryName}`;

const response = await fetcher.get({
url: `${API_URL}/${accessCode}/reference-link`,
url: `${API_URL}/${accessCode}/reference-link${categoryParamsUrl}`,
errorMessage: ERROR_MESSAGES.GET_REFERENCE_LINKS,
});

Expand All @@ -29,12 +34,13 @@ export const getReferenceLinks = async ({ accessCode }: GetReferenceLinksRequest
interface AddReferenceLinkRequest {
url: string;
accessCode: string;
category: string | null;
}

export const addReferenceLink = async ({ url, accessCode }: AddReferenceLinkRequest) => {
export const addReferenceLink = async ({ url, accessCode, category }: AddReferenceLinkRequest) => {
await fetcher.post({
url: `${API_URL}/${accessCode}/reference-link`,
body: JSON.stringify({ url }),
body: JSON.stringify({ url, categoryName: category }),
errorMessage: ERROR_MESSAGES.ADD_REFERENCE_LINKS,
});
};
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/assets/images/check_box_checked.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions frontend/src/assets/images/check_box_unchecked.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 13 additions & 1 deletion frontend/src/assets/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
import AlarmSound from '@/assets/audio/alarm_sound.mp3';
import CheckBoxChecked from '@/assets/images/check_box_checked.svg';
import CheckBoxUnchecked from '@/assets/images/check_box_unchecked.svg';
import GithubLogoWhite from '@/assets/images/github-mark-white.png';
import GithubLogo from '@/assets/images/github-mark.png';
import LogoIcon from '@/assets/images/logo_icon.svg';
import LogoIconWithTitle from '@/assets/images/logo_icon_with_title.svg';
import LogoTitle from '@/assets/images/logo_title.svg';
import Wave from '@/assets/images/wave.svg';

export { GithubLogo, GithubLogoWhite, LogoIcon, LogoIconWithTitle, LogoTitle, Wave, AlarmSound };
export {
GithubLogo,
GithubLogoWhite,
CheckBoxUnchecked,
CheckBoxChecked,
LogoIcon,
LogoIconWithTitle,
LogoTitle,
Wave,
AlarmSound,
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const Layout = styled.div<{ $isOpen: boolean }>`
padding: 2rem;

font-size: ${({ theme }) => theme.fontSize.lg};
cursor: pointer;
`;

export const TitleContainer = styled.div`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import styled, { css } from 'styled-components';

export const Form = styled.form`
display: flex;
align-items: center;
gap: 2rem;

width: 75%;
padding: 0 2rem;
`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기 사이에 공백이 필요할 것 같습니다 😊


export const ButtonContainer = styled.div`
display: flex;
gap: 0.6rem;
`;

export const inputStyles = css`
height: 4rem;
`;
export const FooterButton = styled.button`
display: flex;
align-items: center;
gap: 1rem;

width: 100%;
height: 6rem;
padding: 2rem;
border-radius: 0 0 1.5rem 1.5rem;

color: ${({ theme }) => theme.color.black[70]};
font-size: ${({ theme }) => theme.fontSize.base};

transition: all 0.2s ease 0s;

&:hover {
background-color: ${({ theme }) => theme.color.black[20]};
}
`;

export const ReferenceFormContainer = styled.div`
display: flex;
width: 100%;
height: 6rem;
align-items: center;
padding-left: 1rem;
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { useState } from 'react';

import { LuPlus } from 'react-icons/lu';

import Button from '@/components/common/Button/Button';
import Dropdown from '@/components/common/Dropdown/Dropdown/Dropdown';
import Input from '@/components/common/Input/Input';

import useInput from '@/hooks/common/useInput';

import { BUTTON_TEXT } from '@/constants/button';

import * as S from './AddReferenceForm.styles';

interface ReferenceFormProps {
handleAddReferenceLink: (value: string, category: string | null) => void;
categories: string[];
}
const AddReferenceForm = ({ categories, handleAddReferenceLink }: ReferenceFormProps) => {
const { value, status, message, handleChange, resetValue } = useInput();
const [isFooterOpen, setIsFooterOpen] = useState(false);
const [currentCategory, setCurrentCategory] = useState<string | null>(null);

const handleSubmit = (event: React.FormEvent) => {
event.preventDefault();

const category = currentCategory === '카테고리 없음' ? null : currentCategory;
handleAddReferenceLink(value, category);

resetValue();
};
const handleCategory = (option: string | null) => {
setCurrentCategory(option);
};

const newCategories = [...categories, '카테고리 없음'];

return isFooterOpen ? (
<S.ReferenceFormContainer>
<Dropdown
height=""
width="17rem"
options={newCategories}
defaultOption="카테고리 없음"
placeholder="카테고리를 선택해주세요."
onSelect={(option) => handleCategory(option)}
direction="upper"
/>
<S.Form onSubmit={handleSubmit}>
<Input
$css={S.inputStyles}
placeholder="링크를 입력해주세요."
value={value}
status={status}
message={message}
onChange={handleChange}
/>
<S.ButtonContainer>
<Button type="button" size="sm" filled={false} rounded={true} onClick={() => setIsFooterOpen(false)}>
{BUTTON_TEXT.CANCEL}
</Button>
<Button type="submit" size="sm" rounded={true} disabled={value === '' || status !== 'DEFAULT'}>
{BUTTON_TEXT.CONFIRM}
</Button>
</S.ButtonContainer>
</S.Form>
</S.ReferenceFormContainer>
) : (
<S.FooterButton onClick={() => setIsFooterOpen(true)}>
<LuPlus />
링크 추가하기
</S.FooterButton>
);
};

export default AddReferenceForm;
Original file line number Diff line number Diff line change
Expand Up @@ -82,22 +82,36 @@ export const Content = styled.p`

export const DeleteButton = styled(MdClose)`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

링크 삭제 버튼이 찌그러져서 이상하게 등장해요...
image

position: absolute;
top: 1.2rem;
right: 1.2rem;
z-index: 3;

width: 2rem;
height: 2rem;
padding: 0.3rem;
border-radius: 100%;

background-color: ${({ theme }) => theme.color.black[80]};
opacity: 0.5;
color: ${({ theme }) => theme.color.black[50]};
right: 1rem;
top: 1rem;

background-color: ${({ theme }) => theme.color.black[90]};
opacity: 0.6;
color: ${({ theme }) => theme.color.black[20]};

cursor: pointer;

&:hover {
opacity: 1;
}
`;

export const Header = styled.div`
padding: 0 1rem;
display: flex;

justify-content: space-between;
position: absolute;
top: 1.2rem;
width: 100%;

button {
width: fit-content;
padding: 0 1rem;
}
`;
Loading
Loading