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 (
+
+
+
+ );
+}
+
+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;