-
Notifications
You must be signed in to change notification settings - Fork 0
질문 작성 API를 연결한다. #35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
질문 작성 API를 연결한다. #35
Changes from all commits
16edc90
1f768fd
6cb1770
4125e54
376b510
f8a690d
d39fb19
9eebe65
e46d5be
796733c
1488d78
f2cc88a
66ebe42
20f1e99
cbd764b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,12 @@ | ||
| import React, {ChangeEvent, useState} from 'react'; | ||
| import * as React from 'react'; | ||
| import {ChangeEvent, useState, useContext, useEffect} from 'react'; | ||
| import {MemberContext} from "../contexts/MemberContext"; | ||
| import styled from 'styled-components'; | ||
| import Questioner from './Questioner'; | ||
| import {fetchAPI} from '../constants/api'; | ||
| import {isCreated} from '../constants/status'; | ||
| import {getIsLogin} from "../utils/tokenHandler"; | ||
| import {useRouter} from "next/router"; | ||
|
|
||
| const Container = styled.div` | ||
| /*Layout 적용 이후 width 변경 요망* */ | ||
|
|
@@ -29,6 +35,7 @@ const InputTitle = styled.input.attrs({ | |
| width: 100%; | ||
| border: 0.1px solid rgba(0, 0, 0, 0.2); | ||
| border-radius: 7px; | ||
|
|
||
| ::placeholder { | ||
| color: rgba(0, 0, 0, 0.2); | ||
| font-weight: 500; | ||
|
|
@@ -68,12 +75,62 @@ const ButtonSubmit = styled.button` | |
| `; | ||
|
|
||
| const QuestionInput: React.FC = () => { | ||
| const {member: {isLogin}, setMember} = useContext(MemberContext); | ||
| const [isFolded, setIsFolded] = useState<boolean>(true); | ||
| const [title, setTitle] = useState<string>(''); | ||
| const [mainContent, setMainContent] = useState<string>(''); | ||
| const {push} = useRouter(); | ||
|
|
||
| useEffect(() => { | ||
| setMember({isLogin: getIsLogin()}); | ||
| }, []); | ||
|
|
||
| const isNotVerified = () => { | ||
| if (!title) { | ||
| alert('질문 제목을 입력해주세요.'); | ||
| return true; | ||
| } else if (!mainContent) { | ||
| alert('질문 내용을 입력해주세요.'); | ||
| return true; | ||
| } else if (!isLogin) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 로그인이 안되어있으니 로그인 페이지로 보내는것도 좋을것 같아요
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오옹 좋은 제안입니다. 다음 커밋에 반영할게요!
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 좋네용 |
||
| alert('로그인이 필요합니다.'); | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| const handleClickNext = () => { | ||
| if (!title) { | ||
| alert('질문 제목을 입력해주세요.'); | ||
| return; | ||
| } | ||
|
|
||
| setIsFolded(false); | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 불필요한 공백을 발견했슴니당😬
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 지금 제 eslint와 prettier 설정이 비정상적인 부분이 많은데..... 다음 프로젝트 들어갈 땐 확실히 설정하고 시작해야겠어요! 반영하겠습니다 😃
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 좋습니다! |
||
| }; | ||
|
|
||
| const handleClickSubmit = async () => { | ||
| if (isNotVerified()) { | ||
| return; | ||
| } | ||
| /**responderId 관련 로직 확인 필요 */ | ||
| const request = { | ||
| title : title, | ||
| contents : mainContent, | ||
| responderId : null, | ||
| } | ||
|
|
||
| const handleClick = () => { | ||
| setIsFolded(!isFolded); | ||
| try { | ||
| const {status} = await fetchAPI('POST', 'Questions', null, request); | ||
|
|
||
| if (isCreated(status)) { | ||
| alert('질문이 등록되었습니다.'); | ||
| await push('/'); | ||
| } | ||
| } catch (e) { | ||
| console.error(e); | ||
| alert('질문 등록에 실패했습니다. 다시 시도해주세요.'); | ||
| } | ||
| }; | ||
|
|
||
| const handleTitleChange = (e: ChangeEvent<HTMLInputElement>) => { | ||
|
|
@@ -90,18 +147,18 @@ const QuestionInput: React.FC = () => { | |
| <> | ||
| <HeaderContainer> | ||
| <Questioner /> | ||
| <InputTitle onChange={handleTitleChange} value={title} /> | ||
| <ButtonNext onClick={handleClick}>Next</ButtonNext> | ||
| <InputTitle onChange={handleTitleChange} /> | ||
| <ButtonNext onClick={handleClickNext}>Next</ButtonNext> | ||
| </HeaderContainer> | ||
| </> | ||
| ) : ( | ||
| <div> | ||
| <HeaderContainer> | ||
| <Questioner /> | ||
| <InputTitle value={title} /> | ||
| <InputTitle onChange={handleTitleChange} defaultValue={title}/> | ||
| </HeaderContainer> | ||
| <InputMain onChange={handleMainChange} value={mainContent} /> | ||
| <ButtonSubmit onClick={handleClick}>Submit</ButtonSubmit> | ||
| <ButtonSubmit onClick={handleClickSubmit}>Submit</ButtonSubmit> | ||
| </div> | ||
| )} | ||
| </Container> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,55 @@ | ||
| const BASE_URL = 'http://localhost:8080'; | ||
| import axios from "axios"; | ||
| import {TOKEN, TOKEN_TYPE} from "./token"; | ||
|
|
||
| export const API = ( | ||
| model: 'Question' | 'Answer' | 'Signup' | 'Login' | string, | ||
| questionsId?: string | ||
| const BASE_URL = 'http://13.124.134.56:8080'; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👏👏👏👏👏 |
||
|
|
||
| type API = 'Questions' | 'Answers' | 'Signup' | 'Login' | 'QuestionDetail'; | ||
|
|
||
| const client = axios.create({ | ||
| baseURL: "", | ||
| }) | ||
|
|
||
| export const getAPIPath = ( | ||
| model: API, | ||
| questionsId?: string | number | ||
| ) => | ||
| model === 'Question' | ||
| model === 'Questions' | ||
| ? `${BASE_URL}/questions` | ||
| : model === 'Answer' | ||
| ? `${BASE_URL}/questions/${questionsId}/answers` | ||
| : model === 'Login' | ||
| ? `${BASE_URL}/login` | ||
| : model === 'Signup' | ||
| ? `${BASE_URL}/members/` | ||
| : ''; | ||
| : model === 'QuestionDetail' | ||
| ? `${BASE_URL}/questions/${questionsId}` | ||
| : model === 'Answers' | ||
| ? `${BASE_URL}/questions/${questionsId}/answers` | ||
| : model === 'Login' | ||
| ? `${BASE_URL}/login` | ||
| : model === 'Signup' | ||
| ? `${BASE_URL}/members` | ||
| : ''; | ||
|
|
||
| export const fetchAPI = async (method: 'POST' | 'GET' | 'PUT' | 'DELETE', api: API, questionsId?: string | number, content?: {}) => { | ||
|
|
||
|
|
||
| if (method === 'POST' && api === 'Signup') { | ||
| return await client.post(getAPIPath(api, questionsId), content); | ||
| } | ||
| if (method === 'POST') { | ||
| return await client.post(getAPIPath(api, questionsId), content, | ||
| { | ||
| headers: {Authorization: `${localStorage.getItem(TOKEN_TYPE)} ${localStorage.getItem(TOKEN)}`} | ||
| }) | ||
| } | ||
| if (method === 'GET') { | ||
| return await client.get(getAPIPath(api, questionsId)) | ||
| } | ||
| if (method === 'PUT') { | ||
| return await client.put(getAPIPath(api, questionsId), content, | ||
| { | ||
| headers: {Authorization: `${TOKEN_TYPE} ${TOKEN}`} | ||
| }) | ||
| } | ||
| if (method === 'DELETE') { | ||
| return await client.delete(getAPIPath(api, questionsId), { | ||
| headers: {Authorization: `${TOKEN_TYPE} ${TOKEN}`} | ||
| }) | ||
| } | ||
| throw new Error(); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| export const | ||
| isOk = (status: number) => status === 200, | ||
| isCreated = (status: number) => status === 201, | ||
| isNoContent = (status: number) => status === 204, | ||
| isBadRequest = (status: number) => status === 400, | ||
| isUnauthorized = (status: number) => status === 401, | ||
| isNotFound = (status: number) => status === 404; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import * as React from 'react'; | ||
| import {useState} from 'react'; | ||
|
|
||
| const MemberContext = React.createContext(null); | ||
|
|
||
| const MemberProvider = ({children}) => { | ||
| const [member, setMember] = useState({ | ||
| isLogin: false, | ||
| }); | ||
|
|
||
| const memberState = { | ||
| member, setMember | ||
| }; | ||
|
|
||
| return ( | ||
| <MemberContext.Provider value={memberState}> | ||
| {children} | ||
| </MemberContext.Provider> | ||
| ) | ||
| }; | ||
|
|
||
| export { | ||
| MemberProvider, | ||
| MemberContext | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
initialState로 type을 추론할 수 있지 않을까요?
이런 경우엔 타입을 생략하는 걸 선호하는데, 어떻게 생각하시나요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
일단 추론 가능한 경우에 type 생략을 선호하신다는 것에 충분히 공감합니다. 'type을 정해주어야한다'는 강박에 매몰되는 걸 경계해야겠구나 하고 생각했어요.
그러면서 타입스크립트를 사용하는 이유에 대해 다시 한 번 생각해보았는데요, 예측 불가능한 실수를 미리 막는다는 점에서 이러한 경우에도 타입을 명시하는 게 더 좋지 않을까(제가 코드를 작성하며 어떤 실수를 할 지 모르니..) 하고 생각했어요. 또 레포를 몇 개 돌아다니면서 봤는데 명시해주는 경우가 더 많은 것 같더라고요. 그래서 그러한 경우에도 저는 타입을 명시해줄 것 같습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
제 pr에 남겨두셨던 코멘트와 비슷한 내용이군요.
어떻게하면 좋을지 생각해보죠!
저번에는 생략쪽에 기울었는데 다희님께서 다른 레포를 참고하시고 명시하는 경우가 더 많다니까 다시 고려해볼만 하네요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
정답은 없는 부분이니 다음에 이야기 나눠보죠 🙂