From bf62dad6d707b1706b224f49da6f8b12dd889d9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AC=B8=EC=A7=80=EC=9B=90?= <81554184+jeewonMoon@users.noreply.github.com> Date: Fri, 6 Sep 2024 20:32:13 +0900 Subject: [PATCH] =?UTF-8?q?design:=20=EB=AA=A8=EB=B0=94=EC=9D=BC=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EA=B0=9C=EC=84=A0=20(#91)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MediaQuery 속성들을 모듈화했습니다. - 필터 레이아웃 중에서 공통으로 사용되는 부분들을 styles에 분리해 놨습니다. - 모바일 환경에서는 헤더의 ': 세종대학교 수강신청 연습 사이트'가 나타나지 않도록 변경했습니다. - 랩탑이 아닌 환경(모바일, 태블릿)에서는 기본적으로 사이드바가 접혀있도록 수정했습니다. - modules 폴더 안에 없는 슬라이스들을 폴더 안으로 옮겼습니다. --- package-lock.json | 75 ++++- package.json | 2 + src/apis/utils/instance.ts | 2 +- src/assets/img/close-sidebar.svg | 1 + .../CourseRegister/RegisterFilters.tsx | 14 +- src/components/CourseRegister/index.tsx | 33 +- src/components/Header/TopMenu.tsx | 4 +- src/components/Header/TopNav.tsx | 4 + src/components/Header/index.tsx | 37 ++- src/components/LectureList/Filters.tsx | 35 +-- src/components/LoginForm/index.tsx | 9 +- src/components/Menubar/BarTitle.tsx | 24 +- src/components/Menubar/index.tsx | 44 ++- src/components/Wishlist/Filters.tsx | 39 +-- src/components/common/FilterInput.tsx | 42 +-- src/components/common/Modal/WaitingModal.tsx | 2 +- .../common/Modal/handlers/handler.tsx | 2 +- src/components/common/SelectBox.tsx | 17 +- src/components/common/Table/index.tsx | 4 + src/pages/index/Home.tsx | 23 +- src/pages/index/Login.tsx | 283 +++++++++++------- .../{ => modules}/courseRegisteredSlice.ts | 0 src/store/{ => modules}/modalSlice.ts | 0 src/store/{ => modules}/userSlice.ts | 0 src/store/store.ts | 6 +- src/styles/FilterLayout.tsx | 39 +++ src/styles/theme/Theme.ts | 8 + src/styles/theme/style.d.ts | 5 +- 28 files changed, 482 insertions(+), 272 deletions(-) create mode 100644 src/assets/img/close-sidebar.svg rename src/store/{ => modules}/courseRegisteredSlice.ts (100%) rename src/store/{ => modules}/modalSlice.ts (100%) rename src/store/{ => modules}/userSlice.ts (100%) create mode 100644 src/styles/FilterLayout.tsx diff --git a/package-lock.json b/package-lock.json index 994db82..9dc03d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,12 +9,14 @@ "version": "0.0.0", "dependencies": { "@reduxjs/toolkit": "^2.2.6", + "@types/react-responsive": "^8.0.8", "axios": "^1.7.2", "js-cookie": "^3.0.5", "react": "^18.3.1", "react-dom": "^18.3.1", "react-ga4": "^2.1.0", "react-redux": "^9.1.2", + "react-responsive": "^10.0.0", "react-router-dom": "^6.24.0", "redux-persist": "^6.0.0", "styled-components": "^6.1.11", @@ -1500,14 +1502,12 @@ "node_modules/@types/prop-types": { "version": "15.7.12", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", - "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==", - "devOptional": true + "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" }, "node_modules/@types/react": { "version": "18.3.3", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", - "devOptional": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -1522,6 +1522,14 @@ "@types/react": "*" } }, + "node_modules/@types/react-responsive": { + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@types/react-responsive/-/react-responsive-8.0.8.tgz", + "integrity": "sha512-HDUZtoeFRHrShCGaND23HmXAB9evOOTjkghd2wAasLkuorYYitm5A1XLeKkhXKZppcMBxqB/8V4Snl6hRUTA8g==", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/styled-components": { "version": "5.1.34", "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.34.tgz", @@ -2050,6 +2058,11 @@ "node": ">=4" } }, + "node_modules/css-mediaquery": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/css-mediaquery/-/css-mediaquery-0.1.2.tgz", + "integrity": "sha512-COtn4EROW5dBGlE/4PiKnh6rZpAPxDeFLaEEwt4i10jpDMFt2EhQGS79QmmrO+iKCHv0PU/HrOWEhijFd1x99Q==" + }, "node_modules/css-to-react-native": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", @@ -2790,6 +2803,11 @@ "react-is": "^16.7.0" } }, + "node_modules/hyphenate-style-name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", + "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==" + }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -3043,6 +3061,14 @@ "yallist": "^3.0.2" } }, + "node_modules/matchmediaquery": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/matchmediaquery/-/matchmediaquery-0.4.2.tgz", + "integrity": "sha512-wrZpoT50ehYOudhDjt/YvUJc6eUzcdFPdmbizfgvswCKNHD1/OBOHYJpHie+HXpu6bSkEGieFMYk6VuutaiRfA==", + "dependencies": { + "css-mediaquery": "^0.1.2" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3141,6 +3167,14 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -3332,6 +3366,16 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -3397,8 +3441,7 @@ "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/react-redux": { "version": "9.1.2", @@ -3431,6 +3474,23 @@ "node": ">=0.10.0" } }, + "node_modules/react-responsive": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-10.0.0.tgz", + "integrity": "sha512-N6/UiRLGQyGUqrarhBZmrSmHi2FXSD++N5VbSKsBBvWfG0ZV7asvUBluSv5lSzdMyEVjzZ6Y8DL4OHABiztDOg==", + "dependencies": { + "hyphenate-style-name": "^1.0.0", + "matchmediaquery": "^0.4.2", + "prop-types": "^15.6.1", + "shallow-equal": "^3.1.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/react-router": { "version": "6.24.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.24.0.tgz", @@ -3598,6 +3658,11 @@ "node": ">=10" } }, + "node_modules/shallow-equal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-3.1.0.tgz", + "integrity": "sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg==" + }, "node_modules/shallowequal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", diff --git a/package.json b/package.json index 312c4cc..c2b82f8 100644 --- a/package.json +++ b/package.json @@ -12,12 +12,14 @@ }, "dependencies": { "@reduxjs/toolkit": "^2.2.6", + "@types/react-responsive": "^8.0.8", "axios": "^1.7.2", "js-cookie": "^3.0.5", "react": "^18.3.1", "react-dom": "^18.3.1", "react-ga4": "^2.1.0", "react-redux": "^9.1.2", + "react-responsive": "^10.0.0", "react-router-dom": "^6.24.0", "redux-persist": "^6.0.0", "styled-components": "^6.1.11", diff --git a/src/apis/utils/instance.ts b/src/apis/utils/instance.ts index 21b2b88..b07fd24 100644 --- a/src/apis/utils/instance.ts +++ b/src/apis/utils/instance.ts @@ -1,4 +1,4 @@ -import {setModalName} from '@/store/modalSlice'; +import {setModalName} from '@/store/modules/modalSlice'; import {setType} from '@/store/modules/errorSlice'; import {store} from '@/store/store'; import axios, {AxiosError, AxiosResponse} from 'axios'; diff --git a/src/assets/img/close-sidebar.svg b/src/assets/img/close-sidebar.svg new file mode 100644 index 0000000..d00c116 --- /dev/null +++ b/src/assets/img/close-sidebar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/CourseRegister/RegisterFilters.tsx b/src/components/CourseRegister/RegisterFilters.tsx index 33a4d78..f411590 100644 --- a/src/components/CourseRegister/RegisterFilters.tsx +++ b/src/components/CourseRegister/RegisterFilters.tsx @@ -5,7 +5,7 @@ import FilterButton from '../common/FilterButton'; import FilterInput from '../common/FilterInput'; import SelectBox from '../common/SelectBox'; import {term, searchOptions} from '@assets/data/filter'; -import {FilterBox, FilterContainer, FilterWrap} from '../LectureList/Filters'; +import {FilterBox, FilterContainer, FilterWrap} from '@/styles/FilterLayout'; interface FiltersProps { isRegistrationStarted: boolean; @@ -139,23 +139,27 @@ function RegisterFilters({onSearch, isRegistrationStarted}: FiltersProps) { const RegisterFilterContainer = styled(FilterContainer)` display: flex; align-items: flex-end; + gap: 0.7rem 0; + + @media ${props => props.theme.device.mobile} { + flex-wrap: wrap; + } `; const SearchBox = styled.div` display: flex; - gap: 0 3rem; + flex-wrap: wrap; + gap: 0.7rem 3rem; `; const SearchWrap = styled(FilterWrap)` > div { margin-right: 0.7rem; } - display: flex; align-items: center; `; -const CuriNoWrap = styled.div` - display: flex; +const CuriNoWrap = styled(FilterBox)` align-items: center; `; diff --git a/src/components/CourseRegister/index.tsx b/src/components/CourseRegister/index.tsx index d0d7f75..a6f7262 100644 --- a/src/components/CourseRegister/index.tsx +++ b/src/components/CourseRegister/index.tsx @@ -5,12 +5,16 @@ import Table from '../common/Table'; import {TableTitle, TableTitleWrap} from '../LectureList'; import RegisteredList from './RegisteredList'; import {useDispatch} from 'react-redux'; -import {setCourseName, setModalName, setScheduleId} from '@store/modalSlice.ts'; +import { + setCourseName, + setModalName, + setScheduleId, +} from '@/store/modules/modalSlice'; import StartButton from '@components/CourseRegister/StartButton.tsx'; import {getCourseList, getRegisterdList, getWishlist} from '@/apis/api/course'; import {useAppSelector} from '@/store/hooks'; import {openModalHandler} from '../common/Modal/handlers/handler'; -import {setEndCount} from '@store/courseRegisteredSlice.ts'; +import {setEndCount} from '@/store/modules/courseRegisteredSlice'; const colData = [ {name: 'action', value: '신청', initialWidth: 30, enableFilters: false}, @@ -32,8 +36,10 @@ function CourseRegister() { const [list, setList] = useState([]); const [registeredList, setRegisteredList] = useState([]); const [currentFilter, setCurrentFilter] = useState({}); - const [currentSearchOption, setCurrentSearchOption] = useState('관심과목'); - const [isRegistrationStarted, setIsRegistrationStarted] = useState(false); + const [currentSearchOption, setCurrentSearchOption] = + useState('관심과목'); + const [isRegistrationStarted, setIsRegistrationStarted] = + useState(false); const [isFirstSearch, setIsFirstSearch] = useState(true); const dispatch = useDispatch(); @@ -56,7 +62,11 @@ function CourseRegister() { setList(searchResult); }, [currentFilter, currentSearchOption, studentId]); - const handleSearch = async (newList: CourseTypes[], filter: CourseTypes, searchOption: string) => { + const handleSearch = async ( + newList: CourseTypes[], + filter: CourseTypes, + searchOption: string, + ) => { if (isRegistrationStarted && isFirstSearch) { openModalHandler(dispatch, 'waiting'); setIsFirstSearch(false); @@ -79,7 +89,6 @@ function CourseRegister() { console.log('35초 지남'); dispatch(setEndCount(true)); }, 35000); - }; const handleAction = async ( @@ -96,10 +105,11 @@ function CourseRegister() { return ( <> - + - 수강대상교과목 @@ -110,10 +120,7 @@ function CourseRegister() { height='35rem' onAction={handleAction} /> - + ); } diff --git a/src/components/Header/TopMenu.tsx b/src/components/Header/TopMenu.tsx index ed85c77..c7e970c 100644 --- a/src/components/Header/TopMenu.tsx +++ b/src/components/Header/TopMenu.tsx @@ -4,7 +4,7 @@ import {useDispatch} from 'react-redux'; import Cookies from 'js-cookie'; import {baseAPI} from '@/apis/utils/instance'; import {useAppSelector} from '@/store/hooks'; -import {clearUserInfo} from '@/store/userSlice'; +import {clearUserInfo} from '@/store/modules/userSlice'; import {deleteAll} from '@/store/modules/tabSlice'; import logout from '@assets/img/logout.png'; import Clock from './Clock'; @@ -41,7 +41,7 @@ const LogoutBtn = styled.button` background-size: 1.8rem; width: 1.8rem; height: 1.8rem; - margin-right: 1rem; + margin-right: 1rem; &:hover { filter: brightness(20%); } diff --git a/src/components/Header/TopNav.tsx b/src/components/Header/TopNav.tsx index 56fca4c..a251110 100644 --- a/src/components/Header/TopNav.tsx +++ b/src/components/Header/TopNav.tsx @@ -12,6 +12,10 @@ const TopNavContatiner = styled.div` display: flex; flex-shrink: 0; align-items: center; + + @media ${props => props.theme.device.mobile} { + display: none; + } `; const TopNavWrap = styled.div` diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx index f0ce012..adf1f40 100644 --- a/src/components/Header/index.tsx +++ b/src/components/Header/index.tsx @@ -8,9 +8,9 @@ function Header() { + - @@ -18,30 +18,29 @@ function Header() { } const HeaderContainer = styled.div` - border-top: 0.5rem solid ${props => props.theme.colors.primary}; - max-width: 100%; - height: 7rem; - display: flex; - align-items: center; - justify-content: space-between; + border-top: 0.5rem solid ${props => props.theme.colors.primary}; + max-width: 100%; + height: 7rem; + display: flex; + align-items: center; + justify-content: space-between; `; const LogoBox = styled.div` - display: flex; - justify-content: center; - align-items: center; - width: 23rem; - > img { - height: 6rem; - display: block; - } + display: flex; + justify-content: center; + align-items: center; + padding: 0 1rem; + > img { + height: 6rem; + display: block; + } `; const HeaderBox = styled.div` - display: flex; - flex-grow: 1; - height: 100%; - justify-content: space-between; + display: flex; + height: 100%; + justify-content: space-between; `; export default Header; diff --git a/src/components/LectureList/Filters.tsx b/src/components/LectureList/Filters.tsx index 2ad888c..ccae9dd 100644 --- a/src/components/LectureList/Filters.tsx +++ b/src/components/LectureList/Filters.tsx @@ -5,6 +5,12 @@ import FilterInput from '@components/common/FilterInput'; import SelectBox from '@components/common/SelectBox'; import {completion, major, optional, term} from '@assets/data/filter'; import {CourseTypes} from '@/assets/types/tableType'; +import { + FilterArea, + FilterBox, + FilterContainer, + FilterWrap, +} from '@/styles/FilterLayout'; interface FiltersProps { onSearch: (newList: CourseTypes[]) => Promise; @@ -113,35 +119,6 @@ function Filters({onSearch}: FiltersProps) { ); } -export const FilterContainer = styled.div` - border: 0.1rem solid #714656; - border-radius: 2px; - padding: 0.5rem 1.5rem; - margin-bottom: 2rem; -`; - -const FilterArea = styled.div` - display: flex; - align-items: flex-end; - margin-bottom: 1rem; -`; - -export const FilterBox = styled.div` - display: flex; - flex-wrap: wrap; - gap: 0.7rem 3rem; -`; - -export const FilterWrap = styled.div` - ${props => props.theme.texts.tableTitle}; - > span { - display: inline-block; - margin-right: 1rem; - text-align: right; - min-width: 4.5rem; - } -`; - const WarningWrap = styled.div` ${props => props.theme.texts.warning}; color: #c30e2e; diff --git a/src/components/LoginForm/index.tsx b/src/components/LoginForm/index.tsx index d8f98f6..6d22923 100644 --- a/src/components/LoginForm/index.tsx +++ b/src/components/LoginForm/index.tsx @@ -4,7 +4,7 @@ import FormInput from './FormInput'; import {login} from '@/apis/api/auth'; import {useDispatch} from 'react-redux'; import {useNavigate} from 'react-router-dom'; -import {setUserInfo} from '@/store/userSlice'; +import {setUserInfo} from '@/store/modules/userSlice'; import {baseAPI} from '@/apis/utils/instance'; import Cookies from 'js-cookie'; import {generateRandomStudentId} from '@/utils/randomUtils.ts'; @@ -107,7 +107,6 @@ function LoginForm({isTermsCheck}: {isTermsCheck: boolean}) { - {/*아이디 찾기 | 비밀번호 찾기*/} {error && {error}} 로그인 @@ -117,7 +116,9 @@ function LoginForm({isTermsCheck}: {isTermsCheck: boolean}) { } const FormContainer = styled.div` - padding: 1.5rem 4rem; + display: flex; + flex-direction: column; + align-items: center; background-color: ${props => props.theme.colors.white}; border-radius: 0.3rem; margin-bottom: 2rem; @@ -162,7 +163,7 @@ const LabelWrap = styled.div` `; const LoginBtnWrap = styled.button` - width: 100%; + width: 90%; height: 5rem; background-color: ${props => props.theme.colors.secondary}; border: none; diff --git a/src/components/Menubar/BarTitle.tsx b/src/components/Menubar/BarTitle.tsx index 492ebe5..0c96b06 100644 --- a/src/components/Menubar/BarTitle.tsx +++ b/src/components/Menubar/BarTitle.tsx @@ -1,12 +1,17 @@ import styled from 'styled-components'; +import Close from '@assets/img/close-sidebar.svg?react'; -function BarTitle() { +interface TitleProps { + setOpen: React.Dispatch>; +} + +function BarTitle({setOpen}: TitleProps) { return ( 학부생수강시스템 - - - + setOpen(!prev)}> + + ); } @@ -16,7 +21,8 @@ const BarTitleContainer = styled.div` height: 4rem; display: flex; align-items: center; - justify-content: space-around; + justify-content: space-between; + padding: 0 1.5rem; `; const BarTitleWrap = styled.div` @@ -25,15 +31,11 @@ const BarTitleWrap = styled.div` color: ${props => props.theme.colors.white}; `; -const IconBox = styled.div` - display: flex; - align-items: center; - column-gap: 0.5rem; -`; - const CloseBtn = styled.button` display: flex; align-items: center; + height: 100%; + width: 1.5rem; `; export default BarTitle; diff --git a/src/components/Menubar/index.tsx b/src/components/Menubar/index.tsx index fb482b1..82c7cad 100644 --- a/src/components/Menubar/index.tsx +++ b/src/components/Menubar/index.tsx @@ -1,18 +1,50 @@ import styled from 'styled-components'; import BarTitle from './BarTitle'; import Menu from './Menu'; +import Open from '@assets/img/close-sidebar.svg?react'; -function Menubar() { +interface BarProps { + open: boolean; + setOpen: React.Dispatch>; +} + +function Menubar({open, setOpen}: BarProps) { return ( - - - + + {open ? ( + + + + + ) : ( + setOpen(true)}> + + + )} ); } -const BarContainer = styled.div` - width: 23rem; +const BarContainer = styled.div<{$open: boolean}>` + min-width: ${props => (props.$open ? '23rem' : '2rem')}; + background-color: ${props => props.theme.colors.white}; + + @media ${props => props.theme.device.mobile} { + position: absolute; + z-index: 999; + } +`; + +const BarBox = styled.div``; + +const OpenBtnWrap = styled.button` + background-color: ${props => props.theme.colors.secondary}; + width: 100%; + height: 4rem; +`; + +const OpenBtn = styled(Open)` + transform: rotate(180deg); `; export default Menubar; diff --git a/src/components/Wishlist/Filters.tsx b/src/components/Wishlist/Filters.tsx index 0be5737..fd5f0f3 100644 --- a/src/components/Wishlist/Filters.tsx +++ b/src/components/Wishlist/Filters.tsx @@ -7,6 +7,12 @@ import {CourseTypes} from '@/assets/types/tableType'; import {openModalHandler} from '../common/Modal/handlers/handler'; import {useDispatch} from 'react-redux'; import {setField, setType} from '@/store/modules/errorSlice'; +import { + FilterArea, + FilterBox, + FilterContainer, + FilterWrap, +} from '@/styles/FilterLayout'; const searchOptions = [ {id: 0, value: '학수번호 검색'}, @@ -148,7 +154,7 @@ function Filters({setSearchResults}: FiltersProps) { - + 조직분류 {}} /> - - + + 년도/학기 {}} /> - + - + 검색구분 handleInputChange('searchType', value || '')} /> - + {renderSearchForm()} 검색 @@ -183,31 +189,12 @@ function Filters({setSearchResults}: FiltersProps) { ); } -const FilterContainer = styled.div` - border: 0.1rem solid #714656; - border-radius: 2px; - padding: 0.5rem 1.5rem; - margin-bottom: 2rem; -`; - -const FilterBox = styled.div` - display: flex; - flex-wrap: wrap; - gap: 0.7rem 3rem; -`; - -const FilterArea = styled.div` - display: flex; - align-items: flex-end; - margin-bottom: 1rem; -`; - const FilterBreak = styled.div` flex-basis: 100%; height: 0; `; -const FilterWrap = styled.div` +const WishFilterWrap = styled(FilterWrap)` ${props => props.theme.texts.tableTitle}; display: flex; align-items: center; diff --git a/src/components/common/FilterInput.tsx b/src/components/common/FilterInput.tsx index 9044513..5cf0917 100644 --- a/src/components/common/FilterInput.tsx +++ b/src/components/common/FilterInput.tsx @@ -1,4 +1,4 @@ -import styled, { css } from 'styled-components'; +import styled, {css} from 'styled-components'; interface InputInterface { disabled?: boolean; @@ -6,7 +6,7 @@ interface InputInterface { onChange: (value: string) => void; } -function FilterInput({ disabled, sizes, onChange }: InputInterface) { +function FilterInput({disabled, sizes, onChange}: InputInterface) { return ( <> ` +const InputWrap = styled.input<{sizes: string}>` ${props => props.theme.texts.content}; ${props => props.sizes === 's' && css` - width: 14rem; + max-width: 14rem; + + @media ${props => props.theme.device.mobile} { + max-width: 14rem; + } `}; ${props => props.sizes === 'm' && css` - width: 19.5rem; + max-width: 19.5rem; + + @media ${props => props.theme.device.mobile} { + max-width: 19.5rem; + } `}; - ${props => - props.sizes === 'l' && - css` - width: 25rem; - `}; ${props => props.sizes === 'l' && css` - width: 38.5rem; - `}; - ${props => - props.sizes === 'xl' && - css` - width: 48.5rem; + max-width: 25rem; + + @media ${props => props.theme.device.mobile} { + max-width: 25rem; + } `}; height: 2.4rem; border: 1px solid ${props => props.theme.colors.neutral5}; padding-left: 0.8rem; - - &:disabled { - background: ${props => props.theme.colors.neutral5}; - } + + &:disabled { + background: ${props => props.theme.colors.neutral5}; + } `; export default FilterInput; diff --git a/src/components/common/Modal/WaitingModal.tsx b/src/components/common/Modal/WaitingModal.tsx index e608ffb..305fc20 100644 --- a/src/components/common/Modal/WaitingModal.tsx +++ b/src/components/common/Modal/WaitingModal.tsx @@ -3,7 +3,7 @@ import logo from '@/assets/img/logo.webp'; import close from '@/assets/img/close-line.png'; import {useEffect, useState} from 'react'; import {useDispatch} from 'react-redux'; -import {clearModalInfo} from '@store/modalSlice.ts'; +import {clearModalInfo} from '@/store/modules/modalSlice'; import {getRandomInt} from '@/utils/randomUtils.ts'; function WaitingModal() { diff --git a/src/components/common/Modal/handlers/handler.tsx b/src/components/common/Modal/handlers/handler.tsx index 03724f4..822671d 100644 --- a/src/components/common/Modal/handlers/handler.tsx +++ b/src/components/common/Modal/handlers/handler.tsx @@ -1,5 +1,5 @@ import {Dispatch} from 'redux'; -import {clearModalInfo, setModalName} from '@store/modalSlice'; +import {clearModalInfo, setModalName} from '@/store/modules/modalSlice'; export const closeHandler = (dispatch: Dispatch) => { dispatch(clearModalInfo()); diff --git a/src/components/common/SelectBox.tsx b/src/components/common/SelectBox.tsx index c07384c..ed8842a 100644 --- a/src/components/common/SelectBox.tsx +++ b/src/components/common/SelectBox.tsx @@ -69,7 +69,7 @@ function SelectBox({options, disabled = false, sizes, onSelect}: SelectProps) { {open && ( - + {filtered.map(option => ( ` props.sizes === 's' && css` width: 15rem; + + @media ${props => props.theme.device.mobile} { + max-width: 15rem; + } `}; ${props => props.sizes === 'm' && css` width: 20.5rem; + + @media ${props => props.theme.device.mobile} { + max-width: 20.5rem; + } `}; ${props => props.sizes === 'xl' && css` width: 50rem; + + @media ${props => props.theme.device.mobile} { + max-width: 50rem; + } `}; + min-width: 7rem; height: 2.4rem; position: relative; display: inline-block; @@ -144,7 +157,7 @@ const ArrowWrap = styled.img` `; const SelectWrap = styled.ul` - width: inherit; + min-width: 15rem; max-height: 12rem; position: absolute; top: 100%; diff --git a/src/components/common/Table/index.tsx b/src/components/common/Table/index.tsx index eae7404..805103d 100644 --- a/src/components/common/Table/index.tsx +++ b/src/components/common/Table/index.tsx @@ -237,6 +237,10 @@ const NoresultWrap = styled.tr<{width: number; height: string}>` display: flex; align-items: center; justify-content: center; + + @media ${props => props.theme.device.mobile} { + max-width: auto; + } `; const Noresult = styled.td` diff --git a/src/pages/index/Home.tsx b/src/pages/index/Home.tsx index 5169fe3..b5e3c62 100644 --- a/src/pages/index/Home.tsx +++ b/src/pages/index/Home.tsx @@ -1,3 +1,4 @@ +import {useEffect, useState} from 'react'; import styled from 'styled-components'; import Menubar from '@components/Menubar'; import Header from '@components/Header'; @@ -12,12 +13,14 @@ import EnrollmentInfoModal from '@components/common/Modal/EnrollmentInfoModal.ts import LoadingModal from '@components/common/Modal/LoadingModal.tsx'; import WaitingModal from '@components/common/Modal/WaitingModal.tsx'; import {useDispatch} from 'react-redux'; -import {clearModalInfo} from '@store/modalSlice.ts'; +import {clearModalInfo} from '@/store/modules/modalSlice'; import ErrorModal from '@components/common/Modal/ErrorModal.tsx'; -import {useEffect} from 'react'; +import {useMediaQuery} from 'react-responsive'; function Home() { + const isPc = useMediaQuery({query: '(min-width: 1024px)'}); const {tab, focused} = useAppSelector(state => state.tabs); + const [barOpen, setBarOpen] = useState(isPc); const {modalName, scheduleId, courseName} = useAppSelector( state => state.modalInfo, @@ -82,8 +85,8 @@ function Home() { {renderModal()}
- -
+ +

{focusedTabName}

@@ -101,10 +104,18 @@ const Container = styled.div` const Box = styled.div` display: flex; + max-width: 100vw; `; -const Main = styled.div` - width: calc(100% - 23rem); +const Main = styled.div<{$isOpen: boolean}>` + width: ${props => + props.$isOpen ? 'calc(100% - 23rem)' : 'calc(100% - 2rem)'}; + + @media ${props => props.theme.device.mobile} { + width: calc(100% - 2rem); + position: absolute; + left: 2rem; + } `; const Article = styled.div` diff --git a/src/pages/index/Login.tsx b/src/pages/index/Login.tsx index a5f5f6f..05b939e 100644 --- a/src/pages/index/Login.tsx +++ b/src/pages/index/Login.tsx @@ -53,8 +53,14 @@ function Login() { 1. 서비스 목적 - 본 서비스는 실제 수강신청 사이트가 아니며, 학습 목적으로 제공되는 모의 수강신청 시스템입니다. 본 서비스에서 사용하는 - 학번은 실제 학번이 아닌, 11자리 이상의 임의로 생성된 학번입니다. + + 본 서비스는 실제 수강신청 사이트가 아니며 + , 학습 목적으로 제공되는 모의 수강신청 시스템입니다. 본 + 서비스에서 사용하는 학번은{' '} + + 실제 학번이 아닌, 11자리 이상의 임의로 생성된 학번 + + 입니다. @@ -62,64 +68,123 @@ function Login() { 2. 개인정보의 수집 및 이용 - 수집하는 개인정보 항목: 본 서비스에서는 사용자가 입력한 임의의 학번, 비밀번호, 관심과목, 수강신청 목록을 - 수집합니다. - 개인정보 수집 목적: 수집된 정보는 사용자가 저장한 과목 목록을 불러오기 위한 용도로만 사용됩니다. 그 외 다른 - 목적으로는 절대 사용되지 않습니다. - 개인정보의 보유 및 이용 기간: 수집된 정보는 매일 자정에 자동으로 삭제되며, 추가적인 보관 기간은 없습니다. - 수집된 정보의 저장 위치: 수집된 정보는 안전한 서버에 저장되며, 외부로 유출되지 않도록 보호됩니다. + + 수집하는 개인정보 항목: 본 서비스에서는 사용자가 입력한 임의의{' '} + + 학번, 비밀번호, 관심과목, 수강신청 목록을 수집 + + 합니다. + + + 개인정보 수집 목적: 수집된 정보는{' '} + + 사용자가 저장한 과목 목록을 불러오기 위한 용도 + + 로만 사용됩니다.{' '} + + 그 외 다른 목적으로는 절대 사용되지 않습니다. + + + + 개인정보의 보유 및 이용 기간: 수집된 정보는{' '} + 매일 자정에 자동으로 삭제되며, 추가적인 + 보관 기간은 없습니다. + + + 수집된 정보의 저장 위치: 수집된 정보는{' '} + 안전한 서버에 저장되며,{' '} + 외부로 유출되지 않도록 보호됩니다. + 3. 비밀번호 및 보안 - 본 서비스에 입력된 비밀번호는 암호화 알고리즘을 사용하여 안전하게 보호됩니다. 비밀번호는 복호화가 불가능하며, 실제 - 사용하는 비밀번호가 아닌 임의의 비밀번호를 사용하는 것을 권장합니다. + + 본 서비스에 입력된 비밀번호는{' '} + 암호화 알고리즘을 사용하여 안전하게 보호 + 됩니다. 비밀번호는 복호화가 불가능하며,{' '} + + 실제 사용하는 비밀번호가 아닌 임의의 비밀번호를 사용하는 것을 + 권장 + + 합니다. + 4. 개인정보의 파기 - 수집된 개인정보는 매일 자정에 자동으로 서버에서 삭제됩니다. 만약 자정 전에 정보를 삭제하고 싶으신 경우, - navigate('/delete')}>https://tutorial-sejong.com/delete 페이지에서 로그인 시 입력한 학번을 입력하여 직접 삭제할 수 있습니다. - 학번을 기억하지 못할 경우, tutorialsejong@gmail.com으로 메일을 보내주시면 관심과목 목록 및 로그인 시간을 기준으로 삭제를 도와드리겠습니다. 만약 확인이 - 불가능한 경우, 모든 정보를 일괄적으로 삭제 처리하겠습니다. + + 수집된 개인정보는{' '} + 매일 자정에 자동으로 서버에서 삭제됩니다. + 만약 자정 전에 정보를 삭제하고 싶으신 경우, + navigate('/delete')}> + https://tutorial-sejong.com/delete + {' '} + 페이지에서 로그인 시 입력한{' '} + 학번을 입력하여 직접 삭제할 수 있습니다. + + + 학번을 기억하지 못할 경우,{' '} + tutorialsejong@gmail.com으로 메일을 + 보내주시면 관심과목 목록 및 로그인 시간을 기준으로 삭제를 + 도와드리겠습니다. 만약 확인이 불가능한 경우,{' '} + 모든 정보를 일괄적으로 삭제 처리 + 하겠습니다. + 5. 개인정보의 파기 - 사용자는 언제든지 본 서비스에 제공된 개인정보의 삭제를 요청할 수 있으며, 삭제 요청은 위의 방법을 통해 처리됩니다. - 개인정보와 관련된 문의는 tutorialsejong@gmail.com으로 문의하시면 신속히 대응해드리겠습니다. + + 사용자는 언제든지 본 서비스에 제공된 개인정보의 삭제를 요청할 수 + 있으며, 삭제 요청은 위의 방법을 통해 처리됩니다. + + + 개인정보와 관련된 문의는{' '} + tutorialsejong@gmail.com으로 문의하시면 + 신속히 대응해드리겠습니다. + 6. 개인정보의 보호 - 본 서비스는 개인정보 보호법과 정보통신망법에 따라 사용자의 개인정보를 보호하기 위해 최선을 다하고 있습니다. 수집된 개인정보는 불법적인 접근, 유출, 사용을 방지하기 위해 방화벽, 암호화 통신, 접근 통제 등 적절한 보안 조치를 취하고 있습니다. + + 본 서비스는 개인정보 보호법과 정보통신망법에 따라 사용자의 + 개인정보를 보호하기 위해 최선을 다하고 있습니다. 수집된 + 개인정보는 불법적인 접근, 유출, 사용을 방지하기 위해 방화벽, + 암호화 통신, 접근 통제 등 적절한 보안 조치를 취하고 있습니다. + 7. 개인정보 처리방침 변경 - 본 서비스의 개인정보 처리방침은 법률 개정이나 서비스 변경에 따라 수정될 수 있으며, 수정된 내용은 이 페이지에서 확인 가능합니다. + + 본 서비스의 개인정보 처리방침은 법률 개정이나 서비스 변경에 따라 + 수정될 수 있으며, 수정된 내용은 이 페이지에서 확인 가능합니다. + - @@ -129,147 +194,133 @@ function Login() { } const Container = styled.div` - background: url(${Bg}) 50% 50% no-repeat; - background-size: cover; - height: 700px; - background-color: #fafafa; - width: 100%; + background: url(${Bg}) 50% 50% no-repeat; + background-size: cover; + height: 700px; + background-color: #fafafa; + width: 100%; `; const Box = styled.div` - display: flex; - flex-direction: column; - align-items: center; + display: flex; + flex-direction: column; + align-items: center; `; const LogoWrap = styled.div` - margin: 3rem 0; + margin: 3rem 0; - > img { - width: 150px; - } + > img { + width: 150px; + } `; const TitleWrap = styled.div` - color: ${props => props.theme.colors.white}; - text-align: center; - margin-bottom: 2.5rem; + color: ${props => props.theme.colors.white}; + text-align: center; + padding: 0 1rem; + margin-bottom: 2.5rem; - > p { - line-height: 2.5rem; - font-weight: 600; - font-size: 1.35rem; - } + > p { + line-height: 2.5rem; + font-weight: 600; + font-size: 1.35rem; + } - > p > em { - color: #ffea9b; - } + > p > em { + color: #ffea9b; + } `; const Title = styled.h1` - font-size: 3.5rem; - font-weight: 700; - margin-bottom: 2rem; + font-size: 3.5rem; + font-weight: 700; + margin-bottom: 2rem; `; const SubTitle = styled.h2` - font-size: 2rem; - font-weight: 700; - margin-bottom: 2rem; + font-size: 2rem; + font-weight: 700; + margin-bottom: 2rem; `; const FormWrap = styled.div` - margin-bottom: 2.5rem; -`; + width: 46rem; + margin-bottom: 2.5rem; -// const WarningWrap = styled.p` -// ${props => props.theme.texts.loginContent}; -// margin-bottom: 2.5rem; -// `; - -const FaqWrap = styled.div` - ${props => props.theme.texts.loginContent}; - - > img { - width: 30px; - cursor: pointer; - display: block; - text-align: center; - margin: 20px auto; - } + @media ${props => props.theme.device.mobile} { + width: 100%; + } `; -/*const FooterWrap = styled.div` +const FaqWrap = styled.div` ${props => props.theme.texts.loginContent}; - letter-spacing: 0; - > em { - color: ${props => props.theme.colors.black}; - } - > p { - color: gray; + + > img { + width: 30px; + cursor: pointer; + display: block; text-align: center; - font-weight: 500; - font-size: 1.2rem; - margin: 0.7rem 0 3rem 0; + margin: 20px auto; } -`;*/ +`; const TermsContainer = styled.div` - max-width: 890px; - margin: 0 auto 20px; - padding: 20px; - background-color: #f9f9f9; - border-radius: 10px; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + max-width: 890px; + margin: 0 auto 20px; + padding: 20px; + background-color: #f9f9f9; + border-radius: 10px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); `; const CheckboxWrap = styled.div` - display: flex; - justify-content: center; - align-items: center; - margin-left: -0.5rem; - margin-top: 1rem; + display: flex; + justify-content: center; + align-items: center; + margin-left: -0.5rem; + margin-top: 1rem; `; const TermsTitle = styled.h1` - font-size: 24px; - font-weight: bold; - color: #333; - margin-bottom: 20px; + font-size: 24px; + font-weight: bold; + color: #333; + margin-bottom: 20px; `; const List = styled.ul` - list-style: none; - padding: 0; - margin: 0; + list-style: none; + padding: 0; + margin: 0; `; const ListTitle = styled.li` - font-size: 18px; - font-weight: bold; - margin-top: 15px; - margin-bottom: 15px; + font-size: 18px; + font-weight: bold; + margin-top: 15px; + margin-bottom: 15px; `; const ListItem = styled.li` - margin-bottom: 5px; - font-size: 15px; - color: #555; - line-height: 2.5rem; + margin-bottom: 5px; + font-size: 15px; + color: #555; + line-height: 2.5rem; - &::before { - content: '•'; - color: #007bff; - font-weight: bold; - display: inline-block; - width: 1em; - margin-left: 0.5em; - } + &::before { + content: '•'; + color: #007bff; + font-weight: bold; + display: inline-block; + width: 1em; + margin-left: 0.5em; + } `; const Highlight = styled.span` - cursor: pointer; - font-weight: bold; - color: #007bff; + cursor: pointer; + font-weight: bold; + color: #007bff; `; export default Login; diff --git a/src/store/courseRegisteredSlice.ts b/src/store/modules/courseRegisteredSlice.ts similarity index 100% rename from src/store/courseRegisteredSlice.ts rename to src/store/modules/courseRegisteredSlice.ts diff --git a/src/store/modalSlice.ts b/src/store/modules/modalSlice.ts similarity index 100% rename from src/store/modalSlice.ts rename to src/store/modules/modalSlice.ts diff --git a/src/store/userSlice.ts b/src/store/modules/userSlice.ts similarity index 100% rename from src/store/userSlice.ts rename to src/store/modules/userSlice.ts diff --git a/src/store/store.ts b/src/store/store.ts index 6564be8..6f05a2e 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -2,9 +2,9 @@ import {combineReducers, configureStore} from '@reduxjs/toolkit'; import {persistReducer} from 'redux-persist'; import storage from 'redux-persist/lib/storage'; -import userSlice from '@store/userSlice.ts'; -import modalSlice from '@store/modalSlice.ts'; -import courseRegisteredSlice from '@store/courseRegisteredSlice.ts'; +import userSlice from '@/store/modules/userSlice'; +import modalSlice from '@/store/modules/modalSlice'; +import courseRegisteredSlice from '@/store/modules/courseRegisteredSlice'; import tabSlice from './modules/tabSlice'; import errorSlice from './modules/errorSlice'; diff --git a/src/styles/FilterLayout.tsx b/src/styles/FilterLayout.tsx new file mode 100644 index 0000000..22e202b --- /dev/null +++ b/src/styles/FilterLayout.tsx @@ -0,0 +1,39 @@ +import styled from 'styled-components'; + +export const FilterContainer = styled.div` + border: 0.1rem solid #714656; + border-radius: 2px; + padding: 0.5rem 1.5rem; + margin-bottom: 2rem; +`; + +export const FilterArea = styled.div` + display: flex; + align-items: flex-end; + margin-bottom: 1rem; + gap: 0.7rem 3rem; + + @media ${props => props.theme.device.mobile} { + flex-wrap: wrap; + } +`; + +export const FilterBox = styled.div` + display: flex; + flex-wrap: wrap; + gap: 0.7rem 3rem; +`; + +export const FilterWrap = styled.div` + ${props => props.theme.texts.tableTitle}; + display: flex; + flex-wrap: wrap; + gap: 0.7rem 0; + align-items: center; + + > span { + margin-right: 1rem; + text-align: right; + min-width: 4.5rem; + } +`; diff --git a/src/styles/theme/Theme.ts b/src/styles/theme/Theme.ts index 0f8db80..213d6b7 100644 --- a/src/styles/theme/Theme.ts +++ b/src/styles/theme/Theme.ts @@ -62,10 +62,18 @@ const texts = { }, }; +const device = { + mobile: 'screen and (max-width: 767px)', + tablet: 'screen and (min-width: 768px) and (max-width: 1023px)', + pc: 'screen and (min-width: 1024px)', +}; + export type ColorsType = typeof colors; export type TextsType = typeof texts; +export type DeviceType = typeof device; export const theme: DefaultTheme = { colors, texts, + device, }; diff --git a/src/styles/theme/style.d.ts b/src/styles/theme/style.d.ts index 8dffb0f..389a5b8 100644 --- a/src/styles/theme/style.d.ts +++ b/src/styles/theme/style.d.ts @@ -1,9 +1,10 @@ import 'styled-components'; -import { ColorsType, TextsType } from './Theme'; +import {ColorsType, DeviceType, TextsType} from './Theme'; declare module 'styled-components' { export interface DefaultTheme { colors: ColorsType; texts: TextsType; + device: DeviceType; } -} \ No newline at end of file +}