티스토리 뷰

오늘은 기존 작성했던 UI에 간단한 기능들을 추가하였다.

 

현재로서는 아직 백엔드쪽 구축이 덜 되서 json-server를 이용하여 간단하게 글의 정보를

받아오는 정도만 구현했다.

 

components/CommentBox

 

댓글을 작성하고 제출 시, 알림 버튼을 누르면 댓글이 작성되는 기능을 추가하였다.

댓글의 남은 글자 수도 하단에 위치해 두었다.

 

 

// Comment 출력시 사용되는 컴포넌트
function CommentBox({ commentList }) {
	const [isWrite, setIsWrite] = useState(false);
	const [comments, setComments] = useState([]);
	const [newComment, setNewComment] = useState('');

	const commentHandler = (event) => {
		setNewComment(event.target.value);
	};

	const isWriteHandler = () => {
		setIsWrite(!isWrite);
	};

	const postComment = () => {
		console.log('comment보내기');
	};

	// 새로운 커멘트 작성
	const postCommentHandler = (e) => {
		e.preventDefault();
		if (window.confirm('댓글을 작성하겠습니까?')) {
			postComment();
			isWriteHandler();
			setNewComment('');
			setComments([...comments, newComment]);
			alert('작성되었습니다.');
		} else {
			alert('취소합니다.');
		}
	};

	return (
		<CommentContainer>
			{comments.map((it, idx) => (
				<div key={1}>
					<p>
						{it} –
						<Link to="/myprofile">
							<span className="user__name">ashughes Jun</span>
						</Link>
						<span className="created__time">{String(new Date())}</span>
					</p>
					<DividerLine />
				</div>
			))}

			<NewCommentContainer>
				{isWrite ? (
					<form className="comment__form">
						<div className="left__container">
							<textarea onChange={commentHandler} maxLength="500" />
							<span>{`${Number(
								500 - newComment.length,
							)} characters left`}</span>
						</div>
						<button
							type="submit"
							onClick={(e) => {
								postCommentHandler(e);
							}}
						>
							Add comment
						</button>
					</form>
				) : (
					<span
						className="create__comment"
						onClick={isWriteHandler}
						aria-hidden="true"
					>
						Add a comment
					</span>
				)}
			</NewCommentContainer>
		</CommentContainer>
	);
}

export default CommentBox;

 

 

components/IconMenu

 

기존 구현한 UI에 관련 페이지들로 이동할 수 있는 기능과, 투표, 책갈피, 추천 기능을 활성화 시켰다.

 

import styled from 'styled-components';
import { Link } from 'react-router-dom';
import { useState } from 'react';
import {
	AiFillCaretUp,
	AiFillCaretDown,
	AiOutlineFieldTime,
} from 'react-icons/ai';
import { MdBookmarkBorder, MdBookmark } from 'react-icons/md';
import { FaCheck } from 'react-icons/fa';

const IconMenuContainer = styled.ul`
	width: 52px;
	padding: 0px;
	padding-right: 16px;

	li {
		position: relative;
		list-style: none;
		font-size: 17px;
		text-align: center;

		color: var(--font-color-gray);
		> svg {
			font-size: var(--font-base);
			width: 36px;
			height: 36px;
		}
	}
	li:hover {
		> div {
			visibility: visible;
		}
	}
	li:active {
		color: orange;
	}
	.bookmark__icon {
		width: 18px;
		height: 18px;
	}

	list-style: none;
`;

const SelectedIcon = styled(FaCheck)`
	color: ${(props) => (props.isSelected ? 'green' : `var(--font-color-gray);`)};
`;

const Tooltip = styled.div`
	display: flex;
	text-align: start;
	visibility: hidden;
	position: relative;
	background: white;
	border: 1px solid var(--font-color-gray);
	width: 180px;

	font-size: var(--font-base);
	color: black;
	border-radius: 5px;
	padding: 12px;

	position: absolute;
	z-index: 1;

	top: 50%;
	left: 105%;
	transform: translateY(-50%);
	p {
		flex-basis: 1;
	}
	&:after,
	&:before {
		right: 100%;
		top: 50%;
		border: solid transparent;
		content: '';
		height: 0;
		width: 0;
		position: absolute;
	}

	&:after {
		border-right-color: white;
		border-width: 5px;
		margin-top: -5px;
	}
	//테두리 설정
	&:before {
		border-right-color: var(--font-color-gray);
		border-width: 6px;
		margin-top: -6px;
	}
`;

// QnABox의 IconMenu
function IconMenu() {
	const [vote, setVote] = useState(0);
	const [isBookmark, setIsBookmark] = useState(false);
	const [isSelected, setIsSelected] = useState(false);

	const postVote = () => {
		console.log('서버전송');
	};

	const postBookmark = () => {
		console.log('서버 전송');
	};

	const postSelectAnswer = () => {
		console.log('서버 전송');
	};

	const voteUpHandler = () => {
		setVote(vote + 1);
		postVote();
	};

	const voteDownHandler = () => {
		setVote(vote - 1);
		postVote();
	};

	const isBookmarkHandler = () => {
		setIsBookmark(!isBookmark);
		postBookmark();
	};

	const isSelectedHadler = () => {
		setIsSelected(!isSelected);
		postSelectAnswer();
	};

	return (
		<IconMenuContainer>
			<li>
				<AiFillCaretUp onClick={voteUpHandler} />
				<Tooltip>
					This question shows research effort; it is useful and clear
				</Tooltip>
			</li>
			<li>{vote}</li>
			<li>
				<AiFillCaretDown onClick={voteDownHandler} />
				<Tooltip>Save this question.</Tooltip>
			</li>
			<li>
				<SelectedIcon isSelected={isSelected} onClick={isSelectedHadler} />
			</li>
			<li>
				{isBookmark ? (
					<MdBookmarkBorder
						className="bookmark__icon on"
						onClick={isBookmarkHandler}
					/>
				) : (
					<MdBookmark className="bookmark__icon" onClick={isBookmarkHandler} />
				)}

				<Tooltip>Save this question.</Tooltip>
			</li>
			<li>
				<Link to="/timeline/:id">
					<AiOutlineFieldTime className="timeline__icon" />
				</Link>
				<Tooltip>Show activity on this post.</Tooltip>
			</li>
		</IconMenuContainer>
	);
}

export default IconMenu;

 

/page/Question

질문 상세 페이지는, 아고라 스테이츠 구현당시의 서버데이터를 활용해서 정보를 받아왔다.

에디터로 글을 작성할 경우 해당 정보를 HTML로 받아와야하기 때문에, 미리 연습해보았다.

 

참고로 React 에서는 HTML 정보를 넣을 때, 아래와 같은 형식으로 받아와야한다.

<div className="text__container" dangerouslySetInnerHTML={{ __html: questionData.bodyHTML }}/>

 

 

 

 

import styled from 'styled-components';
import { Link } from 'react-router-dom';
import { useEffect, useState } from 'react';
import axios from 'axios';

import LoginHeader from '../components/Header/LoginHeader';
import QnABox from '../components/QnABox';

const QuestionContainer = styled.div`
	display: flex;
	flex-direction: column;
	position: absolute;
	top: 50px;
	/* border: black solid 1px; */

	align-items: center;
	width: 100vw;

	main {
		padding: 24px;
		display: flex;
		flex-direction: column;
		/* border: black solid 1px; */
		flex: 1;
		width: 80vw;
	}
`;

const TitleContainer = styled.div`
	display: flex;
	flex-direction: column;

	.top__container {
		display: flex;
		justify-content: space-between;
		align-items: center;
		margin-bottom: 10px;

		span {
			font-size: var(--font-title-large);
		}
		button {
			background-color: var(--main-color);
			font-size: var(--font-base);
			color: white;
			width: 100px;
			height: 45px;
			border: none;
		}
	}

	.bottom__container {
		display: flex;
		align-items: center;
	}

	.info__container {
		display: flex;
	}
`;

const InfoBox = styled.div`
	margin-right: 10px;
	font-size: var(--font-base);
	span {
		margin-right: 5px;
		color: var(--font-color-gray);
	}

	.second__span {
		color: black;
	}
`;

const AnswerForm = styled.form`
	display: flex;
	flex-direction: column;

	> span {
		margin: 20px 0px;
		font-size: var(--font-title-small);
	}
	> textarea {
		height: 100px;
	}
	button {
		margin: 10px 0px;
		width: 130px;
		height: 40px;
		background-color: var(--main-color);
		color: white;
		border: none;
	}
`;

const AnswersContainer = styled.div`
	margin-top: 20px;
	.answers__header {
		display: flex;
		justify-content: space-between;
		margin-bottom: 8px;
		> span {
			font-size: var(--font-title-small);
		}
	}
	.filter__container {
		font-size: var(--font-base);
		display: flex;
		align-items: center;
	}
`;

function Question() {
	const [questionData, setQuestionData] = useState({});
	const [answerList, setAnswerList] = useState([]);

	const getQuestionData = () => {
		axios.get('http://localhost:3001/datas').then((res) => {
			setQuestionData(res.data[0]);
			setAnswerList(res.data[0].answers);
		});
	};

	useEffect(() => {
		getQuestionData();
	}, []);

	console.log(answerList);

	return (
		<QuestionContainer>
			<LoginHeader />

			<main>
				<TitleContainer>
					<div className="top__container">
						<span>{questionData && questionData.title}</span>

						<Link to="/myprofile">
							<button type="button">Ask Questions</button>
						</Link>
					</div>
					<div className="bottom__container">
						<div className="info__container">
							<InfoBox>
								<span className="first__span">Asked</span>
								<span className="second__span">{questionData.createdAt}</span>
							</InfoBox>
							<InfoBox>
								<span className="first__span">Modified</span>
								<span className="second__span">{questionData.createdAt}</span>
							</InfoBox>
							<InfoBox>
								<span className="first__span">Viewed</span>
								<span className="second__span">8times</span>
							</InfoBox>
						</div>
					</div>
				</TitleContainer>

				<QnABox questionData={questionData} setQuestionData={setQuestionData} />

				<AnswersContainer>
					<div className="answers__header">
						<span>{`${answerList.length} Answers`}</span>
						<div className="filter__container">
							<span>Sorted by:</span>
							<select>
								<option>Date modified (newest first)</option>
								<option>Date created (oldest first)</option>
							</select>
						</div>
					</div>
					<div>
						{answerList.map((it) => (
							<QnABox
								key={it.id}
								questionData={it}
								setQuestionData={setQuestionData}
							/>
						))}
					</div>
				</AnswersContainer>

				<AnswerForm>
					<span>Your Answer</span>
					<textarea />
					<div className="bottom__container">
						<button type="submit">Post Your Answer</button>
					</div>
				</AnswerForm>
			</main>
		</QuestionContainer>
	);
}

export default Question;

 

 

코드가 좀 지저분해서, 깔끔하게 정리를 하면서 추가 기능들을 구현해가야겠다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함