Skip to content

Commit 7e65f6f

Browse files
authored
Merge pull request #104 from FE9-2/feat/community/writing
feat: 알바토크/글쓰기페이지
2 parents 134aa0a + c56d7bc commit 7e65f6f

File tree

9 files changed

+10444
-46
lines changed

9 files changed

+10444
-46
lines changed

.storybook/Configure.mdx

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,23 @@ import Accessibility from "./assets/accessibility.png";
1616
import Theming from "./assets/theming.png";
1717
import AddonLibrary from "./assets/addon-library.png";
1818

19-
export const RightArrow = () => <svg
20-
viewBox="0 0 14 14"
21-
width="8px"
22-
height="14px"
23-
style={{
24-
marginLeft: '4px',
25-
display: 'inline-block',
26-
shapeRendering: 'inherit',
27-
verticalAlign: 'middle',
28-
fill: 'currentColor',
29-
'path fill': 'currentColor'
19+
export const RightArrow = () => (
20+
<svg
21+
viewBox="0 0 14 14"
22+
width="8px"
23+
height="14px"
24+
style={{
25+
marginLeft: "4px",
26+
display: "inline-block",
27+
shapeRendering: "inherit",
28+
verticalAlign: "middle",
29+
fill: "currentColor",
30+
"path fill": "currentColor",
3031
}}
31-
>
32-
<path d="m11.1 7.35-5.5 5.5a.5.5 0 0 1-.7-.7L10.04 7 4.9 1.85a.5.5 0 1 1 .7-.7l5.5 5.5c.2.2.2.5 0 .7Z" />
33-
</svg>
32+
>
33+
<path d="m11.1 7.35-5.5 5.5a.5.5 0 0 1-.7-.7L10.04 7 4.9 1.85a.5.5 0 1 1 .7-.7l5.5 5.5c.2.2.2.5 0 .7Z" />
34+
</svg>
35+
);
3436

3537
<Meta title="Configure your project" />
3638

@@ -39,6 +41,7 @@ export const RightArrow = () => <svg
3941
# Configure your project
4042

4143
Because Storybook works separately from your app, you'll need to configure it for your specific stack and setup. Below, explore guides for configuring Storybook with popular frameworks and tools. If you get stuck, learn how you can ask for help from our community.
44+
4245
</div>
4346
<div className="sb-section">
4447
<div className="sb-section-item">
@@ -97,6 +100,7 @@ export const RightArrow = () => <svg
97100
# Do more with Storybook
98101

99102
Now that you know the basics, let's explore other parts of Storybook that will improve your experience. This list is just to get you started. You can customise Storybook in many ways to fit your needs.
103+
100104
</div>
101105

102106
<div className="sb-section">
@@ -234,12 +238,12 @@ export const RightArrow = () => <svg
234238
>Star on GitHub<RightArrow /></a>
235239
</div>
236240
<div className="sb-section-item">
237-
<Image
241+
<Image
238242
width={33}
239243
height={32}
240244
layout="fixed"
241-
src={Discord}
242-
alt="Discord logo"
245+
src={Discord}
246+
alt="Discord logo"
243247
className="sb-explore-image"
244248
/>
245249
<div>
@@ -252,12 +256,12 @@ export const RightArrow = () => <svg
252256
</div>
253257
</div>
254258
<div className="sb-section-item">
255-
<Image
259+
<Image
256260
width={32}
257261
height={32}
258262
layout="fixed"
259-
src={Youtube}
260-
alt="Youtube logo"
263+
src={Youtube}
264+
alt="Youtube logo"
261265
className="sb-explore-image"
262266
/>
263267
<div>
@@ -270,12 +274,12 @@ export const RightArrow = () => <svg
270274
</div>
271275
</div>
272276
<div className="sb-section-item">
273-
<Image
277+
<Image
274278
width={33}
275279
height={32}
276280
layout="fixed"
277-
src={Tutorials}
278-
alt="A book"
281+
src={Tutorials}
282+
alt="A book"
279283
className="sb-explore-image"
280284
/>
281285
<p>Follow guided walkthroughs on for key workflows.</p>
@@ -285,6 +289,7 @@ export const RightArrow = () => <svg
285289
target="_blank"
286290
>Discover tutorials<RightArrow /></a>
287291
</div>
292+
288293
</div>
289294

290295
<style>

public/404.json

Lines changed: 7825 additions & 1 deletion
Large diffs are not rendered by default.

public/error.json

Lines changed: 2293 additions & 1 deletion
Large diffs are not rendered by default.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
"use client";
2+
import { useRouter } from "next/router";
3+
4+
const DetailPage = () => {
5+
const router = useRouter();
6+
const { id } = router.query;
7+
8+
return <div>글 상세 페이지 - ID: {id}</div>;
9+
};
10+
11+
export default DetailPage;
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
"use client";
2+
3+
import React, { useEffect, useState } from "react";
4+
import { useForm, SubmitHandler, Controller } from "react-hook-form";
5+
import { useRouter } from "next/navigation";
6+
import Button from "../../../components/button/default/Button";
7+
import BaseInput from "@/app/components/input/text/BaseInput";
8+
import ImageInputwithPlaceHolder from "../../../components/input/file/ImageInput/ImageInputwithPlaceHolder";
9+
import { usePost } from "../../../../hooks/usePost";
10+
11+
interface FormInputs {
12+
title: string;
13+
content: string;
14+
imageUrl?: string;
15+
}
16+
17+
export default function AddTalk() {
18+
const {
19+
control,
20+
handleSubmit,
21+
setValue,
22+
formState: { errors },
23+
} = useForm<FormInputs>({ defaultValues: { title: "", content: "" } });
24+
25+
const [imageUrls, setImageUrls] = useState<string[]>([]);
26+
const router = useRouter();
27+
const { mutate: createPost, isPending } = usePost();
28+
29+
const onSubmit: SubmitHandler<FormInputs> = (data) => {
30+
if (!data.title || !data.content) {
31+
alert("제목과 내용을 입력하세요.");
32+
return;
33+
}
34+
35+
const postData = {
36+
...data,
37+
imageUrl: imageUrls.join(","),
38+
};
39+
40+
console.log("폼 데이터 확인:", postData);
41+
42+
createPost(postData, {
43+
onSuccess: (response) => {
44+
alert("게시글이 등록되었습니다.");
45+
const boardId = response?.id;
46+
router.push(`/boards/${boardId}`);
47+
},
48+
onError: (err) => {
49+
alert(err.message || "게시글 등록에 실패했습니다.");
50+
},
51+
});
52+
};
53+
54+
useEffect(() => {
55+
const handleImageListChanged = (event: CustomEvent) => {
56+
const urls = event.detail.urls; // 이벤트에서 전달된 URL 리스트
57+
console.log("업데이트된 이미지 URL:", urls); // 디버깅용
58+
setImageUrls(urls);
59+
setValue("imageUrl", urls.join(",")); // React Hook Form에 동기화
60+
};
61+
62+
window.addEventListener("imageListChanged", handleImageListChanged as EventListener);
63+
64+
return () => {
65+
window.removeEventListener("imageListChanged", handleImageListChanged as EventListener);
66+
};
67+
}, [setValue]);
68+
69+
const handleImageClick = () => {
70+
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
71+
if (fileInput) {
72+
fileInput.click();
73+
}
74+
};
75+
76+
return (
77+
<>
78+
<div className="flex min-h-screen w-full items-start justify-center bg-grayscale-50 py-8 font-nexon">
79+
<div className="w-full max-w-[1480px] rounded-md bg-white px-4 sm:px-6 md:px-8 lg:px-10">
80+
<div className="mb-[40px] flex h-[58px] w-full items-center justify-between border-b border-[#line-100] md:h-[78px] lg:h-[126px]">
81+
<h1 className="flex items-center text-[18px] font-semibold md:text-[20px] lg:text-[32px]">글쓰기</h1>
82+
<div className="hidden space-x-1 font-semibold md:flex md:space-x-2 lg:space-x-4">
83+
<Button
84+
variant="solid"
85+
className="bg-grayscale-100 text-grayscale-50 hover:bg-grayscale-200 md:h-[46px] md:w-[108px] md:text-[14px] lg:h-[58px] lg:w-[180px] lg:text-[18px]"
86+
onClick={() => router.push("/albaTalk")}
87+
>
88+
취소
89+
</Button>
90+
<Button
91+
variant="solid"
92+
className="bg-primary-orange-300 text-grayscale-50 hover:bg-orange-400 md:h-[46px] md:w-[108px] md:text-[14px] lg:h-[58px] lg:w-[180px] lg:text-[18px]"
93+
onClick={handleSubmit(onSubmit)}
94+
disabled={isPending}
95+
>
96+
{isPending ? "등록 중..." : "등록하기"}
97+
</Button>
98+
</div>
99+
</div>
100+
<form
101+
className="mx-auto max-w-[1432px] space-y-6 px-2 sm:px-0 md:space-y-8"
102+
onSubmit={handleSubmit(onSubmit)}
103+
>
104+
<div className="w-full">
105+
<label
106+
htmlFor="title"
107+
className="mb-2 flex items-center text-[16px] font-medium text-black-300 md:text-[18px] lg:text-[20px]"
108+
>
109+
제목<span className="ml-1 text-primary-orange-300">*</span>
110+
</label>
111+
<Controller
112+
name="title"
113+
control={control}
114+
rules={{ required: "제목을 입력하세요." }}
115+
render={({ field }) => (
116+
<BaseInput
117+
{...field}
118+
type="text"
119+
variant="white"
120+
name="title"
121+
size="w-full h-[52px] md:h-[54px] lg:h-[64px]"
122+
placeholder="제목을 입력하세요"
123+
errormessage={errors.title?.message}
124+
/>
125+
)}
126+
/>
127+
</div>
128+
129+
<div className="w-full">
130+
<label
131+
htmlFor="content"
132+
className="mb-2 flex items-center text-[16px] font-medium text-black-300 md:text-[18px] lg:text-[20px]"
133+
>
134+
내용<span className="ml-1 text-primary-orange-300">*</span>
135+
</label>
136+
<Controller
137+
name="content"
138+
control={control}
139+
rules={{ required: "내용을 입력하세요." }}
140+
render={({ field }) => (
141+
<div className="relative w-full">
142+
<BaseInput
143+
{...field}
144+
type="text"
145+
variant="white"
146+
name="content"
147+
size="w-full h-[180px] md:h-[200px] lg:h-[240px]"
148+
placeholder=""
149+
errormessage={errors.content?.message}
150+
wrapperClassName="!items-start"
151+
innerClassName="opacity-0 !h-full"
152+
/>
153+
<textarea
154+
{...field}
155+
className="absolute left-0 top-0 h-full w-full resize-none rounded-lg bg-background-200 p-[14px] placeholder:text-grayscale-400 focus:outline-none focus:ring-2 focus:ring-primary-orange-300 lg:py-[18px]"
156+
placeholder="내용을 입력하세요"
157+
/>
158+
</div>
159+
)}
160+
/>
161+
</div>
162+
163+
<div className="w-full" onClick={handleImageClick}>
164+
<label
165+
htmlFor="image"
166+
className="mb-2 block text-[16px] font-medium text-black-300 md:text-[18px] lg:text-[20px]"
167+
>
168+
이미지
169+
</label>
170+
<ImageInputwithPlaceHolder />
171+
</div>
172+
</form>
173+
</div>
174+
</div>
175+
{/* 모바일 버전 버튼 */}
176+
<div className="fixed bottom-4 left-4 right-4 flex w-full flex-col items-center space-y-2 rounded-t-lg bg-white p-4 font-semibold md:hidden">
177+
<button
178+
className="mb-2 h-[58px] w-[327px] rounded-[8px] bg-grayscale-100 text-white hover:bg-grayscale-200"
179+
onClick={() => router.push("/albaTalk")}
180+
>
181+
취소
182+
</button>
183+
<button
184+
className="h-[58px] w-[327px] rounded-[8px] bg-primary-orange-300 text-white hover:bg-orange-400"
185+
onClick={handleSubmit(onSubmit)}
186+
disabled={isPending}
187+
>
188+
{isPending ? "등록 중..." : "등록하기"}
189+
</button>
190+
</div>
191+
</>
192+
);
193+
}

src/app/components/card/board/BoardComment.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,13 @@ const BoardComment: React.FC<BoardCommentProps> = ({
7272
className="hover:text-grayscale-700 flex items-center justify-center text-grayscale-500"
7373
aria-label="Options"
7474
>
75-
<Image src={kebabSrc} alt="Kebab Menu Icon" width={28} height={28} /> {/* 크기 조정 */}
75+
<Image
76+
src={kebabSrc}
77+
alt="Kebab Menu Icon"
78+
width={isLargeScreen ? 28 : 24}
79+
height={isLargeScreen ? 28 : 24}
80+
/>{" "}
81+
{/* 크기 조정 */}
7682
</button>
7783
</div>
7884

src/app/components/card/board/CardBoard.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,13 @@ const CardBoard: React.FC<CardBoardProps> = ({
6868
className="hover:text-grayscale-700 flex items-center justify-center text-grayscale-500"
6969
aria-label="Options"
7070
>
71-
<Image src={kebabSrc} alt="Kebab Menu Icon" width={28} height={28} /> {/* 크기 조정 */}
71+
<Image
72+
src={kebabSrc}
73+
alt="Kebab Menu Icon"
74+
width={isLargeScreen ? 28 : 24}
75+
height={isLargeScreen ? 28 : 24}
76+
/>{" "}
77+
{/* 크기 조정 */}
7278
</button>
7379
</div>
7480
{/* Content */}

0 commit comments

Comments
 (0)