-
Notifications
You must be signed in to change notification settings - Fork 51
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
[송민혁] week13 #483
The head ref may contain hidden characters: "part3-\uC1A1\uBBFC\uD601-week13"
[송민혁] week13 #483
Changes from all commits
14a99c8
96054f5
9da5b02
e8b0ab4
448a19b
8448f99
30207fb
30c745a
fc6db57
d70bcc0
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,19 @@ | ||
import styles from '@/styles/addLink.module.css'; | ||
|
||
function AddLink(): JSX.Element { | ||
return ( | ||
<> | ||
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. 불필요한 Fragment입니다. 단일 Wrapper만 있는경우에는 제거해주세요 |
||
<div className={styles.container}> | ||
<div className={styles.input_container}> | ||
<div className={styles.add_box}> | ||
<img src="/assets/image/linkIcon.svg" alt="링크 아이콘" /> | ||
<input className={styles.link_input} placeholder="링크를 추가해 보세요" /> | ||
<button className={styles.cta}>추가하기</button> | ||
</div> | ||
</div> | ||
</div> | ||
</> | ||
); | ||
} | ||
|
||
export default AddLink; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import Moment from 'react-moment'; | ||
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. Moment보다는 date-fns 같은 유틸리티 모듈을 사용하시는 것이 좋습니다. moment는 브라우저에서 사용하기에 너무 기능이 무겁습니다. 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. 이 말을 듣고 라이브러리를 비교해서 쓸 필요가 있다고 느껴서 라이브러리를 정리하는 시간을 가져봤어요! |
||
import '@/styles/card.module.css'; | ||
import { useState } from 'react'; | ||
import { Popover } from 'react-tiny-popover'; | ||
|
||
type OpenPopoverFunc = (e: any) => void; | ||
|
||
function Card({ card }: { card: any }): JSX.Element { | ||
const [isPopoverOpen, setIsPopoverOpen] = useState(false); | ||
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. popover는 아직 구현이 안된것이겠쥬? |
||
|
||
const OpenPopover: OpenPopoverFunc = (e) => { | ||
e.stopPropagation(); | ||
setIsPopoverOpen(!isPopoverOpen); | ||
}; | ||
return ( | ||
<> | ||
<div className="card-wrapper" ref={card.url} rel="noreferrer noopener"> | ||
<div className="card-image-box"> | ||
<img | ||
className="card-image" | ||
src={card.image_source ? card.image_source : `/assets/image/no-img-card.svg`} | ||
alt={card.title} | ||
/> | ||
<button className="star-mark-button"> | ||
<img className="star-mark" src="/assets/image/star.png" alt="카드 즐겨찾기 버튼" /> | ||
</button> | ||
</div> | ||
<div className="card-info-box"> | ||
<div className="card-info-top"> | ||
<Moment className="card-passed-time" fromNow> | ||
{card.createdAt ? card.createdAt : card.created_at} | ||
</Moment> | ||
</div> | ||
<p className="card-description">{card.description}</p> | ||
<Moment format="YYYY.MM.DD">{card.createdAt}</Moment> | ||
</div> | ||
</div> | ||
</> | ||
); | ||
} | ||
|
||
export default Card; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import Card from './Card'; | ||
import '@/styles/cardList.module.css'; | ||
import NoSavedLinks from './NoSavedLinks'; | ||
|
||
function CardList({ cards }: { cards: any }): JSX.Element { | ||
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. any보다도 파일에 빈 타입이라도 만들어두고 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. any를 계속 지우는 연습해보겠습니다 |
||
if (!cards) { | ||
return <NoSavedLinks />; | ||
} | ||
if (cards.length === 0) { | ||
return <NoSavedLinks />; | ||
} | ||
return ( | ||
<> | ||
<div className="container"> | ||
<div className="card-list"> | ||
{cards.map((card: any) => { | ||
return ( | ||
<li key={card.id}> | ||
<Card card={card} /> | ||
</li> | ||
); | ||
})} | ||
</div> | ||
</div> | ||
</> | ||
); | ||
} | ||
|
||
export default CardList; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import '@/styles/cta.module.css'; | ||
|
||
function Cta({ name }: { name: string }): JSX.Element { | ||
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. Cta 처럼 약어는 웬만하면 자제해주시고 풀네임을 쓰시는 것이 좋습니다. |
||
return <button className="cta-btn">{name}</button>; | ||
} | ||
|
||
export default Cta; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import '@/styles/folder.module.css'; | ||
import CardList from './CardList'; | ||
import { useEffect, useState } from 'react'; | ||
import { getLinks } from '@/pages/api/api'; | ||
import FolderButton from './FolderButton'; | ||
import OptionBtn from './OptionBtn'; | ||
import PenIcon from '@/public/images/pen.svg'; | ||
import ShareIcon from '@/public/images/share.svg'; | ||
import TrashIcon from '@/public/images/trash.svg'; | ||
|
||
const INITIAL_FOLDER = { | ||
id: '', | ||
name: '전체', | ||
}; | ||
|
||
function FolderList({ folderList = null, getCardList }): JSX.Element { | ||
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 [folderName, setFolderName] = useState('전체'); | ||
|
||
const handleButton = (name: string, id: string) => { | ||
setFolderName(name); | ||
getCardList(id); | ||
}; | ||
|
||
return ( | ||
<div> | ||
<div className="folderlist-container"> | ||
<FolderButton folder={INITIAL_FOLDER} handleButton={handleButton} /> | ||
{folderList && ( | ||
<> | ||
{folderList.map((folder) => { | ||
return ( | ||
<div key={folder.id}> | ||
<FolderButton folder={folder} handleButton={handleButton} /> | ||
</div> | ||
); | ||
})} | ||
</> | ||
)} | ||
</div> | ||
<div className="folder-title-container"> | ||
<div className="folder-title">{folderName}</div> | ||
<div className="option-container"> | ||
<OptionBtn src={ShareIcon} alt="공유"> | ||
공유 | ||
</OptionBtn> | ||
<OptionBtn src={PenIcon} alt="이름 변경"> | ||
이름 변경 | ||
</OptionBtn> | ||
<OptionBtn src={TrashIcon} alt="삭제"> | ||
삭제 | ||
</OptionBtn> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
function Folder({ folderList = null }) { | ||
const [cards, setCards] = useState(); | ||
|
||
const getCardList = async (id = '') => { | ||
const result = await getLinks(id); | ||
setCards(() => { | ||
return [...result?.data]; | ||
}); | ||
}; | ||
|
||
useEffect(() => { | ||
getLinks(); | ||
}, []); | ||
|
||
return ( | ||
<div className="container"> | ||
<FolderList folderList={folderList} getCardList={getCardList} /> | ||
<CardList cards={cards} /> | ||
</div> | ||
); | ||
} | ||
|
||
export default Folder; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
type Props = { | ||
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. 컴포넌트props 타입은 언제나 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. FolderProps, CardProps ... 이런 식으로 작성하도록 하겠습니다. |
||
folder: any; | ||
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. 제 ts 프로젝트에서 any를 쓰는 일은 거의 없습니다 |
||
handleButton: (name: string, id: string) => void; | ||
}; | ||
|
||
function FolderButton({ folder, handleButton }: Props): JSX.Element { | ||
const { id = '', name = '전체' } = folder; | ||
|
||
const handleButtonClick = (e: any): void => { | ||
handleButton(name, id); | ||
}; | ||
|
||
return ( | ||
<> | ||
<button className="button" onClick={handleButtonClick}> | ||
{name} | ||
</button> | ||
</> | ||
); | ||
} | ||
|
||
export default FolderButton; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import styles from '.@/styles/footer.module.css'; | ||
|
||
function Footer(): JSX.Element { | ||
return ( | ||
<div className="footer"> | ||
<div className="footer_box"> | ||
<span className="copyright">©codeit - 2023</span> | ||
<div className="footer_links"> | ||
<a className="footer_link" href="privacy.html"> | ||
Privacy Policy | ||
</a> | ||
<a className="footer_link" href="faq.html"> | ||
FAQ | ||
</a> | ||
</div> | ||
<div className="sns"> | ||
<a href="https://www.facebook.com/" target="_blank" rel="noopener noreferrer"> | ||
<img src="../assets/image/facebook.svg" alt="facebook 홈페이지로 연결된 facebook 로고" /> | ||
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. next의 |
||
</a> | ||
<a href="https://twitter.com/" target="_blank" rel="noopener noreferrer"> | ||
<img src="../assets/image/twitter.svg" alt="twitter 홈페이지로 연결된 twitter 로고" /> | ||
</a> | ||
<a href="https://www.youtube.com/" target="_blank" rel="noopener noreferrer"> | ||
<img src="../assets/image/youtube.svg" alt="youtube 홈페이지로 연결된 youtube 로고" /> | ||
</a> | ||
<a href="https://www.instagram.com/" target="_blank" rel="noopener noreferrer"> | ||
<img src="../assets/image/instagram.svg" alt="instagram 홈페이지로 연결된 instagram 로고" /> | ||
</a> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
export default Footer; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import '@/styles/header.css'; | ||
|
||
function Header({ folder }: { folder: any }): JSX.Element { | ||
return ( | ||
<div className="header"> | ||
<div className="header-info"> | ||
<img className="header-avatar" src={folder?.avatar} alt="avatar" /> | ||
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. folder가 없다면 에러가 발생할텐데요. foder가 없을때 그려질 ui를 정의해주는것이 좋습니다. |
||
<span className="header-user-name">@{folder?.ownerName}</span> | ||
</div> | ||
<span className="header-folder-name">{folder?.folderName}</span> | ||
</div> | ||
); | ||
} | ||
|
||
export default Header; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import styles from '@/styles/mobileFolderButton.module.css'; | ||
|
||
function MobileFolderButton(): JSX.Element { | ||
return <button className={styles.button}>폴더 추가 +</button>; | ||
} | ||
|
||
export default MobileFolderButton; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import styles from '@/styles/nav.module.css'; | ||
import UserProfile from './UserProfile'; | ||
|
||
function Nav({ userProfile }: { userProfile: any }): JSX.Element { | ||
return ( | ||
<div className={styles.nav_wrapper}> | ||
<div className={styles.gnb}> | ||
<a href="index.html"> | ||
<img className={styles.logo} src="@/public/images/linkbrary.svg" alt="홈으로 연결된 Linkbrary 로고" /> | ||
</a> | ||
<UserProfile userProfile={userProfile} /> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
export default Nav; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
function NoSavedLinks(): JSX.Element { | ||
return ( | ||
<div className="container"> | ||
<p>저장된 링크가 없습니다.</p> | ||
</div> | ||
); | ||
} | ||
|
||
export default NoSavedLinks; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import Image from 'next/image'; | ||
import { ReactNode } from 'react'; | ||
|
||
interface Props { | ||
src: any; | ||
alt: string; | ||
children: ReactNode; | ||
} | ||
|
||
function OptionBtn({ src, alt, children, onClick }: Props): JSX.Element { | ||
return ( | ||
<div className="container"> | ||
<Image src={src} alt={alt} fill /> | ||
<button className="button" onClick={onClick}> | ||
{children} | ||
</button> | ||
</div> | ||
); | ||
} | ||
|
||
export default OptionBtn; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import styles from '@/styles/searchBar.module.css'; | ||
import SearchIcon from '@/public/images/search.svg'; | ||
|
||
function SearchBar(): JSX.Element { | ||
return ( | ||
<> | ||
<form className={styles.search_form}> | ||
<img className={styles.search_icon} src={SearchIcon} alt="검색 아이콘" /> | ||
<input className={styles.search_bar} type="text" placeholder="링크를 검색해 보세요." /> | ||
</form> | ||
</> | ||
); | ||
} | ||
|
||
export default SearchBar; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { UserRawData } from '@/pages/api/type'; | ||
import styles from '@/styles/userProfile.module.css'; | ||
|
||
function UserProfile({ userProfile }: any): JSX.Element { | ||
const userProfileImage = userProfile?.image_source; | ||
const userEmail = userProfile?.email; | ||
|
||
return ( | ||
<> | ||
<div className={styles.user_profile}> | ||
<img className={styles.user_profile_image} src={userProfileImage} alt="유저 프로필 이미지" /> | ||
<p className={styles.user_profile_email}>{userEmail}</p> | ||
</div> | ||
</> | ||
); | ||
} | ||
|
||
export default UserProfile; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { FolderRawData } from '@/pages/api/type'; | ||
import { useCallback, useState } from 'react'; | ||
|
||
function useAsync(asyncFunction: any) { | ||
const [pending, setPending] = useState(false); | ||
const [error, setError] = useState(null); | ||
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. data까지 제공해주고 hook 호출 시점에 내부에서 호출하는게 더 낫지 않을까요? |
||
|
||
const wrappedFunction = useCallback( | ||
async (...args: any[]) => { | ||
setPending(true); | ||
setError(null); | ||
try { | ||
return (await asyncFunction)(...args); | ||
} catch (error: any) { | ||
setError(error); | ||
} finally { | ||
setPending(false); | ||
} | ||
}, | ||
[asyncFunction], | ||
); | ||
|
||
return [pending, error, wrappedFunction]; | ||
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. 2개가 넘어가버리면 tuple보다는 객체를 반환해주는것이 좋습니다. |
||
} | ||
|
||
export default useAsync; |
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.
굳이 returntype을 명시하지 않으셔도됩니다. 컴포넌트는 알아서 타입이 추론되게 하는게 좋습니다.