-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #290 from choi-youngsun/Next-최영선-sprint9
[최영선] sprint9
- Loading branch information
Showing
31 changed files
with
2,014 additions
and
244 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}" | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
}, []); | ||
|
||
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"; | ||
|
||
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" | ||
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; |
Oops, something went wrong.