Skip to content

Commit

Permalink
Merge pull request #290 from choi-youngsun/Next-최영선-sprint9
Browse files Browse the repository at this point in the history
[최영선] sprint9
  • Loading branch information
arthurkimdev authored Aug 20, 2024
2 parents 8d163dd + c3316a5 commit f8269ce
Show file tree
Hide file tree
Showing 31 changed files with 2,014 additions and 244 deletions.
15 changes: 15 additions & 0 deletions .vscode/launch.json
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}"
}
]
}
151 changes: 151 additions & 0 deletions components/BestPostList.tsx
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;
18 changes: 18 additions & 0 deletions components/Button.ts
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;
32 changes: 32 additions & 0 deletions components/DropDown.tsx
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;
84 changes: 84 additions & 0 deletions components/FileInput.styled.ts
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,
};
72 changes: 72 additions & 0 deletions components/FileInput.tsx
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;
Loading

0 comments on commit f8269ce

Please sign in to comment.