diff --git a/.gitignore b/.gitignore index 3c3629e..e1cc852 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ node_modules + +.env + +.env.development +.idea diff --git a/package-lock.json b/package-lock.json index ae26def..a207bea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^13.3.0", "@testing-library/user-event": "^13.5.0", + "prettier": "^2.7.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.3.0", @@ -13564,6 +13565,20 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -26382,6 +26397,11 @@ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" }, + "prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==" + }, "pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", diff --git a/package.json b/package.json index 45ee750..aecd253 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^13.3.0", "@testing-library/user-event": "^13.5.0", + "prettier": "^2.7.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.3.0", diff --git a/src/hooks/user/useSetUser.js b/src/hooks/user/useSetUser.js new file mode 100644 index 0000000..d3e8a5e --- /dev/null +++ b/src/hooks/user/useSetUser.js @@ -0,0 +1,7 @@ +import { useSetRecoilState } from "recoil"; +import userState from "../../state/user"; + +const useSetUser = () => { + return useSetRecoilState(userState); +}; +export default useSetUser; diff --git a/src/hooks/user/useUserLogin.js b/src/hooks/user/useUserLogin.js new file mode 100644 index 0000000..828460d --- /dev/null +++ b/src/hooks/user/useUserLogin.js @@ -0,0 +1,26 @@ +import { + requestAccessTokenAPI, + requestLoginAPI, +} from "../../pages/solutionReportPage/utils/gitHubLogin"; +import useSetUser from "./useSetUser"; +import { useState } from "react"; +import useUserValue from "./useUserValue"; + +const useUserLogin = () => { + const [isLoggedIn, setIsLoggedIn] = useState(false); + const userInfo = useUserValue(); + const setUserInfo = useSetUser(); + const requestLogin = (code) => { + requestAccessTokenAPI(code) + .then((response) => requestLoginAPI(response.access_token)) + .then((response) => { + if (response.hasOwnProperty("login")) { + setIsLoggedIn(true); + setUserInfo(response); + } + if (userInfo) setIsLoggedIn(true); + }); + }; + return { isLoggedIn, requestLogin }; +}; +export default useUserLogin; diff --git a/src/hooks/user/useUserProfile.js b/src/hooks/user/useUserProfile.js new file mode 100644 index 0000000..d5ae9ea --- /dev/null +++ b/src/hooks/user/useUserProfile.js @@ -0,0 +1,11 @@ +import useUserValue from "./useUserValue"; + +const useUserProfile = () => { + const user = useUserValue(); + return { + profileImg: user.avatar_url ?? "", + username: user.name ?? "", + gitHubUrl: user.html_url ?? "", + }; +}; +export default useUserProfile; diff --git a/src/hooks/user/useUserValue.js b/src/hooks/user/useUserValue.js new file mode 100644 index 0000000..7db3079 --- /dev/null +++ b/src/hooks/user/useUserValue.js @@ -0,0 +1,7 @@ +import { useRecoilValue } from "recoil"; +import userState from "../../state/user"; + +const useUserValue = () => { + return useRecoilValue(userState); +}; +export default useUserValue; diff --git a/src/pages/errorReportPage/ErrorReport.js b/src/pages/errorReportPage/ErrorReport.js index a83763c..4fba4b6 100644 --- a/src/pages/errorReportPage/ErrorReport.js +++ b/src/pages/errorReportPage/ErrorReport.js @@ -21,10 +21,13 @@ export default function ErrorReport() { const [questionName, setQuestionName] = useState(""); const [detailContent, setDetailContent] = useState(""); - const isQuestionNameVisible = errorCategory !== "" && errorCategory !== "error-notCopied"; + const isQuestionNameVisible = + errorCategory !== "" && errorCategory !== "error-notCopied"; const isDetailContentVisible = - errorCategory !== "" && (errorCategory !== "error-wrongAnswer" || questionName !== ""); - const isSubmitBtnDisabled = errorCategory === "error-other" && detailContent === ""; + errorCategory !== "" && + (errorCategory !== "error-wrongAnswer" || questionName !== ""); + const isSubmitBtnDisabled = + errorCategory === "error-other" && detailContent === ""; function handleOtherErrorBtnClick() { setSubmitted(false); @@ -56,14 +59,19 @@ export default function ErrorReport() { {submitted ? ( <> 제보해주셔서 감사합니다. - 다른 오류 제보 + + 다른 오류 제보 + ) : ( 오류 유형 - diff --git a/src/pages/solutionReportPage/SolutionReport.js b/src/pages/solutionReportPage/SolutionReport.js index b919157..7cd7f9b 100644 --- a/src/pages/solutionReportPage/SolutionReport.js +++ b/src/pages/solutionReportPage/SolutionReport.js @@ -1,31 +1,41 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import styled from "styled-components"; import Header from "../../components/Header"; import { - ThanksMsg, - OtherReportBtn, + InputLabel, MainContetnWrapper, + OtherReportBtn, StepByStepInputItem, - InputLabel, - TextInput, - QuestionList, - QuestionItem, - QuestionBtn, - TextArea, SubmitBtn, + TextArea, + TextInput, + ThanksMsg, } from "../../style/styledComponents"; import gitHubLogoSrc from "../../images/github-logo-white.png"; +import { useSearchParams } from "react-router-dom"; +import { LOGIN_URL } from "./utils/gitHubLogin"; +import useUserProfile from "../../hooks/user/useUserProfile"; +import useUserLogin from "../../hooks/user/useUserLogin"; export default function SolutionReport() { const [submitted, setSubmitted] = useState(false); const [questionName, setQuestionName] = useState(""); const [detailContent, setDetailContent] = useState(""); + const [searchParams, setSearchParams] = useSearchParams(); + const userInfo = useUserProfile(); + const { isLoggedIn, requestLogin } = useUserLogin(); + useEffect(() => { + if (searchParams.get("code")) { + const code = searchParams.get("code"); + requestLogin(code); + } + }, [searchParams]); + const handleGitHubLogin = async () => {}; const isDetailContentVisible = questionName !== ""; const isSubmitBtnDisabled = detailContent === ""; function handleOtherSolutionBtnClick() { - console.log("!"); setSubmitted(false); setQuestionName(""); setDetailContent(""); @@ -50,7 +60,9 @@ export default function SolutionReport() { {submitted ? ( <> 제보해주셔서 감사합니다. - 다른 정답 제보 + + 다른 정답 제보 + ) : ( @@ -61,40 +73,55 @@ export default function SolutionReport() { placeholder="문제 이름을 검색하세요." defaultValue={questionName} onInput={handleQuestionNameInput} - > - - - 1번문제 - - - 2번문제 - - - 3번문제 - - - 4번문제 - - - 5번문제 - - - 6번문제 - - - 7번문제 - - - 8번문제 - - + /> + {/**/} + {/* */} + {/* 1번문제*/} + {/* */} + {/* */} + {/* 2번문제*/} + {/* */} + {/* */} + {/* 3번문제*/} + {/* */} + {/* */} + {/* 4번문제*/} + {/* */} + {/* */} + {/* 5번문제*/} + {/* */} + {/* */} + {/* 6번문제*/} + {/* */} + {/* */} + {/* 7번문제*/} + {/* */} + {/* */} + {/* 8번문제*/} + {/* */} + {/**/} - {isDetailContentVisible && ( + {/*isDetailContentVisible &&*/} + { <> 기여자 등록 - GitHub 로그인 + {isLoggedIn ? ( + + 이름: {userInfo.username} + 이미지: {userInfo.profileImg} + + ) : ( + + + GitHub 로그인 + + + )} 내용 @@ -104,7 +131,7 @@ export default function SolutionReport() { cols="100" onInput={handleDetailContentInput} defaultValue={detailContent} - > + /> @@ -117,7 +144,7 @@ export default function SolutionReport() { - )} + } )} @@ -139,7 +166,6 @@ const GitHubLoginBtn = styled.button` background-repeat: no-repeat; cursor: pointer; `; - // const Msg = styled.span` // color: ${(props) => props.theme.programmersBlue}; // `; diff --git a/src/pages/solutionReportPage/utils/gitHubLogin.js b/src/pages/solutionReportPage/utils/gitHubLogin.js new file mode 100644 index 0000000..e2e39b6 --- /dev/null +++ b/src/pages/solutionReportPage/utils/gitHubLogin.js @@ -0,0 +1,35 @@ +export const LOGIN_URL = + "https://github.com/login/oauth/authorize?client_id=" + + process.env.REACT_APP_CLIENT_ID; + +export const requestAccessTokenAPI = async (code) => { + try { + const client_secret = process.env.REACT_APP_CLIENT_SECRET; + const client_id = process.env.REACT_APP_CLIENT_ID; + const response = await fetch( + `https://github.com/login/oauth/access_token?client_id=${client_id}&client_secret=${client_secret}&code=${code}`, + { + method: "POST", + headers: { + Accept: "application/json", + }, + } + ); + return response.json(); + } catch (e) { + console.log(e); + } +}; +export const requestLoginAPI = async (access_token) => { + try { + const userInfo = await fetch("https://api.github.com/user", { + headers: { + Accept: "application/vnd.github+json", + Authorization: `token ${access_token}`, + }, + }); + return userInfo.json(); + } catch (e) { + console.log(e); + } +}; diff --git a/src/state/user.js b/src/state/user.js new file mode 100644 index 0000000..71fd64d --- /dev/null +++ b/src/state/user.js @@ -0,0 +1,48 @@ +import { atom } from "recoil"; +import { sessionStorageEffect } from "./utils/sessionStorageEffect"; + +const userState = atom({ + key: "userState", + default: {}, + effects: [sessionStorageEffect("userState")], +}); +export default userState; + +const userObject = { + avatar_url: "https://avatars.githubusercontent.com/u/54318460?v=4", + bio: null, + blog: "", + company: null, + created_at: "2019-08-20T13:16:15Z", + email: "codeisneverodd@gmail.com", + events_url: "https://api.github.com/users/codeisneverodd/events{/privacy}", + followers: 73, + followers_url: "https://api.github.com/users/codeisneverodd/followers", + following: 168, + following_url: + "https://api.github.com/users/codeisneverodd/following{/other_user}", + gists_url: "https://api.github.com/users/codeisneverodd/gists{/gist_id}", + gravatar_id: "", + hireable: null, + html_url: "https://github.com/codeisneverodd", + id: 54318460, + location: null, + login: "codeisneverodd", + name: "codeisneverodd", + node_id: "MDQ6VXNlcjU0MzE4NDYw", + organizations_url: "https://api.github.com/users/codeisneverodd/orgs", + public_gists: 1, + public_repos: 9, + received_events_url: + "https://api.github.com/users/codeisneverodd/received_events", + repos_url: "https://api.github.com/users/codeisneverodd/repos", + site_admin: false, + starred_url: + "https://api.github.com/users/codeisneverodd/starred{/owner}{/repo}", + subscriptions_url: + "https://api.github.com/users/codeisneverodd/subscriptions", + twitter_username: null, + type: "User", + updated_at: "2022-07-15T04:07:10Z", + url: "https://api.github.com/users/codeisneverodd", +}; diff --git a/src/state/utils/sessionStorageEffect.js b/src/state/utils/sessionStorageEffect.js new file mode 100644 index 0000000..f994c05 --- /dev/null +++ b/src/state/utils/sessionStorageEffect.js @@ -0,0 +1,14 @@ +export const sessionStorageEffect = + (key) => + ({ setSelf, onSet }) => { + const savedValue = sessionStorage.getItem(key); + if (savedValue != null) { + setSelf(JSON.parse(savedValue)); + } + + onSet((newValue, _, isReset) => { + isReset + ? sessionStorage.removeItem(key) + : sessionStorage.setItem(key, JSON.stringify(newValue)); + }); + };