diff --git a/client/package-lock.json b/client/package-lock.json index 70374a36..7f2c503f 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -16,7 +16,6 @@ "@ckeditor/ckeditor5-paragraph": "^39.0.1", "@ckeditor/ckeditor5-react": "^6.1.0", "@ckeditor/ckeditor5-theme-lark": "^39.0.1", - "@fortawesome/fontawesome-free": "^6.4.2", "@fortawesome/free-brands-svg-icons": "^6.4.2", "@fortawesome/free-regular-svg-icons": "^6.4.2", @@ -32,6 +31,7 @@ "react-redux": "^8.1.2", "react-router-dom": "^6.14.2", "react-scripts": "5.0.1", + "redux": "^4.1.2", "styled-components": "^6.0.7", "web-vitals": "^2.1.4" }, @@ -4553,6 +4553,30 @@ "node": ">=6" } }, + "node_modules/@fortawesome/free-brands-svg-icons": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.4.2.tgz", + "integrity": "sha512-LKOwJX0I7+mR/cvvf6qIiqcERbdnY+24zgpUSouySml+5w8B4BJOx8EhDR/FTKAu06W12fmUIcv6lzPSwYKGGg==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.4.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-regular-svg-icons": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.4.2.tgz", + "integrity": "sha512-0+sIUWnkgTVVXVAPQmW4vxb9ZTHv0WstOa3rBx9iPxrrrDH6bNLsDYuwXF9b6fGm+iR7DKQvQshUH/FJm3ed9Q==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.4.2" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@fortawesome/free-solid-svg-icons": { "version": "6.4.2", "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.4.2.tgz", @@ -4577,6 +4601,11 @@ "react": ">=16.3" } }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==" + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", @@ -5567,6 +5596,14 @@ } } }, + "node_modules/@reduxjs/toolkit/node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, "node_modules/@remix-run/router": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.7.2.tgz", @@ -17936,9 +17973,9 @@ } }, "node_modules/redux": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", - "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.1.2.tgz", + "integrity": "sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==", "dependencies": { "@babel/runtime": "^7.9.2" } diff --git a/client/package.json b/client/package.json index b77f1382..04106456 100644 --- a/client/package.json +++ b/client/package.json @@ -26,6 +26,7 @@ "react-redux": "^8.1.2", "react-router-dom": "^6.14.2", "react-scripts": "5.0.1", + "redux": "^4.1.2", "styled-components": "^6.0.7", "web-vitals": "^2.1.4" }, diff --git a/client/src/App.js b/client/src/App.js index 1b6ac1eb..496f6699 100644 --- a/client/src/App.js +++ b/client/src/App.js @@ -1,15 +1,12 @@ import './App.css'; +import AskQuestionPage from './pages/AskQuestionPage'; -import Sidebar from './components/sidebar'; -import Header from './components/Header'; -import Footer from './components/Footer'; +// import LoginPage from './pages/LoginPage'; function App() { return (
-
- -
); } diff --git a/client/src/components/Editor5.js b/client/src/components/Editor5.js index 868d4063..a2604258 100644 --- a/client/src/components/Editor5.js +++ b/client/src/components/Editor5.js @@ -2,8 +2,10 @@ import { CKEditor } from '@ckeditor/ckeditor5-react'; import ClassicEditor from '@ckeditor/ckeditor5-build-classic'; import { styled } from 'styled-components'; import PropTypes from 'prop-types'; -import { useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +// import { useState } from 'react'; +// ========== styled-components ========== const StyledEditor = styled.div` max-width: 800px; background-color: #ffffff; @@ -50,15 +52,20 @@ function Editor5({ handleButtonClick, editorRef, }) { - const [editorContent, setEditorContent] = useState(''); // 텍스트 내용을 상태로 관리 + const dispatch = useDispatch(); + + const editorContent = useSelector((state) => ({ + editorContent: state.editorContent, + })); const handleEditorChange = (event, editor) => { const data = editor.getData(); - setEditorContent(data); // 텍스트 내용 업데이트 + dispatch({ type: 'SET_EDITOR_CONTENT', payload: data }); }; // 처음 글자입력시

태그까지 글자로 인식하여 8부터 시작함 때문에 length를 26으로 설정 - const isButtonDisabled = editorContent.length < 26; + // editorContent가 객체 형태라서 editorContent.editorContent.length + const isButtonDisabled = editorContent.editorContent.length < 26; return (

diff --git a/client/src/images/facebook-icon.svg b/client/src/images/facebook-icon.svg new file mode 100644 index 00000000..fd46c08d --- /dev/null +++ b/client/src/images/facebook-icon.svg @@ -0,0 +1 @@ + diff --git a/client/src/images/github-icon.svg b/client/src/images/github-icon.svg new file mode 100644 index 00000000..ffbaa01f --- /dev/null +++ b/client/src/images/github-icon.svg @@ -0,0 +1,2 @@ + + diff --git a/client/src/images/google-icon.svg b/client/src/images/google-icon.svg new file mode 100644 index 00000000..d567bc20 --- /dev/null +++ b/client/src/images/google-icon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/client/src/images/small-logo.svg b/client/src/images/small-logo.svg new file mode 100644 index 00000000..2cbf6ae5 --- /dev/null +++ b/client/src/images/small-logo.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/client/src/index.js b/client/src/index.js index 9168dc41..9958c0e9 100644 --- a/client/src/index.js +++ b/client/src/index.js @@ -3,12 +3,16 @@ import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; +import { Provider } from 'react-redux'; +import store from './store'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( - - - , + + + + + , ); // If you want to start measuring performance in your app, pass a function diff --git a/client/src/pages/AskQuestionPage.js b/client/src/pages/AskQuestionPage.js index 7c85789c..3c39470f 100644 --- a/client/src/pages/AskQuestionPage.js +++ b/client/src/pages/AskQuestionPage.js @@ -1,13 +1,18 @@ import { styled } from 'styled-components'; import Editor5 from '../components/Editor5'; -import { useState, useRef } from 'react'; +import { useRef } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; import QuestionPageDropdown from '../components/QuestionPageDropdown'; +// import Header from '../components/Header'; +// import Footer from '../components/Footer'; const StyleAskPage = styled.div` background-color: #f8f9f9; display: flex; flex-direction: column; align-items: center; + height: 100vh; + margin-top: 56px; // 사이드 여백 조정 .inner { @@ -135,75 +140,109 @@ const StyleAskPage = styled.div` `; function AskQuestionPage() { - const [isChecked, SetIsChecked] = useState(false); - - const handleCheckboxChange = (event) => { - const checkedValue = event.target.checked; - SetIsChecked(checkedValue); // 체크박스 상태 업데이트 - }; - - const isButtonDisabled = !isChecked; - - const [checkContainerVisible, setCheckContainerVisible] = useState(false); - - const [titleButtonVisible, setTitleButtonVisible] = useState(true); - const [editorButtonVisible, setEditorButtonVisible] = useState(false); - const [editor2ButtonVisible, setEditor2ButtonVisible] = useState(false); - - const [tagButtonVisible, setTagButtonVisible] = useState(false); - const [reviewButtonVisible, setReviewButtonVisible] = useState(false); - const editorRef = useRef(null); const editor2Ref = useRef(null); const TagRef = useRef(null); - const handleTitleButtonClick = () => { - setTitleButtonVisible(false); - setEditorButtonVisible(true); + // 리덕스 스토어에 액션을 디스패치하여 상태를 업데이트 하는 dispatch함수 + const dispatch = useDispatch(); + + const { + checkContainerVisible, + isChecked, + titleButtonVisible, + editorButtonVisible, + editor2ButtonVisible, + tagButtonVisible, + reviewButtonVisible, + isDropdownOpen, + } = useSelector((state) => ({ + // 리덕스 스토어에서 상태값을 가져옴 + checkContainerVisible: state.checkContainerVisible, + isChecked: state.isChecked, + titleButtonVisible: state.titleButtonVisible, + tagButtonVisible: state.tagButtonVisible, + reviewButtonVisible: state.reviewButtonVisible, + editorButtonVisible: state.editorButtonVisible, + editor2ButtonVisible: state.editor2ButtonVisible, + isDropdownOpen: state.isDropdownOpen, + })); + + // isChecked 상태의 반전 값을 가짐 + const isButtonDisabled = !isChecked; + + // ========== 핸들러 함수내 액션을 디스패치하여 Redux 상태를 업데이트하는 부분입니다. ========== - // 첫 번째 에디터에 포커스 설정 + // 제목 버튼 클릭 핸들러 + const handleTitleButtonClick = () => { + // titleButtonVisible 상태를 숨김(false)으로 변경 + dispatch({ type: 'SET_TITLE_BUTTON_VISIBLE', payload: false }); + // editorButtonVisible 상태를 표시(true)로 변경 + dispatch({ type: 'SET_EDITOR_BUTTON_VISIBLE', payload: true }); + // 만약 editorRef가 현재 존재하고 에디터 인스턴스가 있는 경우, + // 첫 번째 에디터의 인스턴스에 포커스를 설정 if (editorRef.current && editorRef.current.editor) { const editorInstance = editorRef.current.editor; editorInstance.focus(); } }; + // 첫 번째 에디터 버튼 클릭 핸들러 const handleEditorButtonClick = () => { - setEditorButtonVisible(false); - setEditor2ButtonVisible(true); - // 두 번째 에디터에 포커스 설정 + // editorButtonVisible 상태를 숨김(false)으로 변경 + dispatch({ type: 'SET_EDITOR_BUTTON_VISIBLE', payload: false }); + // editor2ButtonVisible 상태를 표시(true)로 변경 + dispatch({ type: 'SET_EDITOR2_BUTTON_VISIBLE', payload: true }); + // 만약 editor2Ref가 현재 존재하고 에디터 인스턴스가 있는 경우, + // 두 번째 에디터의 인스턴스에 포커스를 설정 if (editor2Ref.current && editor2Ref.current.editor) { const editorInstance = editor2Ref.current.editor; editorInstance.focus(); } }; + // 두 번째 에디터 버튼 클릭 핸들러 const handleEditor2ButtonClick = () => { - setEditor2ButtonVisible(false); - setTagButtonVisible(true); - // 태그에 포커스 설정 + // editor2ButtonVisible 상태를 숨김(false)으로 변경 + dispatch({ type: 'SET_EDITOR2_BUTTON_VISIBLE', payload: false }); + // tagButtonVisible 상태를 표시(true)로 변경 + dispatch({ type: 'SET_TAG_BUTTON_VISIBLE', payload: true }); + // 만약 TagRef가 현재 존재하면 (태그 입력란이 마운트되어 있으면),해당 태그 인풋에 포커스를 설정 if (TagRef.current) { TagRef.current.focus(); } }; + // 태그 버튼 클릭 핸들러 const handleTagButtonClick = () => { - setTagButtonVisible(false); - setReviewButtonVisible(true); - setCheckContainerVisible(true); + // tagButtonVisible 상태를 숨김(false)으로 변경 + dispatch({ type: 'SET_TAG_BUTTON_VISIBLE', payload: false }); + // checkContainerVisible 상태를 표시(true)로 변경 + dispatch({ type: 'SET_CHECK_CONTAINER_VISIBLE', payload: true }); + // reviewButtonVisible 상태를 표시(true)로 변경 + dispatch({ type: 'SET_REVIEW_BUTTON_VISIBLE', payload: true }); }; + // 질문 검토 버튼 클릭 핸들러 const handleReviewButtonClick = () => { - setReviewButtonVisible(false); - setCheckContainerVisible(false); + // reviewButtonVisible 상태를 숨김(false)으로 변경 + dispatch({ type: 'SET_REVIEW_BUTTON_VISIBLE', payload: false }); + //checkContainerVisible 상태를 숨김(false)으로 변경 + dispatch({ type: 'SET_CHECK_CONTAINER_VISIBLE', payload: false }); }; - // 드롭다운 상태를 관리하는 상태 변수 - const [isDropdownOpen, setIsDropdownOpen] = useState(false); + // 체크박스 변경 핸들러 함수 + const handleCheckboxChange = (event) => { + // 체크박스의 선택 여부를 가져옴 + const checkedValue = event.target.checked; + // 리덕스 스토어에 액션을 디스패치하여 isChecked 상태를 업데이트함 + dispatch({ type: 'SET_IS_CHECKED', payload: checkedValue }); + }; // 드롭다운 토글 함수 const handleDropdownToggle = () => { - setIsDropdownOpen(!isDropdownOpen); + // isDropdownOpen 상태를 토글함 + dispatch({ type: 'SET_IS_DROPDOWN_OPEN' }); }; return ( @@ -325,7 +364,6 @@ function AskQuestionPage() { isOpen={isDropdownOpen} onToggle={handleDropdownToggle} /> - {/* */}
{checkContainerVisible && (
diff --git a/client/src/pages/LoginPage.js b/client/src/pages/LoginPage.js new file mode 100644 index 00000000..6398c3f9 --- /dev/null +++ b/client/src/pages/LoginPage.js @@ -0,0 +1,248 @@ +import { styled } from 'styled-components'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faUpRightFromSquare } from '@fortawesome/free-solid-svg-icons'; +import icon from '../images/small-logo.svg'; +import googleIcon from '../images/google-icon.svg'; +import githubIcon from '../images/github-icon.svg'; +import facebookIcon from '../images/facebook-icon.svg'; + +const StyleLoginPage = styled.div` + background-color: #f1f2f3; + height: 100vh; + display: flex; + align-items: center; + justify-content: center; + + .container { + width: 288.45px; + } + + .img-contaienr { + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 24px; + } + + .link-button-container { + display: flex; + flex-direction: column; + gap: 8px; + width: 100%; + } + + .link-icon { + margin-right: 5px; + } + + .link-button { + height: 37.4px; + flex-shrink: 0; + border-radius: 7px; + border: 1px solid #c8ccd0; + color: #ffffff; + display: flex; + align-items: center; + justify-content: center; + } + a { + color: #ffffff; + } + + .link-button > a { + display: flex; + justify-content: center; + align-items: center; + } + + .google-btn { + background-color: #ffffff; + } + .google-btn > a { + color: #3b4045; + } + .google-icon { + width: 18px; + height: 18px; + } + .github-btn { + background-color: #2f3337; + } + + .facebook-btn { + background-color: #385499; + } + .form-container { + width: 100%; + margin-top: 16px; + margin-bottom: 24px; + height: 234px; + border-radius: 7px; + background: #ffffff; + box-shadow: + 0 10px 24px hsla(0, 0%, 0%, 0.05), + 0 20px 48px hsla(0, 0%, 0%, 0.05), + 0 1px 4px hsla(0, 0%, 0%, 0.1); + padding: 24px; + } + #login-form { + display: flex; + flex-direction: column; + justify-content: space-around; + gap: 15px; + } + + label { + font-weight: bold; + } + input { + border-radius: 5px; + border: 1px solid #babfc4; + background: #fff; + height: 35px; + flex-shrink: 0; + width: 100%; + } + + .login-button-container { + display: flex; + align-items: center; + justify-content: center; + width: 223px; + height: 37px; + background-color: #0a95ff; + border-radius: 5px; + width: 100%; + } + + .login-button { + color: #ffffff; + } + .password-lable { + display: flex; + justify-content: space-between; + } + + .forgot-password { + color: #0074cc; + font-size: 12px; + } + + .join-guide-container { + text-align: center; + padding: 16px; + font-size: 14px; + width: 100%; + } + .talent-guide-container { + margin-top: 12px; + width: 100%; + } + .join-link { + margin-left: 3px; + color: #0074cc; + } + .fa-up-right-from-square { + margin-left: 5px; + } +`; + +function LoginPage() { + return ( + +
+
+ + stackoverflow-logo + +
+
+ + + + +
+
+
+
+ + +
+ +
+ +
+
+
+
+ + + Forgot password? + +
+
+ +
+
+
+
+ +
+ +
+
+ Don’t have an account? + + Sign Up + +
+ Are you an employer? + + Sign up on Talent + {' '} + +
+
+
+
+ ); +} + +export default LoginPage; diff --git a/client/src/store.js b/client/src/store.js new file mode 100644 index 00000000..7341b149 --- /dev/null +++ b/client/src/store.js @@ -0,0 +1,47 @@ +// store.js + +import { createStore } from 'redux'; + +// 초기 상태 설정 +const initialState = { + checkContainerVisible: false, + isChecked: false, + titleButtonVisible: true, + tagButtonVisible: false, + reviewButtonVisible: false, + editorButtonVisible: false, + editor2ButtonVisible: false, + isDropdownOpen: false, + editorContent: '', +}; + +// 리듀서 함수, 액션에 따라 상태를 업데이트 +const rootReducer = (state = initialState, action) => { + switch (action.type) { + case 'SET_CHECK_CONTAINER_VISIBLE': + return { ...state, checkContainerVisible: action.payload }; + case 'SET_IS_CHECKED': + return { ...state, isChecked: action.payload }; + case 'SET_TITLE_BUTTON_VISIBLE': + return { ...state, titleButtonVisible: action.payload }; + case 'SET_TAG_BUTTON_VISIBLE': + return { ...state, tagButtonVisible: action.payload }; + case 'SET_REVIEW_BUTTON_VISIBLE': + return { ...state, reviewButtonVisible: action.payload }; + case 'SET_EDITOR_BUTTON_VISIBLE': + return { ...state, editorButtonVisible: action.payload }; + case 'SET_EDITOR2_BUTTON_VISIBLE': + return { ...state, editor2ButtonVisible: action.payload }; + case 'SET_IS_DROPDOWN_OPEN': + return { ...state, isDropdownOpen: !state.isDropdownOpen }; + case 'SET_EDITOR_CONTENT': + return { ...state, editorContent: action.payload }; + default: + return state; + } +}; + +// 스토어 생성 +const store = createStore(rootReducer); + +export default store;