-
Notifications
You must be signed in to change notification settings - Fork 35
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
[최영선] sprint10 #290
The head ref may contain hidden characters: "Next-\uCD5C\uC601\uC120-sprint9"
[최영선] sprint10 #290
Changes from all commits
93a0e93
b2e37bd
6f8bbb0
e11e25f
212e864
4dc5dd0
0d988ef
fdfcbfa
2b65f26
6afe759
3624e32
6166934
7e23153
68f8fb4
e3200ab
ba5e9ec
c82360a
c3316a5
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,15 @@ | ||
{ | ||
// Use IntelliSense to learn about possible attributes. | ||
// Hover to view descriptions of existing attributes. | ||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 | ||
"version": "0.2.0", | ||
"configurations": [ | ||
{ | ||
"type": "chrome", | ||
"request": "launch", | ||
"name": "Launch Chrome against localhost", | ||
"url": "http://localhost:8080", | ||
"webRoot": "${workspaceFolder}" | ||
} | ||
] | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,151 @@ | ||||||||||||||||||||||||||||||||||||||||
import Image from "next/image"; | ||||||||||||||||||||||||||||||||||||||||
import DateTrimmer from "@/utils/TimeTrimmer"; | ||||||||||||||||||||||||||||||||||||||||
import styled from "styled-components"; | ||||||||||||||||||||||||||||||||||||||||
import { useState, useEffect } from "react"; | ||||||||||||||||||||||||||||||||||||||||
import axios from "@/lib/axios"; | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
const Container = styled.div` | ||||||||||||||||||||||||||||||||||||||||
display: flex; | ||||||||||||||||||||||||||||||||||||||||
gap: 24px; | ||||||||||||||||||||||||||||||||||||||||
`; | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
const StyledTitle = styled.h1` | ||||||||||||||||||||||||||||||||||||||||
font-size: 20px; | ||||||||||||||||||||||||||||||||||||||||
font-weight: 700; | ||||||||||||||||||||||||||||||||||||||||
color: var(--gray-900); | ||||||||||||||||||||||||||||||||||||||||
margin-bottom: 24px; | ||||||||||||||||||||||||||||||||||||||||
`; | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
const StyledPostArea = styled.div` | ||||||||||||||||||||||||||||||||||||||||
width: 384px; | ||||||||||||||||||||||||||||||||||||||||
padding: 0px 24px; | ||||||||||||||||||||||||||||||||||||||||
border-radius: 8px; | ||||||||||||||||||||||||||||||||||||||||
background-color: var(--gray-50); | ||||||||||||||||||||||||||||||||||||||||
`; | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
const StyledPostTitle = styled.p` | ||||||||||||||||||||||||||||||||||||||||
font-size: 20px; | ||||||||||||||||||||||||||||||||||||||||
font-weight: 600; | ||||||||||||||||||||||||||||||||||||||||
color: var(--gray-800); | ||||||||||||||||||||||||||||||||||||||||
`; | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
const StyledTopArea = styled.div` | ||||||||||||||||||||||||||||||||||||||||
display: flex; | ||||||||||||||||||||||||||||||||||||||||
justify-content: space-between; | ||||||||||||||||||||||||||||||||||||||||
`; | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
const StyledImageWrapper = styled.img` | ||||||||||||||||||||||||||||||||||||||||
width: 72px; | ||||||||||||||||||||||||||||||||||||||||
height: 72px; | ||||||||||||||||||||||||||||||||||||||||
border: 1px solid var(--gray-200); | ||||||||||||||||||||||||||||||||||||||||
border-radius: 6px; | ||||||||||||||||||||||||||||||||||||||||
position: relative; | ||||||||||||||||||||||||||||||||||||||||
`; | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
const StyledBottomArea = styled.div` | ||||||||||||||||||||||||||||||||||||||||
display: flex; | ||||||||||||||||||||||||||||||||||||||||
justify-content: space-between; | ||||||||||||||||||||||||||||||||||||||||
`; | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
const StyledBottomLeftArea = styled.div` | ||||||||||||||||||||||||||||||||||||||||
display: flex; | ||||||||||||||||||||||||||||||||||||||||
justify-content: space-between; | ||||||||||||||||||||||||||||||||||||||||
gap: 8px; | ||||||||||||||||||||||||||||||||||||||||
align-items: center; | ||||||||||||||||||||||||||||||||||||||||
`; | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
const StyledNickname = styled.p` | ||||||||||||||||||||||||||||||||||||||||
font-size: 14px; | ||||||||||||||||||||||||||||||||||||||||
font-weight: 400; | ||||||||||||||||||||||||||||||||||||||||
color: var(--gray-600); | ||||||||||||||||||||||||||||||||||||||||
`; | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
const StyledLikeCount = styled.p` | ||||||||||||||||||||||||||||||||||||||||
font-size: 14px; | ||||||||||||||||||||||||||||||||||||||||
font-weight: 400; | ||||||||||||||||||||||||||||||||||||||||
color: var(--gray-500); | ||||||||||||||||||||||||||||||||||||||||
`; | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
const StyledDate = styled.p` | ||||||||||||||||||||||||||||||||||||||||
font-size: 14px; | ||||||||||||||||||||||||||||||||||||||||
font-weight: 400; | ||||||||||||||||||||||||||||||||||||||||
color: var(--gray-400); | ||||||||||||||||||||||||||||||||||||||||
`; | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
type Writer = { | ||||||||||||||||||||||||||||||||||||||||
nickname: string; | ||||||||||||||||||||||||||||||||||||||||
id: number; | ||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
type Article = { | ||||||||||||||||||||||||||||||||||||||||
updatedAt: string; | ||||||||||||||||||||||||||||||||||||||||
createdAt: string; | ||||||||||||||||||||||||||||||||||||||||
likeCount: number; | ||||||||||||||||||||||||||||||||||||||||
writer: Writer; | ||||||||||||||||||||||||||||||||||||||||
image: string; | ||||||||||||||||||||||||||||||||||||||||
title: string; | ||||||||||||||||||||||||||||||||||||||||
id: number; | ||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
function BestPostList() { | ||||||||||||||||||||||||||||||||||||||||
const [articles, setArticles] = useState<Article[]>([]); | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
async function getProducts() { | ||||||||||||||||||||||||||||||||||||||||
const query = { | ||||||||||||||||||||||||||||||||||||||||
orderBy: "like", | ||||||||||||||||||||||||||||||||||||||||
page: 1, | ||||||||||||||||||||||||||||||||||||||||
pageSize: 3, | ||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||
const res = await axios.get( | ||||||||||||||||||||||||||||||||||||||||
`/articles?orderBy=${query.orderBy}&page=${query.page}&pageSize=${query.pageSize}` | ||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||
const nextArticles = res.data.list; | ||||||||||||||||||||||||||||||||||||||||
setArticles(nextArticles); | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||
getProducts(); | ||||||||||||||||||||||||||||||||||||||||
}, []); | ||||||||||||||||||||||||||||||||||||||||
Comment on lines
+106
to
+108
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
|
||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||||||||||||||||
<> | ||||||||||||||||||||||||||||||||||||||||
<StyledTitle>베스트 게시글</StyledTitle> | ||||||||||||||||||||||||||||||||||||||||
<Container> | ||||||||||||||||||||||||||||||||||||||||
{articles.map((article) => ( | ||||||||||||||||||||||||||||||||||||||||
<StyledPostArea key={article.id}> | ||||||||||||||||||||||||||||||||||||||||
<Image | ||||||||||||||||||||||||||||||||||||||||
unoptimized={true} | ||||||||||||||||||||||||||||||||||||||||
width={102} | ||||||||||||||||||||||||||||||||||||||||
height={30} | ||||||||||||||||||||||||||||||||||||||||
src="/image/best_badge.png" | ||||||||||||||||||||||||||||||||||||||||
alt="베스트 게시글 뱃지" | ||||||||||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||||||||||
<StyledTopArea> | ||||||||||||||||||||||||||||||||||||||||
<StyledPostTitle>{article.title}</StyledPostTitle> | ||||||||||||||||||||||||||||||||||||||||
<StyledImageWrapper | ||||||||||||||||||||||||||||||||||||||||
src={article.image} | ||||||||||||||||||||||||||||||||||||||||
alt="게시글 첨부 이미지" | ||||||||||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||||||||||
</StyledTopArea> | ||||||||||||||||||||||||||||||||||||||||
<StyledBottomArea> | ||||||||||||||||||||||||||||||||||||||||
<StyledBottomLeftArea> | ||||||||||||||||||||||||||||||||||||||||
<StyledNickname>{article.writer.nickname}</StyledNickname> | ||||||||||||||||||||||||||||||||||||||||
<Image | ||||||||||||||||||||||||||||||||||||||||
unoptimized={true} | ||||||||||||||||||||||||||||||||||||||||
width={15} | ||||||||||||||||||||||||||||||||||||||||
height={13} | ||||||||||||||||||||||||||||||||||||||||
src="/image/heart_inactive.png" | ||||||||||||||||||||||||||||||||||||||||
alt="좋아요 아이콘" | ||||||||||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||||||||||
<StyledLikeCount>{article.likeCount}</StyledLikeCount> | ||||||||||||||||||||||||||||||||||||||||
</StyledBottomLeftArea> | ||||||||||||||||||||||||||||||||||||||||
<StyledDate>{DateTrimmer(article.createdAt)}</StyledDate> | ||||||||||||||||||||||||||||||||||||||||
</StyledBottomArea> | ||||||||||||||||||||||||||||||||||||||||
</StyledPostArea> | ||||||||||||||||||||||||||||||||||||||||
))} | ||||||||||||||||||||||||||||||||||||||||
</Container> | ||||||||||||||||||||||||||||||||||||||||
</> | ||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
export default BestPostList; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import styled from "styled-components"; | ||
|
||
const StyledCommonButton = styled.button` | ||
height: 42px; | ||
padding: 12px 20px; | ||
border-radius: 8px; | ||
margin: auto 0; | ||
border: none; | ||
color: var(--gray-50); | ||
background-color: var(--blue-100); | ||
cursor: pointer; | ||
text-align: center; | ||
align-items: center; | ||
font-size: 18px; | ||
font-weight: 600; | ||
`; | ||
|
||
export default StyledCommonButton; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import styled from "styled-components"; | ||
|
||
const StyledBox = styled.select` | ||
width: 130px; | ||
height: 42px; | ||
padding: 12px 20px; | ||
gap: 10px; | ||
border-radius: 12px; | ||
border: 1px solid var(--gray-200); | ||
`; | ||
|
||
interface DropDownProps { | ||
onOrderChange: (orderBy: string) => void; | ||
} | ||
|
||
function DropDown({ onOrderChange }: DropDownProps) { | ||
const handleChange = (event: React.ChangeEvent<HTMLSelectElement>) => { | ||
onOrderChange(event.target.value); | ||
}; | ||
|
||
return ( | ||
<> | ||
<label htmlFor="order"></label> | ||
<StyledBox onChange={handleChange}> | ||
<option value="recent">최신순</option> | ||
<option value="like">좋아요순</option> | ||
</StyledBox> | ||
</> | ||
); | ||
} | ||
|
||
export default DropDown; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import styled from "styled-components"; | ||
|
||
const StyledLabel = styled.p` | ||
font-size: 18px; | ||
font-weight: 700; | ||
`; | ||
const StyledPreviewImg = styled.img` | ||
width: 282px; | ||
height: 282px; | ||
border-radius: 12px; | ||
&:hover { | ||
outline: 2px solid var(--blue-100); | ||
} | ||
`; | ||
const StyledFileInputBox = styled.div` | ||
width: 282px; | ||
height: 282px; | ||
border-radius: 12px; | ||
background-color: var(--gray-100); | ||
position: relative; | ||
background-image: url("/image/ic_plus.png"); | ||
background-repeat: no-repeat; | ||
background-position-x: 50%; | ||
background-position-y: 40%; | ||
&:hover { | ||
outline: 2px solid var(--blue-100); | ||
} | ||
`; | ||
const StyledFileInputPlaceholder = styled.div` | ||
width: 100%; | ||
text-align: center; | ||
position: absolute; | ||
top: 55%; | ||
color: var(--gray-400); | ||
font-size: 16px; | ||
font-weight: 400; | ||
`; | ||
const StyledFileInput = styled.input` | ||
width: 282px; | ||
height: 282px; | ||
border-radius: 12px; | ||
background-color: var(--gray-100); | ||
opacity: 0; | ||
position: absolute; | ||
cursor: pointer; | ||
`; | ||
const StyledCancelButton = styled.button` | ||
width: 25px; | ||
height: 25px; | ||
position: absolute; | ||
top: 15px; | ||
right: 15px; | ||
background-color: var(--gray-400); | ||
color: var(--gray-50); | ||
border: none; | ||
border-radius: 50%; | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
cursor: pointer; | ||
&:hover { | ||
background-color: var(--blue-100); | ||
} | ||
`; | ||
const StyledFileArea = styled.div` | ||
display: flex; | ||
justify-content: flex-start; | ||
gap: 20px; | ||
`; | ||
const StyledImgArea = styled.div` | ||
position: relative; | ||
display: inline-block; | ||
`; | ||
|
||
export { | ||
StyledLabel, | ||
StyledPreviewImg, | ||
StyledFileInputBox, | ||
StyledFileInputPlaceholder, | ||
StyledFileInput, | ||
StyledCancelButton, | ||
StyledFileArea, | ||
StyledImgArea, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { ChangeEvent, useEffect, useRef, useState } from "react"; | ||
import { | ||
StyledLabel, | ||
StyledPreviewImg, | ||
StyledFileInputBox, | ||
StyledFileInputPlaceholder, | ||
StyledFileInput, | ||
StyledCancelButton, | ||
StyledFileArea, | ||
StyledImgArea, | ||
} from "./FileInput.styled"; | ||
|
||
Comment on lines
+1
to
+12
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. 지난번 리뷰 사항 반영 굳! 👍 |
||
interface Props { | ||
name: string; | ||
value: string | File | null; | ||
onChange: (name: string, value: string | File | null) => void; | ||
} | ||
|
||
function FileInput({ name, value, onChange }: Props) { | ||
const [preview, setPreview] = useState<string | null>(null); | ||
const inputRef = useRef<HTMLInputElement>(null); | ||
|
||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => { | ||
const nextValue = e.target.files?.[0] || null; | ||
onChange(name, nextValue); | ||
}; | ||
|
||
const handleClearClick = () => { | ||
const inputNode = inputRef.current; | ||
if (!inputNode) return; | ||
inputNode.value = ""; | ||
onChange(name, null); | ||
}; | ||
useEffect(() => { | ||
if (!value) return; | ||
|
||
const nextPreview: string = URL.createObjectURL(value as File); | ||
setPreview(nextPreview); | ||
|
||
return () => { | ||
setPreview(null); | ||
URL.revokeObjectURL(nextPreview); | ||
}; | ||
}, [value]); | ||
|
||
return ( | ||
<div> | ||
<StyledLabel>이미지</StyledLabel> | ||
<StyledFileArea> | ||
<StyledFileInputBox> | ||
<StyledFileInput | ||
type="file" | ||
accept="image/png, image/jpeg" | ||
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. 👍 |
||
onChange={handleChange} | ||
ref={inputRef} | ||
/> | ||
<StyledFileInputPlaceholder>이미지 등록</StyledFileInputPlaceholder> | ||
</StyledFileInputBox> | ||
<StyledImgArea> | ||
{value ? ( | ||
<StyledCancelButton onClick={handleClearClick}> | ||
X | ||
</StyledCancelButton> | ||
) : null} | ||
{preview && <StyledPreviewImg src={preview} alt="이미지 미리보기" />} | ||
</StyledImgArea> | ||
</StyledFileArea> | ||
</div> | ||
); | ||
} | ||
|
||
export default FileInput; |
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.
이 부분은 아래 코드에서도 추가적으로 2번 재사용이 되더라구요~ 아래처럼 모듈로 빼면 더 좋겠어요!