Giter Site home page Giter Site logo

sg_blog's Introduction

Blog

📒 개요

스마일게이트 데브 캠프 1인 프로젝트로 진행한 블로그 개발 프로젝트입니다. 클라이언트부터 서버까지 구현하였으며, velog 사이트의 UI를 참고하였습니다.

📗 기간

구현 : 2022.12.05. ~ 2022.12.25.

refactor : 2022.12.26. ~ 2023. 01. 14.


📙 Feature

✔ 글 쓰기/ 수정

✔ 글 목록/삭제

✔ 댓글 (대댓글은 추후 구현 예정)

✔관리자 도구

✔ UI디자인

✔ 다크모드


📔 화면

홈화면

관리자도구

글 작성화면

글 보여주는 화면

Figma 프로토타입


📘 Tech stack

front

  • React  v.18

back

  • node.js   v16.18.1
  • MySQL   v5.7

개발도구

  • VScode

💻 실행방법

  post-server , image-server 디렉토리 명령어 npm start
  client 디렉토리 명령어 npm run start:dev
  제 컴퓨터에서 Nginx 설정을 해서 실행하실 때 client 폴더에서 80번 포트로의 요청을 3000번 포트로 변경해주셔야 합니다.

⚙ 서버 포트 번호

  • 리액트 : 8080
  • nginx(프록시 서버) :80
  • 글, 댓글 서버 : 3000
  • 이미지 서버 : 4000

⭐ 개발 과정에서 궁금했던 부분

   1.  현재 컴포넌트마다 각각 api 호출해서 하고 있는데 api 호출하는 부분을 recoil이나 redux로 전역상태로 따로 빼서 관리하는 것이 좋은지 궁금합니다.(코드리뷰를 통해 따로 커스텀 훅으로 변경함)
      
   2. axios.get 으로 받아온 json array를 Obect.keys().map()으로 처리했는데 json array에서 배열만 빼서 map메소드로만 처리하는 방법이 궁금합니다. (답변 해주셔서 해결 완료)

sg_blog's People

Watchers

 avatar

Forkers

jhseo9955

sg_blog's Issues

조회수 기능 추가하기

블로그 글을 통해서 어떻게 할지 알아냈다.

서버에서 클라이언트 ip주소 받아온 후에

배열에다가 ip주소 넣고 setTimeout으로 일정시간 후에는 배열에서 ip주소를 제거하는데 이때 조회수 증가시키면 된다.

[bug] PostList 페이지에서 삭제하기 버튼 눌렀을 때 confirm 창이 두번 뜨는 이슈

삭제하기 버튼을 누르면 confirm 창이 뜨고 '예' 를 누르면 confirm 창이 다시 뜨고 '예'를 누르면 삭제됨

--rendering issue

` useEffect(() => {

const baseUrl = "http://localhost:3000";
const deletePosts = async () => {
  await axios
    .delete(baseUrl + "/posts/delete", {
      params: {
        unq: unq,
      },
    })
    .then((res) => {
      console.log(res);
      navigate("/");
    })
    .catch((err) => {
      console.log(err);
    });
};
const deleteCancel = () => {
  alert("삭제취소");
  navigate("/");
};
confirm("정말 삭제하시겠습니까?") ? deletePosts() : deleteCancel();

}, []);
return

; `

이렇게 적어서 confirm 이 두 번 떴음

[bug] 서버사이드 설정 에러

프론트에서 Link 를 클릭해서 들어가면 페이지 접속이 되지만

url을 직접 입력하면 "CANNOT GET /" 이라는 ERROR가 뜸.

delete 메소드 실행되면 바로 컴포넌트 렌더링시키는 것이 안됨

댓글 삭제가 이루어지면 컴포넌트가 렌더링되어서 바로 삭제된 결과를 보여주고 싶었으나
deleteReply 함수의 try 문 안에 getReply함수를 호출해도 (+ finally 만들어서 finally에다가 getReply 함수 호출해도) 렌더링이 일어나지 않음 getReply(unq)로 해도 되지 않았음 ㅠㅠ

일단은 getReply함수

const getReply = async () => {
    try {
      const res = await axios.get(baseUrl + `/reply/get/${unq}`);
      const copy = [...reply];
      const fetchedReply = copy.concat(res.data);
      setReply(fetchedReply);
    } catch {
      (error) => console.log(error);
    }
  };

그리고 deleteReply 함수

 const deleteReply = async (reply_id) => {
    try {
      await axios.delete(baseUrl + `/reply/delete/${reply_id}`);
      alert("댓글이 삭제되었습니다");
      navigate(-1);
    } catch {
      (error) => console.log(error);
    }
  };

...
 return (
    <div>
      {Object.keys(reply).map((e, i) => {
        const reply_id = reply[i].reply_no;
        const reply_password = reply[i].password;
        return (
          <div key={reply_id}>
            <span className="mr-60">{reply[i].nickname}</span>
            <span>{reply[i].created_at}</span>
            <p>{reply[i].comment}</p>
            <button
              className="reply_cancel"
              onClick={() =>
                passwordCheck(reply_password)
                  ? deleteReply(reply_id)
                  : alert("잘못된 비밀번호입니다")
              }
            >
              삭제
            </button>
            <button
              onClick={() => {
                showInput();
              }}
              className="re_reply-btn_add"
            >
              댓글
            </button>

전체코드

import React, { useEffect, useState } from "react";
import axios from "axios";
import styled from "styled-components";
import ReplyToReply from "./ReplyToReply";
import { useNavigate } from "react-router-dom";
const ReplyAddBtn = styled.button`
  padding: 5px 30px;
  border: 1px solid #eeeeee;
  border-radius: 40px;
  color: white;
  background-color: black;
`;
function Comment({ unq }) {

  useEffect(() => {
    getReply();
  }, []);
  const [inputs, setInputs] = useState({
    unq: unq,
    nickname: "",
    password: "",
    content: "",
  });
  const [reply, setReply] = useState("");
  const [showReplyInput, setShowReplyInput] = useState("");

  const sendReply = async () => {
    try {
      await axios.post(`${process.env.REACT_APP_API_URL}/reply/write`, inputs);
      alert("댓글이 저장되었습니다");
    } catch {
      (error) => {
        console.log(error);
      };
    }
    getReply();
  };
  const getReply = async () => {
    try {
      const res = await axios.get(baseUrl + `/reply/get/${unq}`);
      const copy = [...reply];
      const fetchedReply = copy.concat(res.data);
      setReply(fetchedReply);
    } catch {
      (error) => console.log(error);
    }
  };
  const navigate = useNavigate(-1);
  const deleteReply = async (reply_id) => {
    try {
      await axios.delete(baseUrl + `/reply/delete/${reply_id}`);
      alert("댓글이 삭제되었습니다");
      navigate(-1);
    } catch {
      (error) => console.log(error);
    }
  };
  const passwordCheck = (x) => {
    const password_input = prompt("비밀번호 입력", "");
    return password_input == x ? true : false;
  };

  function showInput() {
    setShowReplyInput(!showReplyInput);
  }
  const handleInput = (e) => {
    setInputs({ ...inputs, [e.target.name]: e.target.value });
  };
  return (
    <div>
      {Object.keys(reply).map((e, i) => {
        const reply_id = reply[i].reply_no;
        const reply_password = reply[i].password;
        return (
          <div key={reply_id}>
            <span className="mr-60">{reply[i].nickname}</span>
            <span>{reply[i].created_at}</span>
            <p>{reply[i].comment}</p>
            <button
              className="reply_cancel"
              onClick={() =>
                passwordCheck(reply_password)
                  ? deleteReply(reply_id)
                  : alert("잘못된 비밀번호입니다")
              }
            >
              삭제
            </button>
            <button
              onClick={() => {
                showInput();
              }}
              className="re_reply-btn_add"
            >
              댓글
            </button>
            <hr style={{ marginTop: "5px" }} />
            <ReplyToReply showReplyInput={showReplyInput} />
          </div>
        );
      })}
      <div>
        <div>
          <input
            style={{ width: "277.8px" }}
            name="nickname"
            placeholder="닉네임"
            type="text"
            value={inputs.nickname}
            onChange={handleInput}
          />
          <input
            style={{ width: "277.8px" }}
            name="password"
            placeholder="비밀번호"
            type="password"
            value={inputs.password}
            onChange={handleInput}
          />
        </div>
        <textarea
          cols="70"
          rows="6"
          name="content"
          style={{
            resize: "none",
            padding: "8px",
            maxHeight: "90px",
          }}
          value={inputs.content}
          onChange={handleInput}
        />
        <ReplyAddBtn
          onClick={() =>
            inputs.password ? sendReply() : alert("비밀번호를 입력해주세요")
          }
        >
          등록
        </ReplyAddBtn>
      </div>
    </div>
  );
}

export default Comment;

댓글 작성, 삭제 시 렌더링 되어야함

Describe the bug
A clear and concise description of what the bug is.

To Reproduce
Steps to reproduce the behavior:

  1. Go to '...'
  2. Click on '....'
  3. Scroll down to '....'
  4. See error

Expected behavior
A clear and concise description of what you expected to happen.

Screenshots
If applicable, add screenshots to help explain your problem.

Desktop (please complete the following information):

  • OS: [e.g. iOS]
  • Browser [e.g. chrome, safari]
  • Version [e.g. 22]

Smartphone (please complete the following information):

  • Device: [e.g. iPhone6]
  • OS: [e.g. iOS8.1]
  • Browser [e.g. stock browser, safari]
  • Version [e.g. 22]

Additional context
Add any other context about the problem here.

react-quill 에디터에서 볼드체 또는 이탤릭체로 글 쓰면 DB에서 가져온 데이터도 그렇게 보여야함

 <ReactQuill
        theme="snow"
        modules={modules}
        value={value || ""}
        onChange={(content, delta, source, editor) =>
          onChange(editor.getText())
        }
        className="height-500"
        placeholder="내용을 입력하세요..."
      />

위에서 getText()로 해서 그런 것같은데 getHtml()로 한 후에 DB에 저장하고 그 후에 어떻게 처리할지 생각중

 const handleContent = (value) => {
    content.current = value;
    console.log(content.current);
  };
  return (
    <div className="ml-40 mr-40">
      <div className="pt-5rem pb-5rem ">
        <input
          className="title_input"
          placeholder=" 제목을 입력하세요"
          value={title}
          onChange={(e) => {
            setTitle(e.target.value);
          }}
        />
        <Editor value={content} onChange={handleContent} />
      </div>

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.