ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,98 @@
+import { Component } from "react";
+import { Link, withRouter } from "react-router-dom";
+import "./Login.scss";
+
+class Login extends Component {
+  constructor() {
+    super();
+    this.state = {
+      id: "",
+      pw: "",
+    };
+  }
+
+  // localStorage.clear();
+  // localStorage.removeItem('key');
+  // localStorage.getItem('key');
+
+  //저장
+
+  //조회
+  // let getValue = localStorage.getItem('Token');
+  // console.log(getValue);
+  // localStorage
+
+  goToMain = () => {
+    fetch("http://10.58.2.229:8000/users/login", {
+      method: "POST",
+      body: JSON.stringify({
+        account: this.state.id,
+        password: this.state.pw,
+        // phone_number: "01034358181",
+        // name: "김도히",
+      }),
+    })
+      .then((res) => res.json())
+      .then((result) => {
+        console.log(result);
+        if (result.MESSAGE === "SUCCESS") {
+          localStorage.setItem("Token", result.Token);
+          alert("로그인성공!");
+          this.props.history.push("/maindh");
+        } else {
+          alert("🤬IT'S YOUR FAULT!🤬");
+        }
+      });
+  };
+
+  handleInput = (event) => {
+    const { name, value } = event.target;
+    this.setState({
+      [name]: value,
+    });
+  };
+
+  render() {
+    const { id, pw } = this.state;
+    return (
+      <>
+        <div className="login-wrap">
+          <h1 className="title">Westagram</h1>
+          <div className="inner-wrap">
+            <input
+              className="id"
+              type="text"
+              placeholder="전화번호, 사용자 이름 또는 이메일"
+              onChange={this.handleInput}
+              value={id}
+              name="id"
+            />
+            <input
+              className="password"
+              type="password"
+              placeholder="비밀번호"
+              onChange={this.handleInput}
+              value={pw}
+              name="pw"
+            />
+            <button
+              className={id.includes("@") && pw.length >= 5 ? "on" : "off"}
+              onClick={this.goToMain}
+            >
+              로그인
+            </button>
+            <p>
+              <Link to="/Main">비밀번호를 잊으셨나요?</Link>
+            </p>
+          </div>
+        </div>
+      </>
+    );
+  }
+}
+
+export default withRouter(Login);
+
+//객체 구조 분해 할당
+
+//const { fontState} = this.state

리뷰: 넵! 리팩토링 해보겠습니다😄
@@ -0,0 +1,63 @@
+import React from "react";
+import Story from "./Mainleft/Story";
+import Feed from "./Mainleft/Feed";
+import "./MainLeft.scss";
+// import { FaRedditAlien } from "react-icons/fa";
+
+class MainLeft extends React.Component {
+  state = {
+    feedList: [],
+  };
+
+  componentDidMount() {
+    fetch("/data/FeedData.json", {
+      method: "GET",
+    })
+      .then((res) => res.json())
+      .then((data) => {
+        this.setState({
+          feedList: data,
+        });
+      });
+  }
+
+  aaa = () => {
+    // console.log("aaa 함수호출");
+    fetch("http://10.58.2.229:8000/posts/post", {
+      method: "GET",
+      headers: {
+        Authorization: localStorage.getItem("Token"),
+      },
+    })
+      .then((res) => res.json())
+      .then((res) => {
+        this.setState({
+          feedList: res.RESULT,
+        });
+        console.log(res.RESULT);
+      });
+  };
+
+  render() {
+    const { feedList } = this.state;
+    return (
+      <div className="main-left">
+        {/* <button onClick={this.aaa}>click</button> */}
+        <Story />
+        {feedList.map((element) => {
+          return (
+            <Feed
+              id={element.id}
+              name={element.userId}
+              count={element.count}
+              img={element.img}
+              title={element.title}
+            />
+          );
+        })}
+      </div>
+    );
+  }
+}
+
+export default MainLeft;

리뷰: 태그명이 한눈에 들어와요👍
@@ -0,0 +1,50 @@
+@import "../../common.scss";
+
+.recommend-header {
+    width:100%;
+    height:100px;
+    @extend %flexbetween;
+    margin-top:5px;
+
+    li {
+        position:relative;
+        &:first-child {
+            &:after {
+                width:64px;
+                height:64px;
+                border:1px solid #ddd;
+                border-radius: 50%;
+                content:"";
+                position:absolute;
+                top:-3px; left:-3px;
+            }
+        }
+
+        .recommend-header-flex {
+            display:flex;
+            justify-content: flex-start;
+            align-items: center;
+
+            li {
+                
+                img {
+                    width:100%;
+                    width:60px;
+                    height:60px;
+                    border-radius:50%;
+                    margin-bottom:5px;
+                }
+
+                .recommend-header-id {
+                    font-weight:700;
+                    margin-left:10px;
+                }
+            }
+        }
+    }
+
+    .log-out {
+        float:right;
+    }
+}
+

리뷰: tirm 함수? 이용해서 빈칸 enter방지하는 방법이있더라구요 같이 사용해봐요🖐
@@ -0,0 +1,88 @@
+@import "../../../../styles/common.scss";
+
+
+%flexbetween {
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+}
+
+nav {
+    width:100%;
+    height:55px;
+    background-color:#fff;
+    border-bottom :1px solid #ddd;
+    position:fixed;
+    top:0;
+    z-index:10;
+
+    ul{
+        margin:auto;
+        height:inherit;
+        width:60%;
+        display:flex;
+        justify-content:space-between;
+        align-items: center;
+
+        li{
+            .logo {
+                font-family: 'Lobster', cursive;
+                font-size:25px;
+                margin-top:-5px;
+            }
+        }
+
+        .search-wrap {
+                position:relative;
+
+            input {
+                width:200px;
+                height:30px;
+                border:1px solid #ddd;
+                background-color:#fafafa;
+                outline:none;
+                border-radius: 5px;
+                &::placeholder {
+                    text-align:center;
+                }
+            }
+
+            svg {
+                color:#888;
+                font-size:12px;
+                position:absolute;
+                left:60px; top:9px;
+            }
+        }
+    }
+}
+
+.icon {
+    @extend %flexbetween;
+    margin-left:-30px;
+
+    li {
+        position:relative;
+        margin-left:15px;
+        &:nth-child(2):after {
+            width:4px;
+            height:4px;
+            border-radius:50%;
+            content:"";
+            background-color:red;
+            position:absolute;
+            top:30px;
+            right:10px;
+        }
+        &:last-child {
+            img {
+                border-radius: 50%;
+            }
+        }
+            img {
+                border-radius: 50%;
+                width:25px;
+                height:25px;
+            }
+    }
+}
\ No newline at end of file

리뷰: placeholder 스타일 값주는 법 저도 써봐야겠어요!
@@ -0,0 +1,46 @@
+footer {
+    width:100%;
+    height:70px;
+
+    ul {
+        width:90%;
+        margin-top:20px;
+
+        li {
+            font-size:12px;
+            color:#888;
+            margin-bottom:10px;
+            position:relative;
+            padding-left:5px;
+            padding-right:10px;
+            display:inline-block;
+
+            &::after {
+                width:3px;
+                height:3px;
+                border-radius: 50%;
+                position:absolute;
+                top:25%; left:95%;
+                content:"";
+                background-color:#888;
+            }
+
+            &:nth-child(6) {
+                &::after {
+                    display:none;
+                }
+            }
+            
+            &:last-child {
+                &::after {
+                    display:none;
+                }
+            }
+        }
+    }
+
+    .made {
+        font-size:12px;
+        color:#888;
+    }
+}
\ No newline at end of file

리뷰: css선택자와 가상요소선택자를 잘 이용하셨네요..! 정확한 이용법을 몰랐는데 배워갑니다 🔥👍🏻
@@ -0,0 +1,50 @@
+@import "../../common.scss";
+
+.recommend-header {
+    width:100%;
+    height:100px;
+    @extend %flexbetween;
+    margin-top:5px;
+
+    li {
+        position:relative;
+        &:first-child {
+            &:after {
+                width:64px;
+                height:64px;
+                border:1px solid #ddd;
+                border-radius: 50%;
+                content:"";
+                position:absolute;
+                top:-3px; left:-3px;
+            }
+        }
+
+        .recommend-header-flex {
+            display:flex;
+            justify-content: flex-start;
+            align-items: center;
+
+            li {
+                
+                img {
+                    width:100%;
+                    width:60px;
+                    height:60px;
+                    border-radius:50%;
+                    margin-bottom:5px;
+                }
+
+                .recommend-header-id {
+                    font-weight:700;
+                    margin-left:10px;
+                }
+            }
+        }
+    }
+
+    .log-out {
+        float:right;
+    }
+}
+

리뷰: after활용 👍🏻
@@ -0,0 +1,98 @@
+import { Component } from "react";
+import { Link, withRouter } from "react-router-dom";
+import "./Login.scss";
+
+class Login extends Component {
+  constructor() {
+    super();
+    this.state = {
+      id: "",
+      pw: "",
+    };
+  }
+
+  // localStorage.clear();
+  // localStorage.removeItem('key');
+  // localStorage.getItem('key');
+
+  //저장
+
+  //조회
+  // let getValue = localStorage.getItem('Token');
+  // console.log(getValue);
+  // localStorage
+
+  goToMain = () => {
+    fetch("http://10.58.2.229:8000/users/login", {
+      method: "POST",
+      body: JSON.stringify({
+        account: this.state.id,
+        password: this.state.pw,
+        // phone_number: "01034358181",
+        // name: "김도히",
+      }),
+    })
+      .then((res) => res.json())
+      .then((result) => {
+        console.log(result);
+        if (result.MESSAGE === "SUCCESS") {
+          localStorage.setItem("Token", result.Token);
+          alert("로그인성공!");
+          this.props.history.push("/maindh");
+        } else {
+          alert("🤬IT'S YOUR FAULT!🤬");
+        }
+      });
+  };
+
+  handleInput = (event) => {
+    const { name, value } = event.target;
+    this.setState({
+      [name]: value,
+    });
+  };
+
+  render() {
+    const { id, pw } = this.state;
+    return (
+      <>
+        <div className="login-wrap">
+          <h1 className="title">Westagram</h1>
+          <div className="inner-wrap">
+            <input
+              className="id"
+              type="text"
+              placeholder="전화번호, 사용자 이름 또는 이메일"
+              onChange={this.handleInput}
+              value={id}
+              name="id"
+            />
+            <input
+              className="password"
+              type="password"
+              placeholder="비밀번호"
+              onChange={this.handleInput}
+              value={pw}
+              name="pw"
+            />
+            <button
+              className={id.includes("@") && pw.length >= 5 ? "on" : "off"}
+              onClick={this.goToMain}
+            >
+              로그인
+            </button>
+            <p>
+              <Link to="/Main">비밀번호를 잊으셨나요?</Link>
+            </p>
+          </div>
+        </div>
+      </>
+    );
+  }
+}
+
+export default withRouter(Login);
+
+//객체 구조 분해 할당
+
+//const { fontState} = this.state

리뷰: 하나의 함수로 합쳐주실수 있겠네요-! 👐
@@ -0,0 +1,79 @@
+@import url('https://fonts.googleapis.com/css2?family=Lobster&display=swap');
+
+* {
+    box-sizing: border-box;
+}
+body{
+    background-color:#fafafa;
+}
+
+.login-wrap{
+    width:30%;
+    height:550px;
+    background-color:#fff;
+    border:3px solid rgba(221, 221, 221, 0.3);
+    margin:130px auto 0 auto;
+
+    h1{
+        width:80%;
+        height:50px;
+        font-size:55px;
+        font-family: 'Lobster', cursive;
+        padding-top:50px;
+        margin:0 auto 120px auto;
+        text-align: center;
+    }
+
+    .inner-wrap {
+        width:80%;
+        margin:auto;
+        height:300px;
+
+        input {
+            width:100%;
+            height:60px;
+            border-radius: 5px;
+            border:2px solid rgba(221, 221, 221, 0.2);
+            margin-bottom:10px;
+            padding:30px 10px;
+            font-size:18px;
+            color:#000;
+            cursor: pointer;
+            outline:none;
+            line-height: 30px;
+        }
+
+        .on {
+            background-color:#0094f6;
+        }
+
+        .off {
+            background-color:#0094f64b;
+        }
+
+        button {
+            width:100%;
+            height:50px;
+            margin-top:15px;
+            border:none;
+            background-color:#0094f64b;
+            border-radius: 8px;
+            color:#fff;
+            font-size:20px;
+            font-weight:600;
+            outline:none;
+            cursor:pointer;
+        }
+
+        p{
+            text-align:center;
+            margin-top:110px;
+
+            a{
+                color:#00376b;
+                font-size:16px;
+                text-decoration: none;
+            }
+        }
+    }
+}

리뷰: 한줄 margin 속성으로 줄여볼 수 있겠네요-! 👐
@@ -0,0 +1,98 @@
+import { Component } from "react";
+import { Link, withRouter } from "react-router-dom";
+import "./Login.scss";
+
+class Login extends Component {
+  constructor() {
+    super();
+    this.state = {
+      id: "",
+      pw: "",
+    };
+  }
+
+  // localStorage.clear();
+  // localStorage.removeItem('key');
+  // localStorage.getItem('key');
+
+  //저장
+
+  //조회
+  // let getValue = localStorage.getItem('Token');
+  // console.log(getValue);
+  // localStorage
+
+  goToMain = () => {
+    fetch("http://10.58.2.229:8000/users/login", {
+      method: "POST",
+      body: JSON.stringify({
+        account: this.state.id,
+        password: this.state.pw,
+        // phone_number: "01034358181",
+        // name: "김도히",
+      }),
+    })
+      .then((res) => res.json())
+      .then((result) => {
+        console.log(result);
+        if (result.MESSAGE === "SUCCESS") {
+          localStorage.setItem("Token", result.Token);
+          alert("로그인성공!");
+          this.props.history.push("/maindh");
+        } else {
+          alert("🤬IT'S YOUR FAULT!🤬");
+        }
+      });
+  };
+
+  handleInput = (event) => {
+    const { name, value } = event.target;
+    this.setState({
+      [name]: value,
+    });
+  };
+
+  render() {
+    const { id, pw } = this.state;
+    return (
+      <>
+        <div className="login-wrap">
+          <h1 className="title">Westagram</h1>
+          <div className="inner-wrap">
+            <input
+              className="id"
+              type="text"
+              placeholder="전화번호, 사용자 이름 또는 이메일"
+              onChange={this.handleInput}
+              value={id}
+              name="id"
+            />
+            <input
+              className="password"
+              type="password"
+              placeholder="비밀번호"
+              onChange={this.handleInput}
+              value={pw}
+              name="pw"
+            />
+            <button
+              className={id.includes("@") && pw.length >= 5 ? "on" : "off"}
+              onClick={this.goToMain}
+            >
+              로그인
+            </button>
+            <p>
+              <Link to="/Main">비밀번호를 잊으셨나요?</Link>
+            </p>
+          </div>
+        </div>
+      </>
+    );
+  }
+}
+
+export default withRouter(Login);
+
+//객체 구조 분해 할당
+
+//const { fontState} = this.state

리뷰: 따로 함수를 만들어주시지 않아도 될 것 같습니다-!
```js
const { id, pw } = this.state;
<button className={id.includes('@') && pw.length >= 5 ? 'on' : 'off'} onClick={this.goToMain}>
```
@@ -0,0 +1,79 @@
+@import url('https://fonts.googleapis.com/css2?family=Lobster&display=swap');
+
+* {
+    box-sizing: border-box;
+}
+body{
+    background-color:#fafafa;
+}
+
+.login-wrap{
+    width:30%;
+    height:550px;
+    background-color:#fff;
+    border:3px solid rgba(221, 221, 221, 0.3);
+    margin:130px auto 0 auto;
+
+    h1{
+        width:80%;
+        height:50px;
+        font-size:55px;
+        font-family: 'Lobster', cursive;
+        padding-top:50px;
+        margin:0 auto 120px auto;
+        text-align: center;
+    }
+
+    .inner-wrap {
+        width:80%;
+        margin:auto;
+        height:300px;
+
+        input {
+            width:100%;
+            height:60px;
+            border-radius: 5px;
+            border:2px solid rgba(221, 221, 221, 0.2);
+            margin-bottom:10px;
+            padding:30px 10px;
+            font-size:18px;
+            color:#000;
+            cursor: pointer;
+            outline:none;
+            line-height: 30px;
+        }
+
+        .on {
+            background-color:#0094f6;
+        }
+
+        .off {
+            background-color:#0094f64b;
+        }
+
+        button {
+            width:100%;
+            height:50px;
+            margin-top:15px;
+            border:none;
+            background-color:#0094f64b;
+            border-radius: 8px;
+            color:#fff;
+            font-size:20px;
+            font-weight:600;
+            outline:none;
+            cursor:pointer;
+        }
+
+        p{
+            text-align:center;
+            margin-top:110px;
+
+            a{
+                color:#00376b;
+                font-size:16px;
+                text-decoration: none;
+            }
+        }
+    }
+}

리뷰: 자주 사용되는 스타일 같은데, scss 변수로 선언해서 사용해주시면 좋을 듯 하네요-! 👐
@@ -0,0 +1,63 @@
+import React from "react";
+import Story from "./Mainleft/Story";
+import Feed from "./Mainleft/Feed";
+import "./MainLeft.scss";
+// import { FaRedditAlien } from "react-icons/fa";
+
+class MainLeft extends React.Component {
+  state = {
+    feedList: [],
+  };
+
+  componentDidMount() {
+    fetch("/data/FeedData.json", {
+      method: "GET",
+    })
+      .then((res) => res.json())
+      .then((data) => {
+        this.setState({
+          feedList: data,
+        });
+      });
+  }
+
+  aaa = () => {
+    // console.log("aaa 함수호출");
+    fetch("http://10.58.2.229:8000/posts/post", {
+      method: "GET",
+      headers: {
+        Authorization: localStorage.getItem("Token"),
+      },
+    })
+      .then((res) => res.json())
+      .then((res) => {
+        this.setState({
+          feedList: res.RESULT,
+        });
+        console.log(res.RESULT);
+      });
+  };
+
+  render() {
+    const { feedList } = this.state;
+    return (
+      <div className="main-left">
+        {/* <button onClick={this.aaa}>click</button> */}
+        <Story />
+        {feedList.map((element) => {
+          return (
+            <Feed
+              id={element.id}
+              name={element.userId}
+              count={element.count}
+              img={element.img}
+              title={element.title}
+            />
+          );
+        })}
+      </div>
+    );
+  }
+}
+
+export default MainLeft;

리뷰: import 순서 수정해주세요 👐 일반적인 convention을 따르는 이유도 있지만 순서만 잘 지켜주셔도 가독성이 좋아집니다. 아래 순서 참고해주세요.
- React → Library(Package) → Component → 변수 / 이미지 → css 파일(scss 파일)
@@ -0,0 +1,63 @@
+import React from "react";
+import Story from "./Mainleft/Story";
+import Feed from "./Mainleft/Feed";
+import "./MainLeft.scss";
+// import { FaRedditAlien } from "react-icons/fa";
+
+class MainLeft extends React.Component {
+  state = {
+    feedList: [],
+  };
+
+  componentDidMount() {
+    fetch("/data/FeedData.json", {
+      method: "GET",
+    })
+      .then((res) => res.json())
+      .then((data) => {
+        this.setState({
+          feedList: data,
+        });
+      });
+  }
+
+  aaa = () => {
+    // console.log("aaa 함수호출");
+    fetch("http://10.58.2.229:8000/posts/post", {
+      method: "GET",
+      headers: {
+        Authorization: localStorage.getItem("Token"),
+      },
+    })
+      .then((res) => res.json())
+      .then((res) => {
+        this.setState({
+          feedList: res.RESULT,
+        });
+        console.log(res.RESULT);
+      });
+  };
+
+  render() {
+    const { feedList } = this.state;
+    return (
+      <div className="main-left">
+        {/* <button onClick={this.aaa}>click</button> */}
+        <Story />
+        {feedList.map((element) => {
+          return (
+            <Feed
+              id={element.id}
+              name={element.userId}
+              count={element.count}
+              img={element.img}
+              title={element.title}
+            />
+          );
+        })}
+      </div>
+    );
+  }
+}
+
+export default MainLeft;

리뷰: 1. 로컬 호스트의 경우 따로 입력을 해주지 않으셔도 자동으로 들어가게 됩니다. 이렇게 생략해서 작성을 해주시는 게 더 좋습니다. 왜냐하면, 포트 번호가 변경되는 때가 생각보다 많은데, 이렇게 직접 작성을 해주신 경우 포트 번호를 일일이 수정해주시야 합니다. 생략을 해서 아래와 같이 작성할 경우, 자동으로 변경된 포트번호가 들어갑니다 🙌
```js
fetch(`/data/Story.json`)
.then(res => res.json())
.then( ... )
```
2. fetch 의 기본 메서드는 GET 입니다. 따라서 { method: 'GET' } 부분도 생략해 주실 수 있습니다.
@@ -1,17 +1,38 @@
 import { AccessTokenGuard } from '#guards/access-token.guard.js';
 import { IReviewController } from '#reviews/interfaces/review.controller.interface.js';
-import { Body, Controller, Delete, Get, Param, Post, UseGuards } from '@nestjs/common';
+import { Body, Controller, Delete, Get, Param, Post, Query, UseGuards } from '@nestjs/common';
 import { ApiOperation } from '@nestjs/swagger';
 import { ReviewService } from './review.service.js';
 import { ReviewInputDTO } from './review.types.js';
+import { GetQueries } from '#types/queries.type.js';
+import { SortOrder } from '#types/options.type.js';
 
 @Controller('reviews')
 export class ReviewController implements IReviewController {
   constructor(private readonly reviewService: ReviewService) {}
 
+  @Get('my')
+  @UseGuards(AccessTokenGuard)
+  @ApiOperation({ summary: '내가 작성한 리뷰 조회' })
+  async getMyReviews(@Query() query: GetQueries) {
+    const { page = 1, pageSize = 10, orderBy = SortOrder.Recent, keyword = '' } = query;
+    const options = { page, pageSize, orderBy, keyword };
+
+    const { totalCount, list } = await this.reviewService.getMyReviews(options);
+
+    return { totalCount, list };
+  }
+
   @Get(':driverId')
   @ApiOperation({ summary: '기사 리뷰 조회' })
-  async getReviews() {}
+  async getDriverReviews(@Param('driverId') driverId: string, @Query() query: GetQueries) {
+    const { page = 1, pageSize = 10, orderBy = SortOrder.Recent, keyword = '' } = query;
+    const options = { page, pageSize, orderBy, keyword };
+
+    const { totalCount, list } = await this.reviewService.getDriverReviews(driverId, options);
+
+    return { totalCount, list };
+  }
 
   @Post(':driverId')
   @UseGuards(AccessTokenGuard)
@@ -22,11 +43,21 @@ export class ReviewController implements IReviewController {
     return review;
   }
 
-  @Post(':reviewId')
+  @Post('patch/:reviewId')
+  @UseGuards(AccessTokenGuard)
   @ApiOperation({ summary: '리뷰 수정' })
-  async patchReview() {}
+  async patchReview(@Param('reviewId') reviewId: string, @Body() body: Partial<ReviewInputDTO>) {
+    const review = await this.reviewService.patchReview(reviewId, body);
+
+    return review;
+  }
 
   @Delete(':reviewId')
+  @UseGuards(AccessTokenGuard)
   @ApiOperation({ summary: '리뷰 삭제' })
-  async deleteReview() {}
+  async deleteReview(@Param('reviewId') reviewId: string) {
+    const review = await this.reviewService.deleteReview(reviewId);
+
+    return review;
+  }
 }

리뷰: userId는 액세스 토큰에서 가져올 수 있으니 파라미터에서 가져올 필요는 없어보여요!