Skip to content
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 #294

21 changes: 13 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 스프린트 미션 9
# 스프린트 미션 10

## 기본 요구사항

Expand All @@ -9,12 +9,17 @@

## 체크리스트 [기본]

- [x] 자유 게시판 페이지 주소는 “/boards” 입니다.
- [x] 전체 게시글에서 드롭 다운으로 “최신 순” 또는 “좋아요 순”을 선택해서 정렬을 할 수 있습니다.
- [x] 게시글 목록 조회 api를 사용하여 베스트 게시글, 게시글을 구현합니다.
- [x] 게시글 title에 검색어가 일부 포함되면 검색이 됩니다.
### 상품 등록 페이지

## 체크리스트 [심화]
- [x] 상품 등록 페이지 주소는 “/addboard” 입니다.
- [] 게시판 이미지는 최대 한개 업로드가 가능합니다.
- [x] 각 input의 placeholder 값을 정확히 입력해주세요.
- [] 이미지를 제외하고 input 에 모든 값을 입력하면 ‘등록' 버튼이 활성화 됩니다.
- [] 회원가입, 로그인 api를 사용하여 받은accessToken을 사용하여 게시물 등록을 합니다.
- [] ‘등록’ 버튼을 누르면 게시물 상세 페이지로 이동합니다.

- [x] 반응형으로 보여지는 베스트 게시판 개수를 다르게 설정할때 서버에 보내는 pageSize값을 적절하게 설정합니다.
- [x] next의 prefetch 기능을 사용해봅니다.
### 상품 상세 페이지

- [x] 상품 상세 페이지 주소는 “/board/{id}” 입니다.
- [] 댓글 input 값을 입력하면 ‘등록' 버튼이 활성화 됩니다.
- [] 활성화된 ‘등록' 버튼을 누르면 댓글이 등록됩니다
6 changes: 0 additions & 6 deletions components/boards/AllBoard.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,6 @@
max-width: 1200px;
margin: 26px auto;
padding-bottom: 20px;
@media (max-width: 1199px) {
padding: 0 24px;
}
@media (max-width: 767px) {
padding: 0 16px;
}
.section-header {
display: flex;
align-items: center;
Expand Down
4 changes: 2 additions & 2 deletions components/boards/AllBoard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { useRouter } from "next/router";
import axios from "@/lib/axios";
import Link from "next/link";
import { formatDate } from "@/lib/utils/formatDate";
import { Article } from "@/types/article";
import { Article } from "@/types/articleTypes";

interface AllBoardProps {
initialArticles: Article[];
Expand Down Expand Up @@ -73,7 +73,7 @@ function AllBoard({ initialArticles }: AllBoardProps) {
<section className={styles["all-board-wrap"]}>
<div className={styles["section-header"]}>
<h2 className={styles.title}>게시글</h2>
<LinkButton href="#" size="sm" color="primary">
<LinkButton href="/addboard" size="sm" color="primary">
글쓰기
</LinkButton>
</div>
Expand Down
73 changes: 73 additions & 0 deletions components/boards/Article.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
.article {
margin: 32px auto;
}
.article-title {
font-size: 20px;
font-weight: 400;
margin-bottom: 16px;
}
.writer-info {
border-bottom: 1px solid var(--gray200);
display: flex;
align-items: center;
gap: 8px;
padding: 16px 0;
.profile-info {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
font-weight: 300;
}
.profile-wrap {
position: relative;
width: 40px;
height: 40px;
border-radius: 50%;
overflow: hidden;
img {
object-fit: contain;
}
}
.date {
font-size: 14px;
color: var(--gray400);
font-weight: 300;
}
.divided {
position: relative;
display: inline-block;
margin: 0 25px;
height: 30px;
width: 1px;
background-color: var(--gray200);
}
}
.article-content {
padding: 24px 0;
p {
font-weight: 300;
font-size: 18px;
}
.article-image-wrap {
position: relative;
max-width: 600px;
height: 600px;
margin: 20px 0;
img {
object-fit: contain;
}
}
}
.btn-favorite {
display: inline-flex;
width: fit-content;
align-items: center;
gap: 8px;
border: 1px solid var(--gray200);
background: var(--white);
border-radius: 2em;
padding: 0.5em 1em;
font-size: 16px;
color: var(--gray500);
}
74 changes: 74 additions & 0 deletions components/boards/Article.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { useEffect, useState } from "react";
import styles from "./Article.module.scss";
import Image from "next/image";
import Icon from "@/components/ui/Icon";
import axios from "@/lib/axios";
import { Article as ArticleType } from "@/types/articleTypes";
import { formatDate } from "@/lib/utils/formatDate";

interface ArticleProps {
id: number;
}

function Article({ id }: ArticleProps) {
const [article, setArticle] = useState<ArticleType | null>(null);
const fetchArticle = async (articleId: number) => {
try {
const response = await axios.get(`/articles/${articleId}`);
setArticle(response.data);
} catch (error) {
console.error("Failed to fetch the article:", error);
}
};

useEffect(() => {
if (id) {
fetchArticle(id);
}
}, [id]);

if (!article) {
return <p>Loading...</p>;
}

return (
<article className={styles.article}>
<h3 className={styles["article-title"]}>{article.title}</h3>
<div className={styles["writer-info"]}>
<div className={styles["profile-info"]}>
<div className={styles["profile-wrap"]}>
<Image
src="/img/profile.png"
alt="프로필 이미지"
className={styles["profile-image"]}
fill
/>
</div>
<div className={styles["nick-name"]}>{article.writer.nickname}</div>
</div>
<div className={styles.date}>{formatDate(article.createdAt)}</div>
<div className={styles.divided}></div>
<button className={styles["btn-favorite"]}>
<Icon type="heart" size="md" />

{article.likeCount}
</button>
</div>
<div className={styles["article-content"]}>
<p>{article.content}</p>
{article.image && (
<div className={styles["article-image-wrap"]}>
<Image
src={article.image}
alt="게시글 이미지"
className={styles["article-image"]}
fill
/>
</div>
)}
</div>
</article>
);
}

export default Article;
6 changes: 0 additions & 6 deletions components/boards/BestBoard.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,6 @@
max-width: 1200px;
margin: 26px auto;
padding-bottom: 20px;
@media (max-width: 1199px) {
padding: 0 24px;
}
@media (max-width: 767px) {
padding: 0 16px;
}
.title {
grid-area: title;
font-size: 20px;
Expand Down
2 changes: 1 addition & 1 deletion components/boards/BestBoard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import axios from "@/lib/axios";
import useViewport from "@/hooks/useViewport";
import Icon from "@/components/ui/Icon";
import { formatDate } from "@/lib/utils/formatDate";
import { Article } from "@/types/article";
import { Article } from "@/types/articleTypes";

const getPageSize = (width: number): number => {
switch (true) {
Expand Down
60 changes: 60 additions & 0 deletions components/form/FileInput.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
.input-label {
display: block;
font-size: 18px;
color: var(--gray800);
margin-bottom: 16px;
font-weight: 700;

@media (max-width: 767px) {
font-size: 14px;
margin-bottom: 8px;
}
}
.image-add-wrap {
display: flex;
gap: 25px;
@media (max-width: 1200px) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저번에 보여드렸던 것처럼 _responsive.scss 파일에 이런 media 구문을 @mixin으로 함수화 시켜서 정리해주시면 편할 거에요 👍

gap: 16px;
}
@media (max-width: 767px) {
gap: 8px;
}

.image-add-btn {
width: 282px;
aspect-ratio: 1/1;
font-size: 16px;
color: var(--gray400);
background-color: var(--gray100);
border-radius: 12px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 12px;
cursor: pointer;
}

.image-add-box {
position: relative;
width: 282px;
aspect-ratio: 1/1;
overflow: hidden;
border-radius: 12px;
img {
object-fit: cover;
}
}
}
.btn-delete {
position: absolute;
background: transparent;
width: 24px;
height: 24px;
right: 10px;
top: 10px;
background: url("/img/icon/ic_X.svg") no-repeat center center;
&:hover {
background-image: url("/img/icon/ic_X_blue.svg");
}
}
53 changes: 53 additions & 0 deletions components/form/FileInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { useState, ChangeEvent } from "react";
import Image from "next/image";
import styles from "./FileInput.module.scss";
import Icon from "@/components/ui/Icon";

interface FileInputProps {
label: string;
imagePreviewUrl: string;
onImageChange: (e: ChangeEvent<HTMLInputElement>) => void;
onImageDelete: () => void;
}

function FileInput({
label,
imagePreviewUrl,
onImageChange,
onImageDelete,
}: FileInputProps) {
return (
<div className="input-group">
<label className={styles["input-label"]}>{label}</label>
<div className={styles["image-add-wrap"]}>
<label
className={styles["image-add-btn"]}
aria-label="이미지 등록 버튼"
>
<input
id="image-upload"
className="sr-only"
type="file"
onChange={onImageChange}
accept="image/*"
/>
<Icon type="plus" size="lg" />
<span>이미지 등록</span>
</label>
{imagePreviewUrl && (
<div className={styles["image-add-box"]}>
<Image src={imagePreviewUrl} alt="이미지" fill />
<button
type="button"
onClick={onImageDelete}
className={styles["btn-delete"]}
aria-label="이미지 제거 버튼"
/>
</div>
)}
</div>
</div>
);
}

export default FileInput;
Empty file.
33 changes: 33 additions & 0 deletions components/form/NumberInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React, { ChangeEvent } from "react";

// Props 타입 정의
interface NumberInputProps {
label: string;
name: string;
value: number;
onChange: (e: ChangeEvent<HTMLInputElement>) => void; // 타입 정의 수정
placeholder?: string;
}

function NumberInput({
label,
name,
value,
onChange,
placeholder,
}: NumberInputProps) {
return (
<div className="input-group">
<label>{label}</label>
<input
type="number"
name={name}
value={value}
onChange={onChange}
placeholder={placeholder}
/>
</div>
);
}

export default NumberInput;
Empty file.
Loading
Loading