-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
[FEAT] 커뮤니티 글 작성 페이지
- Loading branch information
Showing
17 changed files
with
13,251 additions
and
44 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
'use client'; | ||
|
||
import EditorCommunity from "@/components/editorcommunity/editorCommunity"; | ||
|
||
export default function Page() { | ||
return ( | ||
<div> | ||
<EditorCommunity /> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import React, { useState } from "react"; | ||
import MainEditor from "@/components/editorcommunity/mainEditor"; | ||
import SelectCountry from "@/components/editorcommunity/selectCountry"; | ||
import TagEditor from "@/components/editorcommunity/tagEditor"; | ||
import CommonHeader from "@/components/maincommunity/CommonHeader"; | ||
|
||
export default function EditorCommunity() { | ||
// selectedCity에 선택한 도시 카테고리가 저장 -> 게시판 필터링시 전달필요 | ||
const [selectedCity, setSelectedCity] = useState<string>(""); | ||
const [cityEmpty, setCityEmpty] = useState<boolean>(false); | ||
const [title, setTitle] = useState(''); // 제목 | ||
const [titleEmpty, setTitleEmpty] = useState<boolean>(false); | ||
const [contents, setContents] = useState(''); // 내용 | ||
const [contentsEmpty, setContentsEmpty] = useState<boolean>(false); | ||
|
||
// tagEditor의 버튼 눌렀을 때 호출되는 함수들 | ||
/** selectedCity가 비었을때 setCityEmpty를 true로 */ | ||
function handleCityEmptyError() { | ||
setCityEmpty(!selectedCity); | ||
} | ||
/** title 미입력시 setTitleEmpty를 true로 */ | ||
function handleTitleEmptyError() { | ||
setTitleEmpty(!title); | ||
} | ||
/** Contents 미입력시 setContentsEmpty를 true로 */ | ||
function handleContentsEmptyError() { | ||
setContentsEmpty(!contents); | ||
} | ||
|
||
return ( | ||
<div> | ||
<CommonHeader /> | ||
{/** 도시 카테고리 선택, 제목 입력 컴포넌트 */} | ||
<SelectCountry | ||
selectedCity={selectedCity} | ||
setSelectedCity={setSelectedCity} | ||
cityEmpty={cityEmpty} | ||
onCityEmptyError={handleCityEmptyError} | ||
title={title} | ||
setTitle={setTitle} | ||
titleEmpty={titleEmpty} | ||
onTitleEmptyError={handleTitleEmptyError} | ||
/> | ||
{/** 첨부파일 모달, 내용 입력 컴포넌트 */} | ||
<MainEditor | ||
contentsEmpty={contentsEmpty} | ||
onContentsEmptyError={handleContentsEmptyError} | ||
contents={contents} | ||
setContents={setContents} | ||
/> | ||
{/** 태그 입력 컴포넌트 */} | ||
<TagEditor | ||
onCityEmptyError={handleCityEmptyError} | ||
onTitleEmptyError={handleTitleEmptyError} | ||
onContentsEmptyError={handleContentsEmptyError} | ||
/> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
import React, { useState } from "react" | ||
import Image from "next/image" | ||
import imageAdd from "public/images/imageAdd.svg" | ||
import folderPlus from "public/images/folderPlus.svg" | ||
import calenderAdd from "public/images/calendar.svg" | ||
import mapPin from "public/images/mapPin.svg" | ||
import EditorModal from "../modal/EditorModal" | ||
|
||
interface MainEditorProps { | ||
contentsEmpty: boolean; | ||
onContentsEmptyError: () => void; | ||
contents: string; | ||
setContents: (value: string) => void; | ||
} | ||
|
||
export default function MainEditor({ contentsEmpty, onContentsEmptyError, contents, setContents }: MainEditorProps) { | ||
const [modal, setModal] = useState({ | ||
isOpen: false, | ||
type: '', | ||
}); | ||
|
||
/** textarea 입력 handle 함수 */ | ||
const handleContents = (e: React.ChangeEvent<HTMLTextAreaElement>) => { | ||
setContents(e.target.value); | ||
onContentsEmptyError(); | ||
}; | ||
|
||
/** 각 type에 맞는 모달창 열기 */ | ||
const handleModalOpen = (modalType: string) => { | ||
setModal({ | ||
isOpen: true, | ||
type: modalType, | ||
}); | ||
}; | ||
|
||
const handleModalClose = () => { | ||
setModal({ | ||
isOpen: false, | ||
type: '', | ||
}); | ||
}; | ||
|
||
/** 이미지 URL을 textarea에 */ | ||
const handleImageUpload = (images: File[]) => { | ||
let imgTags = images.map((image) => `![${image.name}](URL.createObjectURL(image))`).join('\n'); | ||
setContents(contents + '\n' + imgTags); | ||
// contents에 img URL 추가 | ||
}; | ||
|
||
/** 파일 URL을 textarea에 */ | ||
const handleFileUpload = (files: File[]) => { | ||
let fileTags = files.map((file) => `[${file.name}](URL.createObjectURL(file))`).join('\n'); | ||
setContents(contents + '\n' + fileTags); | ||
// contents에 file URL 추가 | ||
}; | ||
|
||
/** 선택된 장소 id를 textarea에 */ | ||
const handlePlaceUpload = () => { | ||
setContents(contents + '\n' + checkedItems) | ||
}; | ||
|
||
const [checkedItems, setCheckedItems] = useState<Array<number>>([]); | ||
|
||
return ( | ||
<div className="mx-4"> | ||
<div className={`h-[800px] border ${!contents && contentsEmpty ? 'border-alertred' : 'border-lightgrey'} rounded-lg`}> | ||
<div className="h-[90px] border-b border-lightgrey"> | ||
<div className="flex p-5"> | ||
<button | ||
className="mx-3" | ||
onClick={() => handleModalOpen('사진')} | ||
> | ||
<Image | ||
src={imageAdd} | ||
alt="사진 추가" | ||
width={32} | ||
/> | ||
<span className="text-grey pt-1 text-xs">사진</span> | ||
</button> | ||
<button | ||
className="mx-3" | ||
onClick={() => handleModalOpen('파일')} | ||
> | ||
<Image | ||
src={folderPlus} | ||
alt="파일 추가" | ||
width={32} | ||
/> | ||
<span className="text-grey pt-1 text-xs">파일</span> | ||
</button> | ||
<button | ||
className="mx-3" | ||
onClick={() => handleModalOpen('일정')} | ||
> | ||
<Image | ||
src={calenderAdd} | ||
alt="일정 추가" | ||
width={32} | ||
/> | ||
<span className="text-grey pt-1 text-xs">일정</span> | ||
</button> | ||
<button | ||
className="mx-3" | ||
onClick={() => handleModalOpen('장소')} | ||
> | ||
<Image | ||
src={mapPin} | ||
alt="장소 추가" | ||
width={32} | ||
/> | ||
<span className="text-grey pt-1 text-xs">장소</span> | ||
</button> | ||
</div> | ||
{modal.isOpen && ( | ||
<div> | ||
<EditorModal | ||
type = {modal.type} | ||
setIsModal = {() => handleModalClose()} | ||
onImageUpload = {handleImageUpload} | ||
onFileUpload = {handleFileUpload} | ||
onPlaceUpload = {handlePlaceUpload} | ||
checkedItems={checkedItems} | ||
setCheckedItems={setCheckedItems} | ||
/> | ||
</div> | ||
)} | ||
</div> | ||
<div className="p-5 flex h-[710px]"> | ||
<textarea | ||
spellCheck="false" | ||
className="flex-grow outline-none" | ||
placeholder="내용을 입력해주세요." | ||
value={contents} | ||
onChange={(e) => handleContents(e)} | ||
/> | ||
</div> | ||
</div> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
import React from "react" | ||
|
||
interface SelectCountryProps { | ||
selectedCity: string; | ||
setSelectedCity: (city: string) => void; | ||
cityEmpty: boolean; | ||
onCityEmptyError: () => void; | ||
title: string; | ||
setTitle: (value: string) => void; | ||
titleEmpty: boolean; | ||
onTitleEmptyError: () => void; | ||
} | ||
|
||
export default function SelectCountry ({ selectedCity, setSelectedCity, cityEmpty, onCityEmptyError, title, setTitle, titleEmpty, onTitleEmptyError }: SelectCountryProps) { | ||
const locations = { | ||
"아시아": { | ||
"일본": ["도쿄", "오사카", "후쿠오카", "오키나와", "교토", "홋카이도", "일본 기타"], | ||
}, | ||
"유럽": { | ||
"영국": ["런던", "리버풀", "맨체스터", "영국 기타"], | ||
"프랑스": ["파리", "마르세유", "프랑스 기타"], | ||
"이탈리아": ["로마", "피렌체", "밀라노", "베네치아", "이탈리아 기타"], | ||
"스페인": ["마드리드", "바르셀로나", "톨레도", "세비야", "그라나다", "스페인 기타"] | ||
}, | ||
"기타": { "기타": "기타" } | ||
}; | ||
|
||
const AsiaCountries = Object.keys(locations["아시아"]); | ||
const EuropeCountries = Object.keys(locations["유럽"]); | ||
|
||
const JapanCities = locations["아시아"]["일본"]; | ||
const UKCities = locations["유럽"]["영국"]; | ||
const FranceCities = locations["유럽"]["프랑스"]; | ||
const ItalyCities = locations["유럽"]["이탈리아"]; | ||
const SpainCities = locations["유럽"]["스페인"]; | ||
|
||
type CityButtonsProps = { | ||
cities: string[]; | ||
}; | ||
|
||
const CityButtons: React.FC<CityButtonsProps> = ({ cities }) => { | ||
return ( | ||
<div className="flex"> | ||
{cities.map((city, index) => ( | ||
<button | ||
key={index} | ||
className={`mr-10 ${ | ||
city === selectedCity ? | ||
'text-primary font-bold border-b-[3px] border-b-primary' | ||
: 'border-b-[3px] border-brightgrey' // 밑줄 생길때 위아래 밀림 방지 | ||
}`} | ||
onClick={() => { | ||
setSelectedCity(city); | ||
onCityEmptyError(); | ||
}} | ||
> | ||
{city} | ||
</button> | ||
))} | ||
</div> | ||
); | ||
}; | ||
|
||
// title input 관리 함수 | ||
const handleTitle = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
setTitle(e.target.value); | ||
onTitleEmptyError(); | ||
}; | ||
|
||
return ( | ||
<div className="mx-4"> | ||
<div className="text-3xl font-bold mb-8">게시글 작성</div> | ||
<div className="text-xl font-bold">국가를 선택해주세요.</div> | ||
<div className="bg-brightgrey rounded-lg my-2"> | ||
<div className="flex"> | ||
<div className="h-[336px] flex flex-col justify-between w-1/6 py-4 pl-6 border-r border-lightgrey"> | ||
{Object.keys(locations).map((continent, index) => { | ||
return ( | ||
<span key={index} className="font-bold text-grey">{continent}</span> | ||
) | ||
})} | ||
</div> | ||
<div className="flex flex-col justify-between w-1/6 py-4 pl-6 border-r border-lightgrey"> | ||
{AsiaCountries.map((country, index) => { | ||
return ( | ||
<span key={index} className="font-bold">{country}</span> | ||
) | ||
})} | ||
{EuropeCountries.map((country, index) => { | ||
return ( | ||
<span key={index} className="font-bold">{country}</span> | ||
) | ||
})} | ||
<span className="font-bold">{Object.keys(locations["기타"])}</span> | ||
</div> | ||
<div className="flex flex-col justify-between w-2/3 py-4 pl-6"> | ||
<CityButtons cities={JapanCities}/> | ||
<CityButtons cities={UKCities}/> | ||
<CityButtons cities={FranceCities}/> | ||
<CityButtons cities={ItalyCities}/> | ||
<CityButtons cities={SpainCities}/> | ||
<CityButtons cities={[locations["기타"]["기타"]]} /> | ||
</div> | ||
</div> | ||
</div> | ||
<div className="flex justify-between py-2 mb-2"> | ||
<div className={`w-1/6 h-12 bg-brightgrey ${!selectedCity && cityEmpty ? 'border border-alertred' : ''} rounded-lg flex items-center justify-center`}> | ||
{selectedCity ? ( | ||
<span className="font-bold">{selectedCity}</span> | ||
) : ( | ||
<span className={`${cityEmpty ? 'text-alertred' : 'text-grey'}`}>국가명</span> | ||
)} | ||
</div> | ||
<input | ||
className={`w-5/6 ml-5 pl-4 border ${!title && titleEmpty ? 'border-alertred' : 'border-lightgrey'} rounded-lg`} | ||
placeholder="제목을 입력해주세요." | ||
value={title} | ||
onChange={(e) => handleTitle(e)} | ||
/> | ||
</div> | ||
</div> | ||
) | ||
} |
Oops, something went wrong.