-
Notifications
You must be signed in to change notification settings - Fork 46
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
[오다은] Week19 #492
The head ref may contain hidden characters: "part4-\uC624\uB2E4\uC740-week19"
[오다은] Week19 #492
Changes from all commits
ee5719b
dad1a40
82c04c6
eaa7b83
66e0388
0eba22c
a08abf9
8fb4227
a14edb6
b0f0413
b757528
32238d7
75e4cb1
f36b180
8b22898
1fee61b
f0a65e4
79e7909
ba6af43
aeeaa26
8ef9805
9d85f08
1748b66
01a57ac
1f6607a
d14d83e
24f48c8
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 | ||||
---|---|---|---|---|---|---|
@@ -1,6 +1,7 @@ | ||||||
import { AddLink, EditFolderName } from '@/interfaces/api'; | ||||||
import axios from 'axios'; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||
|
||||||
const BASIC_URL = 'https://bootcamp-api.codeit.kr/api'; | ||||||
const BASIC_URL = 'https://bootcamp-api.codeit.kr/api/linkbrary/v1'; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. base URL은 환경 변수에 저장하시는게 좋습니다!
다음과 같이 적용할 수 있습니다:
왜 환경 변수에 저장해야 하나요?개발(
이러한 방식으로 환경 변수를 사용하면, 배포 환경에 따라 쉽게 URL을 변경할 수 있으며, 코드의 가독성과 유지보수성도 개선됩니다. 실제 코드 응용과 관련해서는 다음 한글 아티클을 참고해보세요! => 보러가기 |
||||||
|
||||||
export async function getUser() { | ||||||
let response; | ||||||
|
@@ -13,13 +14,13 @@ export async function getUser() { | |||||
} else { | ||||||
return; | ||||||
} | ||||||
const result = response.data.data[0]; | ||||||
const result = response.data[0]; | ||||||
return result; | ||||||
} | ||||||
|
||||||
export async function getFolderUser(id: number) { | ||||||
const response = await axios.get(`${BASIC_URL}/users/${id}`); | ||||||
const result = response.data.data[0]; | ||||||
const result = response.data[0]; | ||||||
return result; | ||||||
} | ||||||
|
||||||
|
@@ -31,37 +32,102 @@ export async function postCheckDuplicateEmail(id: string) { | |||||
} | ||||||
|
||||||
export async function postSignUp(id: string, pw: string) { | ||||||
const response = await axios.post(`${BASIC_URL}/sign-up`, { | ||||||
const response = await axios.post(`${BASIC_URL}/auth/sign-up`, { | ||||||
email: id, | ||||||
password: pw, | ||||||
}); | ||||||
return response; | ||||||
} | ||||||
|
||||||
export async function postSignIn(id: string, pw: string) { | ||||||
const response = await axios.post(`${BASIC_URL}/sign-in`, { | ||||||
const response = await axios.post(`${BASIC_URL}/auth/sign-in`, { | ||||||
email: id, | ||||||
password: pw, | ||||||
}); | ||||||
const result = response.data.data; | ||||||
const result = response.data; | ||||||
return result; | ||||||
} | ||||||
|
||||||
export async function getFolders(folderId: number, userId?: number | null) { | ||||||
export async function getFolders(folderId: number) { | ||||||
const queryParam = folderId === 0 ? '' : `/${folderId}`; | ||||||
const userIdParam = userId ? `/users/${userId}` : ''; | ||||||
const response = await axios.get( | ||||||
`${BASIC_URL}${userIdParam}/folders${queryParam}` | ||||||
); | ||||||
const result = response.data.data; | ||||||
const response = await axios.get(`${BASIC_URL}/folders${queryParam}`, { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 반환 타입을 제네릭을 통하여 다루면 어떨까요?
Suggested change
이렇게 하면 |
||||||
headers: { | ||||||
Authorization: localStorage.accessToken, | ||||||
}, | ||||||
}); | ||||||
const result = response.data; | ||||||
return result; | ||||||
} | ||||||
|
||||||
export async function getLinks(userId: number | null, folderId: number) { | ||||||
const queryParam = folderId === 0 ? '' : `?folderId=${folderId}`; | ||||||
const response = await axios.get( | ||||||
`${BASIC_URL}/users/${userId}/links${queryParam}` | ||||||
); | ||||||
const data = response.data.data; | ||||||
export async function getLinks(folderId: number) { | ||||||
const queryParam = folderId === 0 ? '/links' : `/folders/${folderId}/links`; | ||||||
const response = await axios.get(`${BASIC_URL}${queryParam}`, { | ||||||
headers: { | ||||||
Authorization: localStorage.accessToken, | ||||||
}, | ||||||
}); | ||||||
const data = response.data; | ||||||
return data; | ||||||
} | ||||||
|
||||||
export async function addFolder(newFolderName: string) { | ||||||
const response = await axios.post( | ||||||
`${BASIC_URL}/folders`, | ||||||
{ | ||||||
name: newFolderName, | ||||||
}, | ||||||
{ | ||||||
headers: { | ||||||
Authorization: localStorage.accessToken, | ||||||
}, | ||||||
} | ||||||
); | ||||||
} | ||||||
|
||||||
export async function addLink({ url, folderId }: AddLink) { | ||||||
const response = await axios.post( | ||||||
`${BASIC_URL}/links`, | ||||||
{ | ||||||
url, | ||||||
folderId, | ||||||
}, | ||||||
{ | ||||||
headers: { | ||||||
Authorization: localStorage.accessToken, | ||||||
}, | ||||||
} | ||||||
); | ||||||
} | ||||||
|
||||||
export async function deleteFolder(folderId: number) { | ||||||
const response = await axios.delete(`${BASIC_URL}/folders/${folderId}`, { | ||||||
headers: { | ||||||
Authorization: localStorage.accessToken, | ||||||
}, | ||||||
}); | ||||||
} | ||||||
|
||||||
export async function deleteLink(linkId: number) { | ||||||
const response = await axios.delete(`${BASIC_URL}/links/${linkId}`, { | ||||||
headers: { | ||||||
Authorization: localStorage.accessToken, | ||||||
}, | ||||||
}); | ||||||
} | ||||||
|
||||||
export async function putFolderName({ | ||||||
folderId, | ||||||
newFolderName, | ||||||
}: EditFolderName) { | ||||||
const response = await axios.put( | ||||||
`${BASIC_URL}/folders/${folderId}`, | ||||||
{ | ||||||
name: newFolderName, | ||||||
}, | ||||||
{ | ||||||
headers: { | ||||||
Authorization: localStorage.accessToken, | ||||||
}, | ||||||
} | ||||||
); | ||||||
} | ||||||
Comment on lines
+118
to
+133
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 만약
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -6,15 +6,29 @@ interface Props extends ButtonHTMLAttributes<HTMLButtonElement> { | |||||||||||||||||||||||||||||
link?: string; | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
export default function Button({ text, className = '', link }: Props) { | ||||||||||||||||||||||||||||||
export default function Button({ | ||||||||||||||||||||||||||||||
text, | ||||||||||||||||||||||||||||||
className = '', | ||||||||||||||||||||||||||||||
link, | ||||||||||||||||||||||||||||||
onClick, | ||||||||||||||||||||||||||||||
type, | ||||||||||||||||||||||||||||||
disabled, | ||||||||||||||||||||||||||||||
}: Props) { | ||||||||||||||||||||||||||||||
Comment on lines
+9
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
export default function Button({ | |
text, | |
className = '', | |
link, | |
onClick, | |
type, | |
disabled, | |
}: Props) { | |
export default function Button({ | |
text, | |
link, | |
children, | |
...rest | |
}: Props) { |
위처럼 사용하고 버튼은 다음과 같이 작성합니다:
<S.Button
{...rest}
>
어떤 장점이 있나요?
소프트웨어 5대 원칙은 '확장'에는 열려있어야 하며, '수정'에는 닫혀있어야 하는 '개방 폐쇄 원칙'이 있습니다.
만약 현재 버튼에 onFocus
, role
, onHover
등의 props
가 추가로 필요하다면 버튼을 수정해야겠지만, 위처럼 ...rest
를 활용한다면 리액트 버튼의 타입들이 추가될 때(onClick
, type
, 등등..) 수정할 필요가 없다는 장점이 있습니다 😊
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.
추가로 !
다음과 같이 리액트에서 제공하는 Attributes를 사용할 수도 있습니다:
import cn from 'classnames';
import { ButtonHTMLAttributes } from 'react';
interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'none';
}
export default function MelonButton({ className, variant, ...rest }: Props) {
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,36 +1,33 @@ | ||
import { ReactEventHandler, useState } from 'react'; | ||
import { MouseEvent, ReactEventHandler, useState } from 'react'; | ||
import Image, { StaticImageData } from 'next/image'; | ||
import Link from 'next/link'; | ||
import { formatDateToString, formatDateToAgo } from '@/utils/date'; | ||
import Modal from '../Modal/Modal'; | ||
import * as S from './Card.styled'; | ||
import star from '@/public/images/star_icon.png'; | ||
import kebab from '@/public/images/kebab_icon.png'; | ||
import defaultImage from '@/public/images/no-image.png'; | ||
import { FolderInterface } from '@/interfaces'; | ||
import AddLinkModal from '../Modal/Contents/AddLinkModal'; | ||
import DeleteLinkModal from '../Modal/Contents/DeleteLinkModal'; | ||
import { useModal, useSetModal } from '@/contexts/ModalContext'; | ||
|
||
interface Props { | ||
item: { | ||
id: number; | ||
created_at: string; | ||
url: string; | ||
title: string; | ||
image_source: string; | ||
}; | ||
folderNames?: string[]; | ||
itemCountsInEachFolder?: number[]; | ||
folders?: FolderInterface[]; | ||
} | ||
|
||
export default function Card({ | ||
item, | ||
folderNames, | ||
itemCountsInEachFolder, | ||
}: Props) { | ||
const { created_at, url, title, image_source } = item; | ||
const [isVisibleKebabModal, setIsVisibleKebabModal] = useState(false); | ||
const [isVisibledeleteCardModal, setIsVisibleDeleteCardModal] = | ||
useState(false); | ||
const [isVisibleAddInFolderModal, setIsVisibleAddInFolderModal] = | ||
useState(false); | ||
export default function Card({ item, folders }: Props) { | ||
const { id, created_at, url, title, image_source } = item; | ||
const [isOpenKebab, setIsOpenKebab] = useState(false); | ||
|
||
const modal = useModal(); | ||
const setModal = useSetModal(); | ||
const dateBetween = formatDateToAgo(created_at); | ||
const date = formatDateToString(created_at); | ||
|
||
|
@@ -40,22 +37,22 @@ export default function Card({ | |
e.currentTarget.src = defaultImage; | ||
}; | ||
|
||
const handleStarClick = (e: React.MouseEvent) => { | ||
const handleStarClick = (e: MouseEvent) => { | ||
e.preventDefault(); | ||
console.log('별 클릭'); | ||
}; | ||
const handleKebabClick = (e: React.MouseEvent) => { | ||
const handleKebabClick = (e: MouseEvent) => { | ||
e.preventDefault(); | ||
setIsVisibleKebabModal(!isVisibleKebabModal); | ||
setIsOpenKebab(!isOpenKebab); | ||
}; | ||
|
||
const handleDeleteButtonClick = (e: React.MouseEvent) => { | ||
const handleDeleteButtonClick = (e: MouseEvent) => { | ||
e.preventDefault(); | ||
setIsVisibleDeleteCardModal(true); | ||
setModal({ isOpen: true, content: `DeleteLinkModal ${id}` }); | ||
}; | ||
const handleAddFolderButtonClick = (e: React.MouseEvent) => { | ||
const handleAddLinkButtonClick = (e: MouseEvent) => { | ||
e.preventDefault(); | ||
setIsVisibleAddInFolderModal(true); | ||
setModal({ isOpen: true, content: `AddLinkModal ${id}` }); | ||
}; | ||
Comment on lines
+49
to
+55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 카드 내부에서 모달을 띄우니 보여지는 모든 카드의 모달이 중복으로 띄워지는 오류가 있어서 우선 구분할 수 있게 ${id}라는 조건을 추가해 두었습니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아하 조건을 추가해서 문제를 해결하셨군요 😊 |
||
|
||
return ( | ||
|
@@ -74,15 +71,15 @@ export default function Card({ | |
<S.TextWrap> | ||
<S.TextTopWrap> | ||
<S.DateAgo>{dateBetween}</S.DateAgo> | ||
{folderNames && ( | ||
{folders && ( | ||
<button onClick={handleKebabClick}> | ||
<Image src={kebab} alt='더보기' width='21' height='17' /> | ||
</button> | ||
)} | ||
{isVisibleKebabModal && ( | ||
{isOpenKebab && ( | ||
<S.KebabModal> | ||
<button onClick={handleDeleteButtonClick}>삭제하기</button> | ||
<button onClick={handleAddFolderButtonClick}> | ||
<button onClick={handleAddLinkButtonClick}> | ||
폴더에 추가 | ||
</button> | ||
</S.KebabModal> | ||
|
@@ -91,30 +88,20 @@ export default function Card({ | |
<S.Title>{title}</S.Title> | ||
<S.Date>{date}</S.Date> | ||
</S.TextWrap> | ||
{folderNames && ( | ||
{folders && ( | ||
<S.Star onClick={handleStarClick}> | ||
<Image src={star} alt='별' fill sizes='34px' /> | ||
</S.Star> | ||
)} | ||
</Link> | ||
</S.Card> | ||
{isVisibledeleteCardModal && ( | ||
<Modal | ||
title='링크 삭제' | ||
semiTitle={url} | ||
button='삭제하기' | ||
onClose={setIsVisibleDeleteCardModal} | ||
/> | ||
)} | ||
{isVisibleAddInFolderModal && ( | ||
<Modal | ||
title='폴더에 추가' | ||
semiTitle={url} | ||
folders={folderNames} | ||
counts={itemCountsInEachFolder} | ||
button='추가하기' | ||
onClose={setIsVisibleAddInFolderModal} | ||
/> | ||
|
||
{modal.isOpen && modal.content === `DeleteLinkModal ${id}` ? ( | ||
<DeleteLinkModal link={url} linkId={id} /> | ||
) : modal.content === `AddLinkModal ${id}` ? ( | ||
<AddLinkModal link={url} folders={folders} /> | ||
) : ( | ||
'' | ||
)} | ||
</> | ||
); | ||
|
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.
api 주소 변경으로 인해 기존 GET 함수를 조금씩 수정하였고,
POST, PUT, DELETE 요청을 추가 구현하였습니다.
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.
넵 ! 설명 감사드립니다. 한 번 꼼꼼히 봐볼게염