Skip to content

Commit

Permalink
Merge pull request #115 from UMC-TRIPY/feat/#102
Browse files Browse the repository at this point in the history
[FEAT] 커뮤니티 글 작성 페이지
  • Loading branch information
seungboshim authored Aug 15, 2023
2 parents 8a7a6b7 + d660b3a commit b98171b
Show file tree
Hide file tree
Showing 17 changed files with 13,251 additions and 44 deletions.
12,462 changes: 12,461 additions & 1 deletion package-lock.json

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions public/images/alertCircle.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions public/images/alertCircleRed.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions public/images/calendar.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/images/folder.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions public/images/folderPlus.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions public/images/imageAdd.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions public/images/upload.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions src/app/community/write/page.tsx
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>
)
}
59 changes: 59 additions & 0 deletions src/components/editorcommunity/editorCommunity.tsx
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>
)
}
140 changes: 140 additions & 0 deletions src/components/editorcommunity/mainEditor.tsx
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>
)
}
123 changes: 123 additions & 0 deletions src/components/editorcommunity/selectCountry.tsx
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>
)
}
Loading

0 comments on commit b98171b

Please sign in to comment.