Skip to content

Commit 00f71a3

Browse files
authored
[Feat] : Ft/post editor (blob, file 두가지 버전) (#12)
* [Feat] : 게시글작성 초안 * [Feat] : 회원가입 기능 완성(blob버전) * [Feat] : ui 수정 및 날씨 키워드 추가 , 키워드별 분리 * [Feat] : useCallback 사용 * [Fix] : 엔터 시 키워드 두번 입력되는 문제 수정 * [Feat] : 이미지 삭제버튼추가, 편의성을 위한 버튼 및 문구 수정 * [Feat] : bloburl과 file 둘다 보내는 형식 [description] : firebase에 저장할 bloburl과 file객체 둘 다 저장
1 parent f6249e9 commit 00f71a3

File tree

10 files changed

+463
-8
lines changed

10 files changed

+463
-8
lines changed

.gitmessage

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ label : 제목의 길이는 최대 40자로 간단 명료하게 작성
33
# label은 소문자로 작성한다.
44
# 제목에 .(마침표) 금지
55
# <label> 리스트
6-
# Feat : 새로운 기능
7-
# Fix : 버그 수정
8-
# Style : 코드 컨벤션 변경, 코드 수정이 없는 경우
9-
# Docs : 문서 (문서 추가, 수정, 삭제)
10-
# Chore : 기타 변경사항 (빌드 스크립트 수정, 라이브러리 추가 등)
11-
# Test : 테스트 코드 추가
6+
# ✨Feat : 새로운 기능
7+
# 🚑️Fix : 버그 수정
8+
# 🎨Style : 코드 컨벤션 변경, 코드 수정이 없는 경우
9+
# 📝Docs : 문서 (문서 추가, 수정, 삭제)
10+
# 🔧Chore : 기타 변경사항 (빌드 스크립트 수정, 라이브러리 추가 등)
11+
# 🧪Test : 테스트 코드 추가
12+
# ♻️Refactor
1213
[description]
1314
# 내용의 길이는 한 줄당 60글자 내외에서 줄 바꿈. 한글로 간단 명료하게 작성
1415
# 어떻게 보다는 무엇을, 왜 변경했는지를 작성할 것 (필수)

firebase/firebaseStorage.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { getStorage } from "firebase/storage";
2+
import app from "./firebaseConfig";
3+
4+
export const storage = getStorage(app);

package-lock.json

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"react": "18.2.0",
2424
"react-dom": "18.2.0",
2525
"react-icons": "^4.10.1",
26+
"recoil": "^0.7.7",
2627
"styled-components": "^6.0.7",
2728
"tailwindcss": "3.3.3",
2829
"typescript": "5.1.6"

pages/_app.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { AppProps } from "next/app";
33
import Navbar from "./components/Navbar";
44
import "../public/fonts/font.css";
55
import "../public/fonts/notoSansKr.css";
6+
import { RecoilRoot } from "recoil";
67

78
export default function App({ Component, pageProps }: AppProps) {
89
if (process.env.NODE_ENV === "development") {
@@ -19,13 +20,13 @@ export default function App({ Component, pageProps }: AppProps) {
1920
}
2021
}
2122
return (
22-
<>
23+
<RecoilRoot>
2324
<div className="wrap">
2425
<div className="container">
2526
<Navbar />
2627
<Component {...pageProps} />
2728
</div>
2829
</div>
29-
</>
30+
</RecoilRoot>
3031
);
3132
}

pages/postEditor/ImageUpload.tsx

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import React, { useState, ChangeEvent, useCallback } from "react";
2+
import { useRecoilState } from "recoil";
3+
import { uploadedImageFilesState, uploadedImageUrlsState } from "../../utils/atoms";
4+
5+
const ImageUpload = () => {
6+
const [uploadedImageFiles, setUploadedImageFiles] = useRecoilState(uploadedImageFilesState);
7+
const [, setUploadedImageUrls] = useRecoilState(uploadedImageUrlsState);
8+
const [previews, setPreviews] = useState<string[]>([]);
9+
10+
const handleDeleteClick = (index: number) => {
11+
setPreviews((prevPreviews) => prevPreviews.filter((_, i) => i !== index));
12+
setUploadedImageFiles((prevImages) => prevImages.filter((_, i) => i !== index));
13+
};
14+
15+
const handleAddImageButtonClick = (e: React.MouseEvent<HTMLLabelElement>) => {
16+
if (uploadedImageFiles.length > 6) {
17+
alert("최대 6개의 이미지만 등록 가능합니다!");
18+
e.preventDefault();
19+
}
20+
};
21+
22+
const handleImageChange = useCallback(
23+
async (e: ChangeEvent<HTMLInputElement>) => {
24+
e.preventDefault();
25+
26+
const files = e.target.files;
27+
28+
if (files) {
29+
let fileArray = Array.from(files);
30+
31+
// Check the number of images
32+
if (fileArray.length + previews.length > 6) {
33+
alert("최대 6개의 이미지만 등록 가능합니다!");
34+
fileArray = fileArray.slice(0, Math.max(0, 6 - previews.length));
35+
}
36+
37+
const imageURLs = fileArray.map((file) => URL.createObjectURL(file));
38+
setPreviews((prevPreviews) => [...prevPreviews, ...imageURLs]);
39+
40+
// Recoil 상태 업데이트
41+
setUploadedImageFiles((prevImages) => [...prevImages, ...fileArray]);
42+
// blob url
43+
setUploadedImageUrls((prevUrls) => [...prevUrls, ...imageURLs]);
44+
}
45+
46+
e.target.value = "";
47+
},
48+
[uploadedImageFiles],
49+
);
50+
51+
return (
52+
<div>
53+
<div className="flex overflow-auto">
54+
{previews.map((preview, index) => (
55+
<div key={index} className="h-[300px] w-[230px] mr-3 relative flex-shrink-0 ">
56+
<img src={preview} alt={`Preview ${index + 1}`} className="h-full w-full mr-3 rounded-lg" />
57+
<button
58+
onClick={() => handleDeleteClick(index)}
59+
className="absolute top-3 right-3 text-sm text-slate-800 w-5 h-5 rounded-full bg-slate-100"
60+
>
61+
X
62+
</button>
63+
</div>
64+
))}
65+
<input type="file" id="file-input" onChange={handleImageChange} accept="image/*" multiple className="hidden" />
66+
<label
67+
htmlFor="file-input"
68+
onClick={handleAddImageButtonClick}
69+
className="btn h-[300px] w-[230px] flex-shrink-0"
70+
>
71+
<p className=" text-5xl font-light">+</p>
72+
</label>
73+
</div>
74+
<p className=" text-red-600 text-xs mt-1 ml-1">최대 6장까지 업로드 가능합니다.</p>
75+
</div>
76+
);
77+
};
78+
79+
export default ImageUpload;

pages/postEditor/KeywordCheckbox.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { ChangeEvent } from "react";
2+
3+
const KeywordCheckbox = ({
4+
keyword,
5+
isChecked,
6+
onChange,
7+
}: {
8+
keyword: string;
9+
isChecked: boolean;
10+
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
11+
}) => (
12+
<div key={keyword}>
13+
<input
14+
type="checkbox"
15+
id={keyword}
16+
value={keyword}
17+
onChange={onChange}
18+
className="hidden peer"
19+
checked={isChecked}
20+
/>
21+
<label
22+
htmlFor={keyword}
23+
className="badge badge-outline border-gray-300 peer-checked:bg-slate-700 peer-checked:text-white p-3 m-1 text-xs whitespace-nowrap"
24+
>
25+
{keyword}
26+
</label>
27+
</div>
28+
);
29+
30+
export default KeywordCheckbox;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { ChangeEvent } from "react";
2+
3+
const KeywordRadioButton = ({
4+
name,
5+
keyword,
6+
isChecked,
7+
onChange,
8+
}: {
9+
name: string;
10+
keyword: string;
11+
isChecked: boolean;
12+
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
13+
}) => (
14+
<div key={keyword}>
15+
<input
16+
type="radio"
17+
id={keyword}
18+
name={name}
19+
value={keyword}
20+
onChange={onChange}
21+
className="hidden peer"
22+
checked={isChecked}
23+
/>
24+
<label
25+
htmlFor={keyword}
26+
className={
27+
"badge badge-outline border-gray-300 peer-checked:bg-slate-700 peer-checked:text-white p-3 m-1 text-xs whitespace-nowrap"
28+
}
29+
>
30+
{keyword}
31+
</label>
32+
</div>
33+
);
34+
35+
export default KeywordRadioButton;

0 commit comments

Comments
 (0)