Skip to content

Commit fad7bf7

Browse files
authored
Merge pull request #301 from hpk5802/Next-강수민-sprint10
Next 강수민 sprint10
2 parents 226f6a1 + 8ea45b6 commit fad7bf7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1628
-187
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ yarn-error.log*
2626

2727
# local env files
2828
.env*.local
29+
.env
2930

3031
# vercel
3132
.vercel
3233

3334
# typescript
3435
*.tsbuildinfo
35-
next-env.d.ts
36+
next-env.d.ts

components/addBoard/Content.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { FormInputInterface } from "@/types/addBoard";
2+
import { PropsWithChildren, ReactNode } from "react";
3+
import { FieldValues, UseFormRegister } from "react-hook-form";
4+
5+
interface ContentProps {
6+
name: keyof FormInputInterface;
7+
register: UseFormRegister<FieldValues>;
8+
required: boolean;
9+
children: ReactNode;
10+
}
11+
12+
function Content({
13+
name,
14+
register,
15+
required,
16+
children,
17+
}: PropsWithChildren<ContentProps>) {
18+
return (
19+
<div className='form-input-wrap'>
20+
<label>{children}</label>
21+
<textarea
22+
{...register(name, { required })}
23+
placeholder='내용을 입력해주세요.'
24+
/>
25+
</div>
26+
);
27+
}
28+
29+
export default Content;
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { FormInputInterface } from "@/types/addBoard";
2+
import Image from "next/image";
3+
import { ReactNode, useEffect, useRef, useState } from "react";
4+
import { FieldValues, UseFormRegister, UseFormWatch } from "react-hook-form";
5+
6+
interface ImgFileInputProps {
7+
name: keyof FormInputInterface;
8+
register: UseFormRegister<FieldValues>;
9+
watch: UseFormWatch<FieldValues>;
10+
setValue: any;
11+
children: ReactNode;
12+
}
13+
14+
function ImgFileInput({
15+
name,
16+
register,
17+
watch,
18+
setValue,
19+
children,
20+
}: ImgFileInputProps) {
21+
const [imagePath, setImagePath] = useState<string | null>(null);
22+
const [showWarn, setShowWarn] = useState(false);
23+
const inputRef = useRef<HTMLInputElement | null>(null);
24+
const img = watch(name);
25+
26+
/**
27+
* 이미지 파일 추가 - 1개 이상인 경우 클릭 시 경고 노출
28+
*/
29+
const handleClick = () => {
30+
if (!imagePath && inputRef.current) inputRef.current.click();
31+
else setShowWarn(true);
32+
};
33+
34+
/**
35+
* 이미지 삭제
36+
*/
37+
const handleDelete = () => {
38+
setImagePath(null);
39+
setValue(name, null);
40+
setShowWarn(false);
41+
};
42+
43+
useEffect(() => {
44+
if (img && img[0]) {
45+
const file = img[0];
46+
setImagePath(URL.createObjectURL(file));
47+
} else {
48+
setImagePath(null);
49+
}
50+
}, [img]);
51+
return (
52+
<div className='form-input-wrap'>
53+
<label>{children}</label>
54+
<input
55+
type='file'
56+
className='sr-only'
57+
accept='image/*'
58+
{...register(name)}
59+
ref={(el) => {
60+
inputRef.current = el;
61+
register(name).ref(el);
62+
}}
63+
/>
64+
{/* className='sr-only' */}
65+
<div className='upload-area'>
66+
<button
67+
className='btn-upload-img'
68+
type='button'
69+
title='이미지 등록'
70+
onClick={handleClick}
71+
>
72+
<span>
73+
<svg
74+
width='48'
75+
height='48'
76+
viewBox='0 0 48 48'
77+
fill='none'
78+
xmlns='http://www.w3.org/2000/svg'
79+
>
80+
<path
81+
d='M10 24H38'
82+
stroke='#9CA3AF'
83+
strokeWidth='4'
84+
strokeLinecap='round'
85+
/>
86+
<path
87+
d='M24 38V10'
88+
stroke='#9CA3AF'
89+
strokeWidth='4'
90+
strokeLinecap='round'
91+
/>
92+
</svg>
93+
</span>
94+
이미지 등록
95+
</button>
96+
{imagePath && (
97+
<div className='thumbnail'>
98+
<Image fill src={imagePath} alt='thumbnail' />
99+
<button
100+
type='button'
101+
className='btn-delete-thumbnail'
102+
onClick={handleDelete}
103+
>
104+
<span className='sr-only'>삭제</span>
105+
</button>
106+
</div>
107+
)}
108+
</div>
109+
{showWarn && (
110+
<div className='error-msg'>*이미지 등록은 최대 1개까지 가능합니다.</div>
111+
)}
112+
</div>
113+
);
114+
}
115+
116+
export default ImgFileInput;

components/addBoard/TitleInput.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { FormInputInterface } from "@/types/addBoard";
2+
import { PropsWithChildren, ReactNode } from "react";
3+
import { FieldValues, UseFormRegister } from "react-hook-form";
4+
5+
interface TitleInputProps {
6+
name: keyof FormInputInterface;
7+
register: UseFormRegister<FieldValues>;
8+
required: boolean;
9+
children: ReactNode;
10+
}
11+
12+
function TitleInput({
13+
name,
14+
register,
15+
required,
16+
children,
17+
}: PropsWithChildren<TitleInputProps>) {
18+
return (
19+
<div className='form-input-wrap'>
20+
<label>{children}</label>
21+
<input
22+
{...register(name, { required })}
23+
placeholder='제목을 입력해주세요.'
24+
/>
25+
</div>
26+
);
27+
}
28+
29+
export default TitleInput;

components/addItem/ImgFileInput.tsx

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,26 @@ function ImgFileInput({
9595
onClick={handleClick}
9696
>
9797
<span>
98-
<Image fill src='/icons/ic_plus.svg' alt='이미지 등록' />
98+
<svg
99+
width='48'
100+
height='48'
101+
viewBox='0 0 48 48'
102+
fill='none'
103+
xmlns='http://www.w3.org/2000/svg'
104+
>
105+
<path
106+
d='M10 24H38'
107+
stroke='#9CA3AF'
108+
strokeWidth='4'
109+
strokeLinecap='round'
110+
/>
111+
<path
112+
d='M24 38V10'
113+
stroke='#9CA3AF'
114+
strokeWidth='4'
115+
strokeLinecap='round'
116+
/>
117+
</svg>
99118
</span>
100119
이미지 등록
101120
</button>

components/articles/Article.tsx

Lines changed: 100 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,37 @@
11
import { ArticleInterface } from "@/types/article";
22
import { formatDate } from "@/utils/formatDate";
3-
import Image from "next/image";
43
import ImageArticle from "./ImageArticle";
54
import Link from "next/link";
5+
import clsx from "clsx";
66

7-
function Article({
8-
isBest = false,
9-
article,
10-
}: {
7+
interface ArticleProps {
118
isBest?: boolean;
129
article: ArticleInterface;
13-
}) {
10+
}
11+
12+
function Article({ isBest = false, article }: ArticleProps) {
1413
const { id, title, image, likeCount, writer, updatedAt } = article;
1514
return (
1615
<Link href={`/boards/${id}`} title={`${title} 상세보기`}>
17-
<div className={["article", isBest ? "best" : ""].join(" ")}>
16+
<div className={clsx("article", isBest && "best")}>
1817
{isBest && (
1918
<div className='medal-wrap'>
2019
<span>
21-
<Image fill src='/icons/ic_medal.svg' alt='best' />
20+
<svg
21+
width='16'
22+
height='16'
23+
viewBox='0 0 16 16'
24+
fill='none'
25+
xmlns='http://www.w3.org/2000/svg'
26+
>
27+
<rect x='4' y='3' width='8' height='7' rx='2.5' fill='white' />
28+
<path
29+
fillRule='evenodd'
30+
clipRule='evenodd'
31+
d='M11.0912 5.78199L7.35789 9.51532L4.90923 7.06665C4.80464 6.95255 4.74815 6.80247 4.75151 6.64773C4.75488 6.49298 4.81786 6.34551 4.9273 6.23606C5.03675 6.12661 5.18422 6.06364 5.33897 6.06027C5.49371 6.0569 5.64379 6.1134 5.75789 6.21798L7.35789 7.81799L10.2426 4.93332C10.2983 4.87759 10.3644 4.83339 10.4372 4.80323C10.5101 4.77308 10.5881 4.75755 10.6669 4.75755C10.7457 4.75755 10.8237 4.77308 10.8965 4.80323C10.9693 4.83339 11.0355 4.87759 11.0912 4.93332C11.147 4.98904 11.1912 5.0552 11.2213 5.128C11.2515 5.20081 11.267 5.27885 11.267 5.35765C11.267 5.43646 11.2515 5.51449 11.2213 5.5873C11.1912 5.66011 11.147 5.72626 11.0912 5.78199ZM14.0979 7.04665C14.1604 6.98414 14.1955 6.89937 14.1955 6.81099C14.1955 6.7226 14.1604 6.63783 14.0979 6.57532L13.1312 5.60865C13.0898 5.56732 13.06 5.51577 13.0449 5.45922C13.0298 5.40268 13.0299 5.34314 13.0452 5.28665L13.3986 3.96665C13.4214 3.88125 13.4093 3.7903 13.365 3.71378C13.3208 3.63727 13.248 3.58147 13.1626 3.55865L11.8426 3.20532C11.7859 3.19014 11.7342 3.16026 11.6928 3.1187C11.6513 3.07714 11.6216 3.02538 11.6066 2.96865L11.2532 1.64865C11.2303 1.56321 11.1743 1.49037 11.0977 1.44612C11.0211 1.40188 10.93 1.38984 10.8446 1.41265L9.52389 1.76665C9.46744 1.78176 9.408 1.78171 9.35158 1.76649C9.29516 1.75127 9.24376 1.72143 9.20256 1.67998L8.23589 0.713318C8.17338 0.650828 8.08861 0.615723 8.00023 0.615723C7.91184 0.615723 7.82707 0.650828 7.76456 0.713318L6.79789 1.68065C6.75652 1.72204 6.705 1.75183 6.64848 1.76704C6.59197 1.78225 6.53245 1.78235 6.47589 1.76732L5.15589 1.41265C5.11356 1.40126 5.0694 1.39834 5.02594 1.40404C4.98248 1.40974 4.94057 1.42396 4.90261 1.44588C4.86465 1.46781 4.83138 1.497 4.80472 1.53179C4.77806 1.56659 4.75852 1.6063 4.74723 1.64865L4.39323 2.96865C4.37816 3.02518 4.34848 3.07675 4.30717 3.11817C4.26586 3.1596 4.21438 3.18943 4.15789 3.20465L2.83723 3.55865C2.75196 3.58161 2.67928 3.63748 2.63516 3.71397C2.59104 3.79047 2.57907 3.88134 2.60189 3.96665L2.95523 5.28665C2.97055 5.34314 2.97066 5.40268 2.95556 5.45922C2.94046 5.51577 2.91067 5.56732 2.86923 5.60865L1.90256 6.57532C1.87154 6.60623 1.84692 6.64295 1.83013 6.68339C1.81333 6.72384 1.80469 6.76719 1.80469 6.81099C1.80469 6.85478 1.81333 6.89813 1.83013 6.93858C1.84692 6.97902 1.87154 7.01574 1.90256 7.04665L2.86923 8.01332C2.91067 8.05479 2.94045 8.10645 2.95555 8.16311C2.97064 8.21976 2.97053 8.27939 2.95523 8.33598L2.60189 9.65598C2.57907 9.74129 2.59104 9.83217 2.63516 9.90867C2.67928 9.98516 2.75196 10.041 2.83723 10.064L4.15789 10.418C4.21438 10.4332 4.26586 10.463 4.30717 10.5045C4.34848 10.5459 4.37816 10.5975 4.39323 10.654L4.74656 11.974C4.75825 12.0168 4.77837 12.0568 4.80573 12.0917C4.83309 12.1266 4.86714 12.1557 4.90589 12.1773L3.66523 15.522H7.12056L7.99989 13.15L8.88056 15.522H12.3352L11.0946 12.1773C11.1333 12.1558 11.1673 12.1267 11.1946 12.0918C11.2218 12.0569 11.2418 12.0168 11.2532 11.974L11.6066 10.654C11.6216 10.5973 11.6514 10.5457 11.6928 10.5042C11.7343 10.4628 11.7859 10.433 11.8426 10.418L13.1626 10.0647C13.2049 10.0534 13.2446 10.0338 13.2794 10.0072C13.3142 9.98049 13.3434 9.94723 13.3653 9.90927C13.3872 9.87131 13.4015 9.8294 13.4072 9.78594C13.4129 9.74248 13.4099 9.69832 13.3986 9.65598L13.0452 8.33598C13.0299 8.2795 13.0298 8.21996 13.0449 8.16341C13.06 8.10686 13.0898 8.05531 13.1312 8.01398L14.0979 7.04732V7.04665Z'
32+
fill='#FFC117'
33+
/>
34+
</svg>
2235
</span>
2336
Best
2437
</div>
@@ -33,7 +46,17 @@ function Article({
3346
<div className='nickname'>{writer.nickname}</div>
3447
<div className='like-count'>
3548
<span>
36-
<Image fill src='/icons/ic_heart.svg' alt='하트' />
49+
<svg
50+
viewBox='0 0 16 16'
51+
fill='none'
52+
xmlns='http://www.w3.org/2000/svg'
53+
>
54+
<path
55+
d='M3.5573 9.31157C3.40759 9.18016 3.28782 9.07475 3.20004 8.99739V8.95147L3.0243 8.77574C2.3305 8.08193 1.93337 7.16547 1.93337 6.2V6.07612C1.99484 4.2062 3.59191 2.66667 5.46671 2.66667C5.74499 2.66667 6.1142 2.76344 6.48225 2.96162C6.82958 3.14864 7.13445 3.40383 7.33689 3.68842C7.63138 4.31494 8.53917 4.30366 8.81157 3.65457C8.97931 3.36092 9.2733 3.0955 9.62562 2.89902C9.99324 2.694 10.3597 2.6 10.6 2.6C12.5294 2.6 14.0717 4.12742 14.1334 6.07581V6.2C14.1334 7.24201 13.73 8.14473 13.0613 8.75771L12.8667 8.93606V8.97957C12.7656 9.06525 12.6353 9.17766 12.4834 9.30974C12.1463 9.6029 11.6949 9.99999 11.2033 10.4332C11.0456 10.5722 10.8838 10.7149 10.7202 10.8592C9.86394 11.6143 8.9594 12.4119 8.34603 12.9417C8.16951 13.0861 7.89724 13.0861 7.72071 12.9417C6.9885 12.3093 5.82589 11.2962 4.83654 10.4316C4.34091 9.9984 3.88917 9.60286 3.5573 9.31157Z'
56+
stroke='#4B5563'
57+
strokeWidth='1.2'
58+
/>
59+
</svg>
3760
</span>
3861
{likeCount}
3962
</div>
@@ -44,14 +67,80 @@ function Article({
4467
<div className='desc-wrap'>
4568
<div className='user-wrap'>
4669
<div className='user-profile'>
47-
<Image fill src='/icons/ic_user.svg' alt={writer.nickname} />
70+
<svg
71+
viewBox='0 0 40 40'
72+
fill='none'
73+
xmlns='http://www.w3.org/2000/svg'
74+
>
75+
<g clipPath='url(#clip0_42798_1063)'>
76+
<circle cx='20' cy='20' r='20' fill='#D1D5DB' />
77+
<path
78+
d='M19.374 8.72955C19.8844 10.4933 18.5349 12.4367 16.9881 12.8874C15.4413 13.338 13.328 12.4034 12.8176 10.6396C12.3072 8.87592 13.3786 6.43217 14.9255 5.98153C16.9881 5.38063 18.8637 6.96583 19.374 8.72955Z'
79+
fill='white'
80+
/>
81+
<path
82+
d='M20.2247 8.72955C19.7143 10.4933 21.0638 12.4367 22.6106 12.8874C24.1575 13.338 26.2707 12.4034 26.7811 10.6396C27.2915 8.87592 26.2201 6.43217 24.6732 5.98153C22.6106 5.38063 20.7351 6.96583 20.2247 8.72955Z'
83+
fill='white'
84+
/>
85+
<path
86+
d='M29.8016 21.6147C29.8016 26.1007 25.4028 27.7683 19.4026 27.7683C13.4025 27.7683 9.10324 26.1007 9.10324 21.6147C9.10324 16.3964 13.4025 11.7689 19.4026 11.7689C25.4028 11.7689 29.8016 16.0026 29.8016 21.6147Z'
87+
fill='white'
88+
/>
89+
<path
90+
d='M17.4167 20.7285C17.4167 22.1978 16.4736 23.8096 15.1558 23.8096C13.8379 23.8096 11.709 22.6124 11.709 20.4177C11.709 18.3559 14.0088 17.6227 16.3853 17.9992C17.8419 18.2299 17.4167 19.2593 17.4167 20.7285Z'
91+
fill='#D1D5DB'
92+
/>
93+
<path
94+
d='M16.3158 20.5796C16.3158 20.8 16.2495 20.9786 15.9181 20.9786C15.5867 20.9786 15.5204 20.8 15.5204 20.5796C15.5204 20.3592 15.653 20.1805 15.9181 20.1805C16.1832 20.1805 16.3158 20.3592 16.3158 20.5796Z'
95+
fill='white'
96+
/>
97+
<path
98+
d='M21.4258 20.7284C21.4258 22.1976 22.3688 23.8094 23.6867 23.8094C25.0045 23.8094 27.1334 22.6123 27.1334 20.4175C27.1334 18.3558 24.8336 17.6226 22.4571 17.9991C21.0006 18.2298 21.4258 19.2592 21.4258 20.7284Z'
99+
fill='#D1D5DB'
100+
/>
101+
<path
102+
d='M22.5283 20.5796C22.5283 20.8 22.5946 20.9786 22.926 20.9786C23.2574 20.9786 23.3237 20.8 23.3237 20.5796C23.3237 20.3592 23.1912 20.1805 22.926 20.1805C22.6609 20.1805 22.5283 20.3592 22.5283 20.5796Z'
103+
fill='white'
104+
/>
105+
<path
106+
fillRule='evenodd'
107+
clipRule='evenodd'
108+
d='M19.7575 30.0001C27.7508 30.0001 33.6107 27.7473 33.6107 21.6867C33.6107 14.1048 27.7508 8.38513 19.7575 8.38513C11.7642 8.38513 6.03683 14.6369 6.03683 21.6867C6.03683 27.7473 11.7642 30.0001 19.7575 30.0001ZM19.4429 27.6725C25.3226 27.6725 29.633 26.0228 29.633 21.5845C29.633 16.0322 25.3226 11.8437 19.4429 11.8437C13.5632 11.8437 9.35031 16.4219 9.35031 21.5845C9.35031 26.0228 13.5632 27.6725 19.4429 27.6725Z'
109+
fill='white'
110+
/>
111+
<g mask='url(#mask0_42798_1063)'>
112+
<ellipse
113+
cx='19.5833'
114+
cy='37.5'
115+
rx='11.25'
116+
ry='12.5'
117+
fill='white'
118+
/>
119+
</g>
120+
</g>
121+
<defs>
122+
<clipPath id='clip0_42798_1063'>
123+
<rect width='40' height='40' fill='white' />
124+
</clipPath>
125+
</defs>
126+
</svg>
48127
</div>
49128
<div className='nickname'>{writer.nickname}</div>
50129
<div className='date'>{formatDate(updatedAt)}</div>
51130
</div>
52131
<div className='like-count'>
53132
<span>
54-
<Image fill src='/icons/ic_heart.svg' alt='하트' />
133+
<svg
134+
viewBox='0 0 16 16'
135+
fill='none'
136+
xmlns='http://www.w3.org/2000/svg'
137+
>
138+
<path
139+
d='M3.5573 9.31157C3.40759 9.18016 3.28782 9.07475 3.20004 8.99739V8.95147L3.0243 8.77574C2.3305 8.08193 1.93337 7.16547 1.93337 6.2V6.07612C1.99484 4.2062 3.59191 2.66667 5.46671 2.66667C5.74499 2.66667 6.1142 2.76344 6.48225 2.96162C6.82958 3.14864 7.13445 3.40383 7.33689 3.68842C7.63138 4.31494 8.53917 4.30366 8.81157 3.65457C8.97931 3.36092 9.2733 3.0955 9.62562 2.89902C9.99324 2.694 10.3597 2.6 10.6 2.6C12.5294 2.6 14.0717 4.12742 14.1334 6.07581V6.2C14.1334 7.24201 13.73 8.14473 13.0613 8.75771L12.8667 8.93606V8.97957C12.7656 9.06525 12.6353 9.17766 12.4834 9.30974C12.1463 9.6029 11.6949 9.99999 11.2033 10.4332C11.0456 10.5722 10.8838 10.7149 10.7202 10.8592C9.86394 11.6143 8.9594 12.4119 8.34603 12.9417C8.16951 13.0861 7.89724 13.0861 7.72071 12.9417C6.9885 12.3093 5.82589 11.2962 4.83654 10.4316C4.34091 9.9984 3.88917 9.60286 3.5573 9.31157Z'
140+
stroke='#4B5563'
141+
strokeWidth='1.2'
142+
/>
143+
</svg>
55144
</span>
56145
{likeCount}
57146
</div>

0 commit comments

Comments
 (0)