Skip to content

Commit

Permalink
Merge pull request #67 from codestates-seb/feat_FE_AskQuestionPage수정
Browse files Browse the repository at this point in the history
 React hooks을 Redux로 교체
27Lia authored Aug 16, 2023
2 parents e41694a + 9e02410 commit 7af5e20
Showing 12 changed files with 467 additions and 55 deletions.
45 changes: 41 additions & 4 deletions client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
@@ -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"
},
9 changes: 3 additions & 6 deletions client/src/App.js
Original file line number Diff line number Diff line change
@@ -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 (
<div className="App">
<Header />
<Sidebar />
<Footer />
<AskQuestionPage />
</div>
);
}
15 changes: 11 additions & 4 deletions client/src/components/Editor5.js
Original file line number Diff line number Diff line change
@@ -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 });
};

// 처음 글자입력시 <p> 태그까지 글자로 인식하여 8부터 시작함 때문에 length를 26으로 설정
const isButtonDisabled = editorContent.length < 26;
// editorContent가 객체 형태라서 editorContent.editorContent.length
const isButtonDisabled = editorContent.editorContent.length < 26;
return (
<StyledEditor isButtonDisabled={isButtonDisabled}>
<div className="editor-container">
1 change: 1 addition & 0 deletions client/src/images/facebook-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions client/src/images/github-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions client/src/images/google-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions client/src/images/small-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 7 additions & 3 deletions client/src/index.js
Original file line number Diff line number Diff line change
@@ -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(
<React.StrictMode>
<App />
</React.StrictMode>,
<Provider store={store}>
<React.StrictMode>
<App />
</React.StrictMode>
</Provider>,
);

// If you want to start measuring performance in your app, pass a function
114 changes: 76 additions & 38 deletions client/src/pages/AskQuestionPage.js
Original file line number Diff line number Diff line change
@@ -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}
/>
{/* */}
</div>
{checkContainerVisible && (
<div className="check-container">
248 changes: 248 additions & 0 deletions client/src/pages/LoginPage.js
Original file line number Diff line number Diff line change
@@ -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 (
<StyleLoginPage>
<div className="container">
<div className="img-contaienr">
<a href="https://stackoverflow.com">
<img src={icon} alt="stackoverflow-logo"></img>
</a>
</div>
<div className="link-button-container">
<button className="link-button google-btn">
<a href="https://accounts.google.com/o/oauth2/auth">
<img className="link-icon" src={googleIcon} alt="google-icon" />
Log in with Google
</a>
</button>

<button className="link-button github-btn">
<a href="https://https://github.com/login/oauth/authorize">
<img
className="link-icon"
src={githubIcon}
alt="github-icon"
></img>
Log in with GitHub
</a>
</button>
<button className="link-button facebook-btn">
<a href="https://www.facebook.com/v2.0/dialog/oauth">
<img
className="link-icon"
src={facebookIcon}
alt="facebook-icon"
></img>
Log in with Facebook{' '}
</a>
</button>
</div>
<div className="form-container">
<form id="login-form">
<div className="login-box">
<form className="login-form">
<label htmlFor="email">Email</label>
<div>
<input
className="email-input"
id="email"
type="email"
></input>
</div>
</form>
</div>
<div className="password-box">
<form className="password-form">
<div className="password-lable">
<label htmlFor="password">Password</label>
<a className="forgot-password" href="/users/account-recovery">
Forgot password?
</a>
</div>
<div>
<input
className="password-input"
id="password"
type="password"
></input>
</div>
</form>
</div>
<div className="login-button-container">
<button className="login-button">Log in</button>
</div>
</form>
</div>
<div className="join-guide-container">
Don’t have an account?
<a
className="join-link "
href="/users/signup?ssrc=head&returnurl=https%3a%2f%2fstackoverflow.com%2f"
>
Sign Up
</a>
<div className="talent-guide-container">
Are you an employer?
<a
className="join-link "
href="https://careers.stackoverflow.com/employer/login"
>
Sign up on Talent
<FontAwesomeIcon icon={faUpRightFromSquare} />{' '}
</a>
</div>
</div>
</div>
</StyleLoginPage>
);
}

export default LoginPage;
47 changes: 47 additions & 0 deletions client/src/store.js
Original file line number Diff line number Diff line change
@@ -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;

0 comments on commit 7af5e20

Please sign in to comment.