From 3f5cca123a8c51e8db9c8db785ef777cb7f315a5 Mon Sep 17 00:00:00 2001 From: Jun99uu_Lee Date: Sat, 14 Oct 2023 22:50:30 +0900 Subject: [PATCH 01/17] feat: add frame component (for mobile) --- client/src/components/Layouts/Frame/index.tsx | 45 +++++++++++++++++++ client/src/components/Layouts/index.tsx | 1 + client/src/hooks/index.tsx | 1 + client/src/hooks/useVh.tsx | 23 ++++++++++ 4 files changed, 70 insertions(+) create mode 100644 client/src/components/Layouts/Frame/index.tsx create mode 100644 client/src/hooks/useVh.tsx diff --git a/client/src/components/Layouts/Frame/index.tsx b/client/src/components/Layouts/Frame/index.tsx new file mode 100644 index 0000000..fc0dd81 --- /dev/null +++ b/client/src/components/Layouts/Frame/index.tsx @@ -0,0 +1,45 @@ +import { useVh } from '@/hooks'; +import { mq } from '@/styles/breakpoints'; +import { css } from '@emotion/react'; +import { type ComponentProps } from 'react'; + +interface FrameProps extends ComponentProps<'div'> { + children: React.ReactNode; +} + +/** 모바일 환경 최적화 프레임 */ +const Frame = ({ children, ...props }: FrameProps) => { + const { vh } = useVh(); + + const backgroundStyle = css` + max-width: 425px; + width: 100vw; + min-height: calc(${vh}px * 100); + position: relative; + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: flex-start; + overflow: hidden; + + ${mq[3]} { + min-height: 100vh; + } + `; + + return ( +
+ {children} +
+ ); +}; + +export const frameStyle = css` + padding: 7rem 0rem; + + ${mq[4]} { + padding: 7rem 0rem; + } +`; + +export default Frame; diff --git a/client/src/components/Layouts/index.tsx b/client/src/components/Layouts/index.tsx index a8a73dc..f97fcf7 100644 --- a/client/src/components/Layouts/index.tsx +++ b/client/src/components/Layouts/index.tsx @@ -1,3 +1,4 @@ export { default as Navigator } from './Navigator'; export { default as Picker } from './Picker'; export { default as Title } from './Title'; +export { default as Frame } from './Frame'; diff --git a/client/src/hooks/index.tsx b/client/src/hooks/index.tsx index e69de29..d3d3ec1 100644 --- a/client/src/hooks/index.tsx +++ b/client/src/hooks/index.tsx @@ -0,0 +1 @@ +export { default as useVh } from './useVh'; diff --git a/client/src/hooks/useVh.tsx b/client/src/hooks/useVh.tsx new file mode 100644 index 0000000..eb55686 --- /dev/null +++ b/client/src/hooks/useVh.tsx @@ -0,0 +1,23 @@ +import { useEffect, useState } from 'react'; + +/** 리사이징 이벤트에 따라 변하는 vh 가져오는 훅 (불필요한 스크롤 생기는 이슈 방지) */ +const useVh = () => { + const [vh, setVh] = useState(0); + + const mobileScreenSize = () => { + let vh = window.innerHeight * 0.01; + setVh(vh); + }; + + useEffect(() => { + mobileScreenSize(); + window.addEventListener('resize', () => mobileScreenSize()); + return () => { + window.removeEventListener('resize', mobileScreenSize); + }; + }, []); + + return { vh }; +}; + +export default useVh; From e112ce9a4e9cc4c43bb290313041a1af7f067530 Mon Sep 17 00:00:00 2001 From: Jun99uu_Lee Date: Sat, 14 Oct 2023 23:11:54 +0900 Subject: [PATCH 02/17] feat: Add frame header --- client/src/atoms/headerTitleState.ts | 3 +++ client/src/atoms/index.tsx | 0 .../components/Layouts/Frame/FrameHeader.tsx | 22 +++++++++++++++++++ client/src/components/Layouts/Frame/index.tsx | 15 ++++++------- .../components/Layouts/Header/NavHeader.tsx | 2 +- .../components/Layouts/Header/TitleHeader.tsx | 6 ++--- client/src/hooks/index.tsx | 1 + client/src/hooks/useHeader.tsx | 14 ++++++++++++ client/src/pages/_app.tsx | 8 +++---- client/src/pages/_document.tsx | 3 +++ client/src/pages/index.tsx | 8 ++++--- 11 files changed, 62 insertions(+), 20 deletions(-) create mode 100644 client/src/atoms/headerTitleState.ts delete mode 100644 client/src/atoms/index.tsx create mode 100644 client/src/components/Layouts/Frame/FrameHeader.tsx create mode 100644 client/src/hooks/useHeader.tsx diff --git a/client/src/atoms/headerTitleState.ts b/client/src/atoms/headerTitleState.ts new file mode 100644 index 0000000..ccd9fa2 --- /dev/null +++ b/client/src/atoms/headerTitleState.ts @@ -0,0 +1,3 @@ +import { atom } from 'jotai'; + +export const headerTitleState = atom(''); diff --git a/client/src/atoms/index.tsx b/client/src/atoms/index.tsx deleted file mode 100644 index e69de29..0000000 diff --git a/client/src/components/Layouts/Frame/FrameHeader.tsx b/client/src/components/Layouts/Frame/FrameHeader.tsx new file mode 100644 index 0000000..d5f8780 --- /dev/null +++ b/client/src/components/Layouts/Frame/FrameHeader.tsx @@ -0,0 +1,22 @@ +import { useRouter } from 'next/router'; +import { NavHeader, TitleHeader } from '../Header'; +import { useHeader } from '@/hooks'; + +const FrameHeader = () => { + const router = useRouter(); + const { headerTitle } = useHeader(); + + switch (router.pathname) { + case '/login': + return <>; + case '/': + case '/template': + case '/mate': + case '/schedule': + return ; + default: + return ; + } +}; + +export default FrameHeader; diff --git a/client/src/components/Layouts/Frame/index.tsx b/client/src/components/Layouts/Frame/index.tsx index fc0dd81..6100683 100644 --- a/client/src/components/Layouts/Frame/index.tsx +++ b/client/src/components/Layouts/Frame/index.tsx @@ -2,6 +2,8 @@ import { useVh } from '@/hooks'; import { mq } from '@/styles/breakpoints'; import { css } from '@emotion/react'; import { type ComponentProps } from 'react'; +import FrameHeader from './FrameHeader'; +import styled from '@emotion/styled'; interface FrameProps extends ComponentProps<'div'> { children: React.ReactNode; @@ -12,7 +14,7 @@ const Frame = ({ children, ...props }: FrameProps) => { const { vh } = useVh(); const backgroundStyle = css` - max-width: 425px; + max-width: 50rem; width: 100vw; min-height: calc(${vh}px * 100); position: relative; @@ -29,17 +31,14 @@ const Frame = ({ children, ...props }: FrameProps) => { return (
- {children} + + {children}
); }; -export const frameStyle = css` - padding: 7rem 0rem; - - ${mq[4]} { - padding: 7rem 0rem; - } +const Container = styled.div` + width: 100%; `; export default Frame; diff --git a/client/src/components/Layouts/Header/NavHeader.tsx b/client/src/components/Layouts/Header/NavHeader.tsx index 3bd5de4..1e31271 100644 --- a/client/src/components/Layouts/Header/NavHeader.tsx +++ b/client/src/components/Layouts/Header/NavHeader.tsx @@ -26,7 +26,7 @@ const NavHeader = ({ title, ...props }: Props) => { const HeaderWrapper = styled.div` width: 100%; min-width: 32rem; - height: 4.4rem; + height: 5rem; position: sticky; top: 0px; diff --git a/client/src/components/Layouts/Header/TitleHeader.tsx b/client/src/components/Layouts/Header/TitleHeader.tsx index 78c0d9d..47692a5 100644 --- a/client/src/components/Layouts/Header/TitleHeader.tsx +++ b/client/src/components/Layouts/Header/TitleHeader.tsx @@ -19,18 +19,18 @@ const hovering = css` transition: all 0.2s; &:hover { - transform: scale(1.02) translateY(-2px); + transform: scale(1.02) translateY(-1px); } &:active { - transform: scale(0.99) translateY(1px); + transform: scale(0.99) translateY(0.5px); } `; const HeaderWrapper = styled.div` width: 100%; min-width: 32rem; - height: 4.4rem; + height: 5rem; ${flex('row', 'between', 'center', 0)}; position: sticky; diff --git a/client/src/hooks/index.tsx b/client/src/hooks/index.tsx index d3d3ec1..e86ae8f 100644 --- a/client/src/hooks/index.tsx +++ b/client/src/hooks/index.tsx @@ -1 +1,2 @@ export { default as useVh } from './useVh'; +export { default as useHeader } from './useHeader'; diff --git a/client/src/hooks/useHeader.tsx b/client/src/hooks/useHeader.tsx new file mode 100644 index 0000000..e7f1d0c --- /dev/null +++ b/client/src/hooks/useHeader.tsx @@ -0,0 +1,14 @@ +import { headerTitleState } from '@/atoms/headerTitleState'; +import { useAtom } from 'jotai'; + +const useHeader = () => { + const [headerTitle, setHeaderTitle] = useAtom(headerTitleState); + + const setHeader = (title: string) => { + setHeaderTitle(title); + }; + + return { headerTitle, setHeader }; +}; + +export default useHeader; diff --git a/client/src/pages/_app.tsx b/client/src/pages/_app.tsx index a2776de..b940375 100644 --- a/client/src/pages/_app.tsx +++ b/client/src/pages/_app.tsx @@ -1,13 +1,11 @@ import type { AppProps } from 'next/app'; -import { Global } from '@emotion/react'; import '@fortawesome/fontawesome-svg-core/styles.css'; -import reset from '@/styles/reset'; +import { Frame } from '@/components/Layouts'; export default function App({ Component, pageProps }: AppProps) { return ( - <> - + - + ); } diff --git a/client/src/pages/_document.tsx b/client/src/pages/_document.tsx index 136d9d9..5e4fb81 100644 --- a/client/src/pages/_document.tsx +++ b/client/src/pages/_document.tsx @@ -1,3 +1,5 @@ +import reset from '@/styles/reset'; +import { Global } from '@emotion/react'; import { Html, Head, Main, NextScript } from 'next/document'; export default function Document() { @@ -5,6 +7,7 @@ export default function Document() { +
diff --git a/client/src/pages/index.tsx b/client/src/pages/index.tsx index 7bcd29e..5b19e64 100644 --- a/client/src/pages/index.tsx +++ b/client/src/pages/index.tsx @@ -1,3 +1,5 @@ -export default function Home() { - return
; -} +const Home = () => { + return <>; +}; + +export default Home; From fda11a31cf764eb65d17eb03ec68875fc4ddcd89 Mon Sep 17 00:00:00 2001 From: Jun99uu_Lee Date: Sat, 14 Oct 2023 23:21:53 +0900 Subject: [PATCH 03/17] feat: Add frame navigator --- .../Layouts/Frame/FrameNavigator.tsx | 22 ++++++++++++++++ client/src/components/Layouts/Frame/index.tsx | 2 ++ .../components/Layouts/Navigator/index.tsx | 25 ++++++++++++------- client/src/pages/login.tsx | 5 ++++ client/src/pages/mate.tsx | 5 ++++ client/src/pages/schedule.tsx | 5 ++++ client/src/pages/template.tsx | 5 ++++ 7 files changed, 60 insertions(+), 9 deletions(-) create mode 100644 client/src/components/Layouts/Frame/FrameNavigator.tsx create mode 100644 client/src/pages/login.tsx create mode 100644 client/src/pages/mate.tsx create mode 100644 client/src/pages/schedule.tsx create mode 100644 client/src/pages/template.tsx diff --git a/client/src/components/Layouts/Frame/FrameNavigator.tsx b/client/src/components/Layouts/Frame/FrameNavigator.tsx new file mode 100644 index 0000000..a77ca30 --- /dev/null +++ b/client/src/components/Layouts/Frame/FrameNavigator.tsx @@ -0,0 +1,22 @@ +import { useRouter } from 'next/router'; +import { Navigator } from '..'; + +const FrameNavigator = () => { + const router = useRouter(); + + const handleRoute = (url: string) => { + router.push(url); + }; + + switch (router.pathname) { + case '/': + case '/template': + case '/mate': + case '/schedule': + return ; + default: + return <>; + } +}; + +export default FrameNavigator; diff --git a/client/src/components/Layouts/Frame/index.tsx b/client/src/components/Layouts/Frame/index.tsx index 6100683..04b2422 100644 --- a/client/src/components/Layouts/Frame/index.tsx +++ b/client/src/components/Layouts/Frame/index.tsx @@ -4,6 +4,7 @@ import { css } from '@emotion/react'; import { type ComponentProps } from 'react'; import FrameHeader from './FrameHeader'; import styled from '@emotion/styled'; +import FrameNavigator from './FrameNavigator'; interface FrameProps extends ComponentProps<'div'> { children: React.ReactNode; @@ -33,6 +34,7 @@ const Frame = ({ children, ...props }: FrameProps) => {
{children} +
); }; diff --git a/client/src/components/Layouts/Navigator/index.tsx b/client/src/components/Layouts/Navigator/index.tsx index 460d06f..388f0fa 100644 --- a/client/src/components/Layouts/Navigator/index.tsx +++ b/client/src/components/Layouts/Navigator/index.tsx @@ -1,5 +1,5 @@ import { COLORS } from '@/styles/colors'; -import { flex } from '@/styles/tokens'; +import { flex, transform } from '@/styles/tokens'; import { TYPO } from '@/styles/typo'; import { css } from '@emotion/react'; import styled from '@emotion/styled'; @@ -32,23 +32,27 @@ const Navigator = ({ curRoute, handleRoute }: Props) => { { icon: , title: '템플릿', - url: '/templates', + url: '/template', }, { icon: , title: '메이트', - url: '/mates', + url: '/mate', }, { icon: , title: '스케줄', - url: '/schedules', + url: '/schedule', }, ]; return ( - {items.map((item, idx) => ( - + {items.map((item) => ( + handleRoute(item.url)} + key={item.title} + > {item.icon} {item.title} @@ -58,14 +62,17 @@ const Navigator = ({ curRoute, handleRoute }: Props) => { }; const NavigatorWrapper = styled.div` - width: 100%; - min-width: 32rem; + max-width: 50rem; + width: 100vw; + min-width: 25rem; height: 7rem; ${flex('row', 'center', 'center', 0)}; position: fixed; bottom: 0px; - left: 0px; + left: 50%; + + ${transform('translate(-50%, 0)')} border-top: 0.5px solid rgba(10, 10, 10, 0.1); `; diff --git a/client/src/pages/login.tsx b/client/src/pages/login.tsx new file mode 100644 index 0000000..e1e7085 --- /dev/null +++ b/client/src/pages/login.tsx @@ -0,0 +1,5 @@ +const Login = () => { + return <>; +}; + +export default Login; diff --git a/client/src/pages/mate.tsx b/client/src/pages/mate.tsx new file mode 100644 index 0000000..8faf483 --- /dev/null +++ b/client/src/pages/mate.tsx @@ -0,0 +1,5 @@ +const Mate = () => { + return <>; +}; + +export default Mate; diff --git a/client/src/pages/schedule.tsx b/client/src/pages/schedule.tsx new file mode 100644 index 0000000..6ba8391 --- /dev/null +++ b/client/src/pages/schedule.tsx @@ -0,0 +1,5 @@ +const Schedule = () => { + return <>; +}; + +export default Schedule; diff --git a/client/src/pages/template.tsx b/client/src/pages/template.tsx new file mode 100644 index 0000000..068f1a8 --- /dev/null +++ b/client/src/pages/template.tsx @@ -0,0 +1,5 @@ +const Template = () => { + return <>; +}; + +export default Template; From 393a205e51ba394474d4b2b7d278aef434fed180 Mon Sep 17 00:00:00 2001 From: Jun99uu_Lee Date: Sun, 15 Oct 2023 11:30:35 +0900 Subject: [PATCH 04/17] feat: Add login page --- client/src/components/Layouts/Frame/index.tsx | 21 ++++- client/src/components/Layouts/Title/index.tsx | 1 + client/src/components/Login/Circle/index.tsx | 15 +++ client/src/components/Login/index.tsx | 1 + client/src/pages/_app.tsx | 36 +++++++ client/src/pages/_document.tsx | 3 - client/src/pages/login.tsx | 93 ++++++++++++++++++- client/src/styles/animations.tsx | 72 +++++++++++++- client/src/styles/reset.tsx | 3 + client/src/styles/typo/index.tsx | 2 +- 10 files changed, 238 insertions(+), 9 deletions(-) create mode 100644 client/src/components/Login/Circle/index.tsx create mode 100644 client/src/components/Login/index.tsx diff --git a/client/src/components/Layouts/Frame/index.tsx b/client/src/components/Layouts/Frame/index.tsx index 04b2422..cae2820 100644 --- a/client/src/components/Layouts/Frame/index.tsx +++ b/client/src/components/Layouts/Frame/index.tsx @@ -5,6 +5,8 @@ import { type ComponentProps } from 'react'; import FrameHeader from './FrameHeader'; import styled from '@emotion/styled'; import FrameNavigator from './FrameNavigator'; +import { COLORS } from '@/styles/colors'; +import { useRouter } from 'next/router'; interface FrameProps extends ComponentProps<'div'> { children: React.ReactNode; @@ -13,6 +15,7 @@ interface FrameProps extends ComponentProps<'div'> { /** 모바일 환경 최적화 프레임 */ const Frame = ({ children, ...props }: FrameProps) => { const { vh } = useVh(); + const router = useRouter(); const backgroundStyle = css` max-width: 50rem; @@ -30,8 +33,17 @@ const Frame = ({ children, ...props }: FrameProps) => { } `; + const getBgColor = (pathname: string) => { + switch (pathname) { + case '/login': + return bgColorStyle.login; + default: + return css``; + } + }; + return ( -
+
{children} @@ -41,6 +53,13 @@ const Frame = ({ children, ...props }: FrameProps) => { const Container = styled.div` width: 100%; + height: 100%; + position: relative; `; +const bgColorStyle = { + login: css` + background-color: ${COLORS.primary}; + `, +}; export default Frame; diff --git a/client/src/components/Layouts/Title/index.tsx b/client/src/components/Layouts/Title/index.tsx index 60f752a..82c33e0 100644 --- a/client/src/components/Layouts/Title/index.tsx +++ b/client/src/components/Layouts/Title/index.tsx @@ -37,6 +37,7 @@ const TitleWrapper = styled.div` ${flex('column', 'start', 'start', 1.1)}; white-space: pre-line; word-break: keep-all; + position: relative; `; const typo = { diff --git a/client/src/components/Login/Circle/index.tsx b/client/src/components/Login/Circle/index.tsx new file mode 100644 index 0000000..22f87df --- /dev/null +++ b/client/src/components/Login/Circle/index.tsx @@ -0,0 +1,15 @@ +import styled from '@emotion/styled'; +import { ComponentProps } from 'react'; + +const Circle = (props: ComponentProps<'div'>) => { + return ; +}; + +const Container = styled.div` + width: 48rem; + height: 48rem; + border-radius: 50rem; + background: linear-gradient(180deg, rgba(29, 155, 240, 0) 0%, #aeddfd 100%); +`; + +export default Circle; diff --git a/client/src/components/Login/index.tsx b/client/src/components/Login/index.tsx new file mode 100644 index 0000000..8713c22 --- /dev/null +++ b/client/src/components/Login/index.tsx @@ -0,0 +1 @@ +export { default as Circle } from './Circle'; diff --git a/client/src/pages/_app.tsx b/client/src/pages/_app.tsx index b940375..90450b2 100644 --- a/client/src/pages/_app.tsx +++ b/client/src/pages/_app.tsx @@ -1,11 +1,47 @@ import type { AppProps } from 'next/app'; import '@fortawesome/fontawesome-svg-core/styles.css'; import { Frame } from '@/components/Layouts'; +import useAuth from '@/hooks/useAuth'; +import { useEffect } from 'react'; +import { Global, css } from '@emotion/react'; +import reset from '@/styles/reset'; +import { useRouter } from 'next/router'; +import { COLORS } from '@/styles/colors'; export default function App({ Component, pageProps }: AppProps) { + const { checkAuth } = useAuth(); + const router = useRouter(); + + const getBgColor = (pathname: string) => { + switch (pathname) { + case '/login': + return bgColorStyle.login; + default: + return bgColorStyle.default; + } + }; + + useEffect(() => { + checkAuth(); + }, []); + return ( + ); } + +const bgColorStyle = { + login: css` + html { + background-color: ${COLORS.primary}; + } + `, + default: css` + html { + background-color: ${COLORS.white}; + } + `, +}; diff --git a/client/src/pages/_document.tsx b/client/src/pages/_document.tsx index 5e4fb81..136d9d9 100644 --- a/client/src/pages/_document.tsx +++ b/client/src/pages/_document.tsx @@ -1,5 +1,3 @@ -import reset from '@/styles/reset'; -import { Global } from '@emotion/react'; import { Html, Head, Main, NextScript } from 'next/document'; export default function Document() { @@ -7,7 +5,6 @@ export default function Document() { -
diff --git a/client/src/pages/login.tsx b/client/src/pages/login.tsx index e1e7085..d8e83a5 100644 --- a/client/src/pages/login.tsx +++ b/client/src/pages/login.tsx @@ -1,5 +1,96 @@ +import { RoundButton } from '@/components/Buttons'; +import { Circle } from '@/components/Login'; +import { useVh } from '@/hooks'; +import { injectAnimation } from '@/styles/animations'; +import { COLORS } from '@/styles/colors'; +import { flex, transform } from '@/styles/tokens'; +import { TYPO } from '@/styles/typo'; +import { css } from '@emotion/react'; +import styled from '@emotion/styled'; +import { useMemo } from 'react'; + const Login = () => { - return <>; + const { vh } = useVh(); + + const fullPageStyle = useMemo( + () => css` + width: 100%; + height: calc(${vh}px * 100); + `, + [vh], + ); + + return ( + + + SSUDOBI + 숭실대학교 도서관 비대면 예약 시스템 + + + + {`로그인 시, 개인 정보 처리 방침 및\n서비스 이용 약관에 동의하게 됩니다.`} + + + + + ); +}; + +const Container = styled.div` + background-color: ${COLORS.primary}; + position: relative; +`; + +const TitleWrapper = styled.div` + ${flex('column', 'center', 'center', 1.4)}; + cursor: default; + + color: white; + text-align: center; + white-space: nowrap; + + position: absolute; + top: 50%; + left: 50%; + ${transform('translate(-50%, -50%)')}; + + ${injectAnimation('loginTitlePopup', '3s', 'ease', '0s')} +`; + +const ButtonWrapper = styled.div` + width: 80%; + max-width: 40rem; + ${flex('column', 'center', 'center', 2.1)}; + + position: absolute; + left: 50%; + + ${transform('translate(-50%, -50%)')}; + ${injectAnimation('loginButtonPopup', '3s', 'ease', '0s')}; +`; + +const Caption = styled.span` + ${TYPO.text2.Reg}; + white-space: pre-line; + text-align: center; + line-height: 150%; + color: white; +`; + +const circleStyles = { + common: css` + position: absolute; + `, + top: css` + top: -30%; + right: -40%; + ${injectAnimation('circleMovingTop', '3s', 'ease', '0s')} + `, + bottom: css` + bottom: -30%; + left: -40%; + ${injectAnimation('circleMovingBottom', '3s', 'ease', '0s')} + `, }; export default Login; diff --git a/client/src/styles/animations.tsx b/client/src/styles/animations.tsx index e9d82a3..9d60464 100644 --- a/client/src/styles/animations.tsx +++ b/client/src/styles/animations.tsx @@ -29,9 +29,73 @@ const popUp = keyframes` } `; +const circleMoving = { + top: keyframes` + 0%{ + opacity: 0; + ${transform('rotate(180deg) translate(10%, -10%)')}; + } + 50%{ + opacity: 1; + ${transform('rotate(180deg) translate(0%, -10%)')}; + } + 100%{ + opacity: 1; + ${transform('rotate(180deg) translate(0%, 0%)')}; + } + `, + bottom: keyframes` + 0%{ + opacity: 0; + ${transform('rotate(10deg) translate(10%, -10%)')}; + } + 50%{ + opacity: 1; + ${transform('rotate(10deg) translate(0%, -10%)')}; + } + 100%{ + opacity: 1; + ${transform('rotate(10deg) translate(0%, 0%)')}; + } + `, +}; + +const loginTitlePopup = keyframes` + 0%{ + opacity: 0; + top: 50%; + } + 50%{ + opacity: 1; + top: 50%; + } + 100%{ + opacity: 1; + top: 35%; + } +`; + +const loginButtonPopup = keyframes` + 0%{ + opacity: 0; + } + 50%{ + opacity: 0; + top: 55%; + } + 100%{ + opacity: 1; + top: 65%; + } +`; + const animations = { fadeInTopDown, popUp, + circleMovingTop: circleMoving.top, + circleMovingBottom: circleMoving.bottom, + loginTitlePopup, + loginButtonPopup, }; export const injectAnimation = ( @@ -39,12 +103,14 @@ export const injectAnimation = ( duration = '1.5s', type = 'linear', delay = '0s', - afterStyle?: SerializedStyles, + relative = false, ): SerializedStyles => { const newAnimation = css` - position: relative; animation: ${animations[animation]} ${duration} ${type} ${delay} forwards; - ${afterStyle} + ${relative && + css` + position: relative; + `}; `; return newAnimation; diff --git a/client/src/styles/reset.tsx b/client/src/styles/reset.tsx index f177429..7c449fb 100644 --- a/client/src/styles/reset.tsx +++ b/client/src/styles/reset.tsx @@ -1,6 +1,7 @@ import { css } from '@emotion/react'; import { mq } from './breakpoints'; import { COLORS } from './colors'; +import { transition } from './tokens'; const reset = css` html, @@ -14,6 +15,8 @@ const reset = css` background-color: ${COLORS.white}; font-size: 9px; + ${transition('0.5s', 'linear')}; + ${mq[2]} { font-size: 9px; } diff --git a/client/src/styles/typo/index.tsx b/client/src/styles/typo/index.tsx index c5757b7..8623f3c 100644 --- a/client/src/styles/typo/index.tsx +++ b/client/src/styles/typo/index.tsx @@ -64,7 +64,7 @@ export const TYPO: TypoType = { font-size: 1.6rem; `, [TEXT_STYLE_WEIGHT.Reg]: css` - ${pretendardSemibold.style}; + ${pretendardRegular.style}; font-size: 1.6rem; `, /** 얘는 조금 특별하게 굵기 차이가 아닌 간격차이로 인지해주세용 */ From be087e1b1e9cb19f83e6fda7f6d987dc16006d31 Mon Sep 17 00:00:00 2001 From: Jun99uu_Lee Date: Sun, 15 Oct 2023 11:33:39 +0900 Subject: [PATCH 05/17] rename: login to landing --- .../components/Layouts/Frame/FrameHeader.tsx | 2 +- client/src/components/Layouts/Frame/index.tsx | 2 +- client/src/hooks/useAuth.tsx | 18 ++++++++++++++++++ client/src/pages/_app.tsx | 2 +- client/src/pages/{login.tsx => landing.tsx} | 4 ++-- 5 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 client/src/hooks/useAuth.tsx rename client/src/pages/{login.tsx => landing.tsx} (97%) diff --git a/client/src/components/Layouts/Frame/FrameHeader.tsx b/client/src/components/Layouts/Frame/FrameHeader.tsx index d5f8780..06059f2 100644 --- a/client/src/components/Layouts/Frame/FrameHeader.tsx +++ b/client/src/components/Layouts/Frame/FrameHeader.tsx @@ -7,7 +7,7 @@ const FrameHeader = () => { const { headerTitle } = useHeader(); switch (router.pathname) { - case '/login': + case '/landing': return <>; case '/': case '/template': diff --git a/client/src/components/Layouts/Frame/index.tsx b/client/src/components/Layouts/Frame/index.tsx index cae2820..b637ab6 100644 --- a/client/src/components/Layouts/Frame/index.tsx +++ b/client/src/components/Layouts/Frame/index.tsx @@ -35,7 +35,7 @@ const Frame = ({ children, ...props }: FrameProps) => { const getBgColor = (pathname: string) => { switch (pathname) { - case '/login': + case '/landing': return bgColorStyle.login; default: return css``; diff --git a/client/src/hooks/useAuth.tsx b/client/src/hooks/useAuth.tsx new file mode 100644 index 0000000..59a5d47 --- /dev/null +++ b/client/src/hooks/useAuth.tsx @@ -0,0 +1,18 @@ +import { authInfoState } from '@/atoms/authInfoState'; +import { useAtom } from 'jotai'; +import { useRouter } from 'next/router'; + +const useAuth = () => { + const router = useRouter(); + const [authInfo, setAuthInfo] = useAtom(authInfoState); + + const handleLogin = () => {}; + + const checkAuth = () => { + if (!authInfo) router.replace('/landing'); + }; + + return { authInfo, checkAuth }; +}; + +export default useAuth; diff --git a/client/src/pages/_app.tsx b/client/src/pages/_app.tsx index 90450b2..a17f511 100644 --- a/client/src/pages/_app.tsx +++ b/client/src/pages/_app.tsx @@ -14,7 +14,7 @@ export default function App({ Component, pageProps }: AppProps) { const getBgColor = (pathname: string) => { switch (pathname) { - case '/login': + case '/landing': return bgColorStyle.login; default: return bgColorStyle.default; diff --git a/client/src/pages/login.tsx b/client/src/pages/landing.tsx similarity index 97% rename from client/src/pages/login.tsx rename to client/src/pages/landing.tsx index d8e83a5..dd4c1cb 100644 --- a/client/src/pages/login.tsx +++ b/client/src/pages/landing.tsx @@ -9,7 +9,7 @@ import { css } from '@emotion/react'; import styled from '@emotion/styled'; import { useMemo } from 'react'; -const Login = () => { +const Landing = () => { const { vh } = useVh(); const fullPageStyle = useMemo( @@ -93,4 +93,4 @@ const circleStyles = { `, }; -export default Login; +export default Landing; From e991bc0e148b3773f2821d20681540b5319d4a76 Mon Sep 17 00:00:00 2001 From: Jun99uu_Lee Date: Sun, 15 Oct 2023 11:34:00 +0900 Subject: [PATCH 06/17] feat: Add login api response type & atom --- client/src/@types/Auth.d.ts | 63 +++++++++++++++++++++++++++++++ client/src/atoms/authInfoState.ts | 4 ++ 2 files changed, 67 insertions(+) create mode 100644 client/src/@types/Auth.d.ts create mode 100644 client/src/atoms/authInfoState.ts diff --git a/client/src/@types/Auth.d.ts b/client/src/@types/Auth.d.ts new file mode 100644 index 0000000..a34ba2e --- /dev/null +++ b/client/src/@types/Auth.d.ts @@ -0,0 +1,63 @@ +declare module 'Auth' { + export type AuthResponse = { + success: boolean; + code: string; + message: string; + data: AuthData; + }; + + export type AuthData = { + availableHomepages: number[]; + isPrivacyPolicyAgree: boolean; + privacyPolicyAgreePeriod: number; + dept: Dept; + accessToken: string; + parentDept: UserDept; + branch: Branch; + showMobileMain: boolean; + memberNo: string; + alternativeId: string; + lastUpdated: string; + branchGroup: BranchGroup; + isPortalLogin: boolean; + patronType: PatronType; + disableServices: string[]; + hasFamily: boolean; + name: string; + printMemberNo: string; + patronState: PatronState; + id: number; + multiTypePatrons: any[]; + isExpired: boolean; + isFamilyLogin: boolean; + }; + + export type UserDept = { + id: number; + code: string; + name: string; + }; + + export type Branch = { + id: number; + name: string; + alias: string; + libraryCode: string; + sortOrder: number; + }; + + export type BranchGroup = { + id: number; + name: string; + }; + + export type PatronType = { + id: number; + name: string; + }; + + export type PatronState = { + id: number; + name: string; + }; +} diff --git a/client/src/atoms/authInfoState.ts b/client/src/atoms/authInfoState.ts new file mode 100644 index 0000000..51d4680 --- /dev/null +++ b/client/src/atoms/authInfoState.ts @@ -0,0 +1,4 @@ +import { AuthResponse } from 'Auth'; +import { atom } from 'jotai'; + +export const authInfoState = atom(null); From bb63ddbedd4fa8c9b0f954280c515d902b5a6b05 Mon Sep 17 00:00:00 2001 From: Jun99uu_Lee Date: Sun, 15 Oct 2023 11:42:21 +0900 Subject: [PATCH 07/17] feat: Add Login page & header back handler --- .../components/Layouts/Frame/FrameHeader.tsx | 6 +++++- .../src/components/Layouts/Header/NavHeader.tsx | 5 +++-- client/src/pages/landing.tsx | 17 +++++++++++++++-- client/src/pages/login.tsx | 17 +++++++++++++++++ 4 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 client/src/pages/login.tsx diff --git a/client/src/components/Layouts/Frame/FrameHeader.tsx b/client/src/components/Layouts/Frame/FrameHeader.tsx index 06059f2..09218c7 100644 --- a/client/src/components/Layouts/Frame/FrameHeader.tsx +++ b/client/src/components/Layouts/Frame/FrameHeader.tsx @@ -6,6 +6,10 @@ const FrameHeader = () => { const router = useRouter(); const { headerTitle } = useHeader(); + const handleBack = () => { + router.back(); + }; + switch (router.pathname) { case '/landing': return <>; @@ -15,7 +19,7 @@ const FrameHeader = () => { case '/schedule': return ; default: - return ; + return ; } }; diff --git a/client/src/components/Layouts/Header/NavHeader.tsx b/client/src/components/Layouts/Header/NavHeader.tsx index 1e31271..95a882b 100644 --- a/client/src/components/Layouts/Header/NavHeader.tsx +++ b/client/src/components/Layouts/Header/NavHeader.tsx @@ -8,13 +8,14 @@ import { ComponentProps } from 'react'; interface Props extends ComponentProps<'div'> { title: string; + onBack?: () => void; } -const NavHeader = ({ title, ...props }: Props) => { +const NavHeader = ({ title, onBack, ...props }: Props) => { return ( - + {title} diff --git a/client/src/pages/landing.tsx b/client/src/pages/landing.tsx index dd4c1cb..34c6c6e 100644 --- a/client/src/pages/landing.tsx +++ b/client/src/pages/landing.tsx @@ -7,10 +7,15 @@ import { flex, transform } from '@/styles/tokens'; import { TYPO } from '@/styles/typo'; import { css } from '@emotion/react'; import styled from '@emotion/styled'; +import { useRouter } from 'next/router'; import { useMemo } from 'react'; +/** + * 시작 페이지 + */ const Landing = () => { const { vh } = useVh(); + const route = useRouter(); const fullPageStyle = useMemo( () => css` @@ -20,6 +25,10 @@ const Landing = () => { [vh], ); + const handleRouteLogin = () => { + route.push('login'); + }; + return ( @@ -27,7 +36,11 @@ const Landing = () => { 숭실대학교 도서관 비대면 예약 시스템 - + {`로그인 시, 개인 정보 처리 방침 및\n서비스 이용 약관에 동의하게 됩니다.`} @@ -66,7 +79,7 @@ const ButtonWrapper = styled.div` left: 50%; ${transform('translate(-50%, -50%)')}; - ${injectAnimation('loginButtonPopup', '3s', 'ease', '0s')}; + ${injectAnimation('loginButtonPopup', '3.3s', 'ease', '0s')}; `; const Caption = styled.span` diff --git a/client/src/pages/login.tsx b/client/src/pages/login.tsx new file mode 100644 index 0000000..9ccbe13 --- /dev/null +++ b/client/src/pages/login.tsx @@ -0,0 +1,17 @@ +import { useHeader } from '@/hooks'; +import { useLayoutEffect } from 'react'; + +/** + * 로그인 페이지 + */ +const Login = () => { + const { setHeader } = useHeader(); + + useLayoutEffect(() => { + setHeader('로그인'); + }, []); + + return <>; +}; + +export default Login; From 7b17896ccec490909bc55c4f8ae277b6dd0c7b12 Mon Sep 17 00:00:00 2001 From: Jun99uu_Lee Date: Sun, 15 Oct 2023 12:13:22 +0900 Subject: [PATCH 08/17] feat: Add custom text input --- .../Field/TextInput/TextInput.stories.tsx | 42 +++++++ .../src/components/Field/TextInput/index.tsx | 115 ++++++++++++++++++ client/src/components/Field/index.tsx | 0 client/src/styles/animations.tsx | 12 ++ 4 files changed, 169 insertions(+) create mode 100644 client/src/components/Field/TextInput/TextInput.stories.tsx create mode 100644 client/src/components/Field/TextInput/index.tsx create mode 100644 client/src/components/Field/index.tsx diff --git a/client/src/components/Field/TextInput/TextInput.stories.tsx b/client/src/components/Field/TextInput/TextInput.stories.tsx new file mode 100644 index 0000000..3e6d212 --- /dev/null +++ b/client/src/components/Field/TextInput/TextInput.stories.tsx @@ -0,0 +1,42 @@ +import type { StoryObj } from '@storybook/react'; +import TextInput from '.'; + +const meta = { + title: 'Field/TextInput', + component: TextInput, + tags: ['autodocs'], +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + value: '', + placeholder: '값을 입력해주세요', + warning: false, + }, +}; + +export const Input: Story = { + args: { + value: '입력 값 입니다.', + warning: false, + }, +}; + +export const Warning: Story = { + args: { + value: '입력 값 입니다.', + warning: true, + }, +}; + +export const WarningCaption: Story = { + args: { + value: '입력 값 입니다.', + warning: true, + warningCaption: '경고 문구입니다.', + }, +}; diff --git a/client/src/components/Field/TextInput/index.tsx b/client/src/components/Field/TextInput/index.tsx new file mode 100644 index 0000000..79a33cf --- /dev/null +++ b/client/src/components/Field/TextInput/index.tsx @@ -0,0 +1,115 @@ +import styled from '@emotion/styled'; +import { css } from '@emotion/react'; +import { ComponentProps } from 'react'; +import { COLORS } from '@/styles/colors'; +import { TYPO } from '@/styles/typo'; +import { injectAnimation } from '@/styles/animations'; + +interface Props extends ComponentProps<'input'> { + /** + * 입력 값 오버라이딩 + */ + value: string; + /** + * 경고 여부 (underline) + */ + warning?: boolean; + /** + * 경고 상황일 때, 하단 캡션 문구 + */ + warningCaption?: string; +} + +/** + * 커스텀 인풋 박스 + */ +const TextInput = ({ + value, + warning = false, + warningCaption, + ...props +}: Props) => { + return ( + + + + {warning && warningCaption && {warningCaption}} + + ); +}; + +const InputWrapper = styled.div` + width: 100%; + position: relative; +`; + +const CustomInput = styled.input` + width: 100%; + border: none; + outline: none; + background: none; + ${TYPO.title3.Lg}; + + &:focus + div::before { + width: 100%; + } + + &::placeholder { + color: ${COLORS.grey5}; + } +`; + +const Underline = styled.div` + position: absolute; + bottom: -0.6rem; + left: 0; + width: 100%; + height: 1px; + background-color: ${COLORS.grey5}; + + ::before, + ::after { + content: ''; + position: absolute; + height: 2px; + width: 0; + bottom: 0; + left: 0; + background-color: ${COLORS.primary}; + transition: width 0.4s; + } +`; + +const Caption = styled.span` + ${TYPO.caption.Reg}; + color: ${COLORS.tomato}; + text-align: start; + white-space: nowrap; + + position: absolute; + bottom: -2.8rem; + right: 0px; + + ${injectAnimation('fadeInTopDownTranslate', '0.2s')}; +`; + +const underlineStyle = { + filled: css` + &::before { + width: 100%; + } + `, + warning: css` + &::after { + background-color: ${COLORS.tomato}; + width: 100%; + } + `, +}; + +export default TextInput; diff --git a/client/src/components/Field/index.tsx b/client/src/components/Field/index.tsx new file mode 100644 index 0000000..e69de29 diff --git a/client/src/styles/animations.tsx b/client/src/styles/animations.tsx index 9d60464..60e64c4 100644 --- a/client/src/styles/animations.tsx +++ b/client/src/styles/animations.tsx @@ -14,6 +14,17 @@ const fadeInTopDown = keyframes` } `; +const fadeInTopDownTranslate = keyframes` + from{ + opacity: 0; + ${transform('translate(0px, -10px)')} + } + to{ + opacity: 1; + ${transform('translate(0px, 0px)')} + } +`; + const popUp = keyframes` 0%{ opacity: 0; @@ -91,6 +102,7 @@ const loginButtonPopup = keyframes` const animations = { fadeInTopDown, + fadeInTopDownTranslate, popUp, circleMovingTop: circleMoving.top, circleMovingBottom: circleMoving.bottom, From 42a6815881e4175bb0a2082b0b4f1d398968805b Mon Sep 17 00:00:00 2001 From: Jun99uu_Lee Date: Sun, 15 Oct 2023 12:36:37 +0900 Subject: [PATCH 09/17] fix: header height --- .../components/Layouts/Header/NavHeader.tsx | 18 ++++++++++++++---- .../components/Layouts/Header/TitleHeader.tsx | 14 ++++++++++---- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/client/src/components/Layouts/Header/NavHeader.tsx b/client/src/components/Layouts/Header/NavHeader.tsx index 95a882b..cab7b8e 100644 --- a/client/src/components/Layouts/Header/NavHeader.tsx +++ b/client/src/components/Layouts/Header/NavHeader.tsx @@ -1,5 +1,6 @@ +import { mq } from '@/styles/breakpoints'; import { COLORS } from '@/styles/colors'; -import { flex } from '@/styles/tokens'; +import { flex, transform } from '@/styles/tokens'; import { TYPO } from '@/styles/typo'; import styled from '@emotion/styled'; import { faChevronLeft } from '@fortawesome/free-solid-svg-icons'; @@ -27,12 +28,17 @@ const NavHeader = ({ title, onBack, ...props }: Props) => { const HeaderWrapper = styled.div` width: 100%; min-width: 32rem; - height: 5rem; - - position: sticky; + height: 6rem; + position: fixed; top: 0px; + left: 50%; + ${transform('translate(-50%, 0%)')}; box-shadow: 0px 1px 1px 0px rgba(0, 0, 0, 0.1); + + ${mq[4]} { + height: 5rem; + } `; const HeaderInner = styled.div` @@ -49,6 +55,10 @@ const HeaderTitle = styled.span` `; const Chevron = styled.span` + height: 100%; + aspect-ratio: 1; + ${flex('row', 'center', 'center', 0)}; + ${TYPO.title2.Md}; color: ${COLORS.grey0}; position: absolute; diff --git a/client/src/components/Layouts/Header/TitleHeader.tsx b/client/src/components/Layouts/Header/TitleHeader.tsx index 47692a5..87ad0c5 100644 --- a/client/src/components/Layouts/Header/TitleHeader.tsx +++ b/client/src/components/Layouts/Header/TitleHeader.tsx @@ -1,9 +1,10 @@ import styled from '@emotion/styled'; import { TYPO } from '@/styles/typo'; import { COLORS } from '@/styles/colors'; -import { flex } from '@/styles/tokens'; +import { flex, transform } from '@/styles/tokens'; import HumanIcon from '@/assets/svg/human.svg'; import { css } from '@emotion/react'; +import { mq } from '@/styles/breakpoints'; const TitleHeader = () => { return ( @@ -30,14 +31,19 @@ const hovering = css` const HeaderWrapper = styled.div` width: 100%; min-width: 32rem; - height: 5rem; + height: 6rem; ${flex('row', 'between', 'center', 0)}; - - position: sticky; + position: fixed; top: 0px; + left: 50%; + ${transform('translate(-50%, 0%)')}; box-shadow: 0px 1px 1px 0px rgba(0, 0, 0, 0.1); padding: 0rem 2.7rem; + + ${mq[4]} { + height: 5rem; + } `; const Logo = styled.span` From 820b0c97b399dafa0b8eafd9efac1e3b88197ec5 Mon Sep 17 00:00:00 2001 From: Jun99uu_Lee Date: Sun, 15 Oct 2023 12:37:06 +0900 Subject: [PATCH 10/17] feat: Add login inner components --- client/src/components/Field/index.tsx | 1 + client/src/hooks/useVh.tsx | 13 +++++- client/src/pages/landing.tsx | 11 +---- client/src/pages/login.tsx | 61 ++++++++++++++++++++++++++- 4 files changed, 72 insertions(+), 14 deletions(-) diff --git a/client/src/components/Field/index.tsx b/client/src/components/Field/index.tsx index e69de29..137a144 100644 --- a/client/src/components/Field/index.tsx +++ b/client/src/components/Field/index.tsx @@ -0,0 +1 @@ +export { default as TextInput } from './TextInput'; diff --git a/client/src/hooks/useVh.tsx b/client/src/hooks/useVh.tsx index eb55686..6bd0728 100644 --- a/client/src/hooks/useVh.tsx +++ b/client/src/hooks/useVh.tsx @@ -1,4 +1,5 @@ -import { useEffect, useState } from 'react'; +import { css } from '@emotion/react'; +import { useEffect, useMemo, useState } from 'react'; /** 리사이징 이벤트에 따라 변하는 vh 가져오는 훅 (불필요한 스크롤 생기는 이슈 방지) */ const useVh = () => { @@ -9,6 +10,14 @@ const useVh = () => { setVh(vh); }; + const fullPageStyle = useMemo( + () => css` + width: 100%; + height: calc(${vh}px * 100); + `, + [vh], + ); + useEffect(() => { mobileScreenSize(); window.addEventListener('resize', () => mobileScreenSize()); @@ -17,7 +26,7 @@ const useVh = () => { }; }, []); - return { vh }; + return { vh, fullPageStyle }; }; export default useVh; diff --git a/client/src/pages/landing.tsx b/client/src/pages/landing.tsx index 34c6c6e..afb208a 100644 --- a/client/src/pages/landing.tsx +++ b/client/src/pages/landing.tsx @@ -8,23 +8,14 @@ import { TYPO } from '@/styles/typo'; import { css } from '@emotion/react'; import styled from '@emotion/styled'; import { useRouter } from 'next/router'; -import { useMemo } from 'react'; /** * 시작 페이지 */ const Landing = () => { - const { vh } = useVh(); + const { fullPageStyle } = useVh(); const route = useRouter(); - const fullPageStyle = useMemo( - () => css` - width: 100%; - height: calc(${vh}px * 100); - `, - [vh], - ); - const handleRouteLogin = () => { route.push('login'); }; diff --git a/client/src/pages/login.tsx b/client/src/pages/login.tsx index 9ccbe13..a7e32a6 100644 --- a/client/src/pages/login.tsx +++ b/client/src/pages/login.tsx @@ -1,4 +1,10 @@ -import { useHeader } from '@/hooks'; +import { RoundButton } from '@/components/Buttons'; +import { TextInput } from '@/components/Field'; +import { useHeader, useVh } from '@/hooks'; +import { COLORS } from '@/styles/colors'; +import { flex } from '@/styles/tokens'; +import { TYPO } from '@/styles/typo'; +import styled from '@emotion/styled'; import { useLayoutEffect } from 'react'; /** @@ -6,12 +12,63 @@ import { useLayoutEffect } from 'react'; */ const Login = () => { const { setHeader } = useHeader(); + const { fullPageStyle } = useVh(); useLayoutEffect(() => { setHeader('로그인'); }, []); - return <>; + return ( + + + + 학번 + + + + 비밀번호 + + + + + + 비밀번호를 재설정하고 싶어요. + + + ); }; +const Container = styled.div` + width: 100%; + padding: 12rem 4.5rem; + ${flex('column', 'between', 'center', 5)}; +`; + +const InputWrapper = styled.div` + width: 100%; + ${flex('column', 'start', 'start', 5)}; +`; + +const InputBox = styled.div` + width: 100%; + ${flex('column', 'start', 'start', 1)}; +`; + +const Caption = styled.span` + ${TYPO.title3.Reg}; + color: ${COLORS.grey0}; +`; + +const ButtonWrapper = styled.div` + width: 100%; + ${flex('column', 'start', 'center', 3)}; +`; + +const Link = styled.span` + ${TYPO.text2.Reg}; + color: ${COLORS.grey3}; + cursor: pointer; + text-decoration: underline; +`; + export default Login; From 1f74cd79849924de5ccc90e0c459e19719bab341 Mon Sep 17 00:00:00 2001 From: Jun99uu_Lee Date: Sun, 15 Oct 2023 12:45:04 +0900 Subject: [PATCH 11/17] fix: typo size --- client/src/components/Buttons/Mini/index.tsx | 2 +- client/src/components/Buttons/Round/index.tsx | 2 +- .../src/components/Buttons/Square/index.tsx | 2 +- .../src/components/Field/TextInput/index.tsx | 1 + client/src/styles/typo/index.tsx | 48 +++++++++---------- 5 files changed, 28 insertions(+), 27 deletions(-) diff --git a/client/src/components/Buttons/Mini/index.tsx b/client/src/components/Buttons/Mini/index.tsx index 74ddb95..c6ba40e 100644 --- a/client/src/components/Buttons/Mini/index.tsx +++ b/client/src/components/Buttons/Mini/index.tsx @@ -54,7 +54,7 @@ const ButtonWrapper = styled.button<{ theme: Theme }>` border-radius: 5rem; border: none; outline: none; - ${TYPO.title3.Md}; + ${TYPO.text1.Md}; box-shadow: rgba(0, 0, 0, 0.05) 0px 0px 0px 1px; ${(props) => { diff --git a/client/src/components/Buttons/Round/index.tsx b/client/src/components/Buttons/Round/index.tsx index 7a07efd..5c22dc1 100644 --- a/client/src/components/Buttons/Round/index.tsx +++ b/client/src/components/Buttons/Round/index.tsx @@ -51,7 +51,7 @@ const ButtonWrapper = styled.button<{ theme: Theme }>` border-radius: 5rem; border: none; outline: none; - ${TYPO.title3.Sb}; + ${TYPO.text1.Sb}; box-shadow: rgba(0, 0, 0, 0.05) 0px 0px 0px 1px; ${flex('row', 'center', 'center', 0)}; diff --git a/client/src/components/Buttons/Square/index.tsx b/client/src/components/Buttons/Square/index.tsx index 727b7d0..4e763dd 100644 --- a/client/src/components/Buttons/Square/index.tsx +++ b/client/src/components/Buttons/Square/index.tsx @@ -51,7 +51,7 @@ const ButtonWrapper = styled.button<{ theme: Theme }>` border-radius: 1rem; border: none; outline: none; - ${TYPO.title3.Sb}; + ${TYPO.text1.Sb}; box-shadow: rgba(0, 0, 0, 0.05) 0px 0px 0px 1px; ${flex('row', 'center', 'center', 0)}; diff --git a/client/src/components/Field/TextInput/index.tsx b/client/src/components/Field/TextInput/index.tsx index 79a33cf..d4da8ee 100644 --- a/client/src/components/Field/TextInput/index.tsx +++ b/client/src/components/Field/TextInput/index.tsx @@ -60,6 +60,7 @@ const CustomInput = styled.input` } &::placeholder { + ${TYPO.title3.Lg}; color: ${COLORS.grey5}; } `; diff --git a/client/src/styles/typo/index.tsx b/client/src/styles/typo/index.tsx index 8623f3c..ca6551d 100644 --- a/client/src/styles/typo/index.tsx +++ b/client/src/styles/typo/index.tsx @@ -43,115 +43,115 @@ export const TYPO: TypoType = { [TEXT_STYLE_SIZE.title1]: { [TEXT_STYLE_WEIGHT.Eb]: css` ${pretendardExtrabold.style}; - font-size: 4.8rem; + font-size: 5rem; `, [TEXT_STYLE_WEIGHT.Sb]: css` ${pretendardSemibold.style}; - font-size: 2rem; + font-size: 2.2rem; `, }, [TEXT_STYLE_SIZE.title2]: { [TEXT_STYLE_WEIGHT.Bd]: css` ${pretendardBold.style}; - font-size: 1.6rem; + font-size: 1.8rem; `, [TEXT_STYLE_WEIGHT.Sb]: css` ${pretendardSemibold.style}; - font-size: 1.6rem; + font-size: 1.8rem; `, [TEXT_STYLE_WEIGHT.Md]: css` ${pretendardMedium.style}; - font-size: 1.6rem; + font-size: 1.8rem; `, [TEXT_STYLE_WEIGHT.Reg]: css` ${pretendardRegular.style}; - font-size: 1.6rem; + font-size: 1.8rem; `, /** 얘는 조금 특별하게 굵기 차이가 아닌 간격차이로 인지해주세용 */ [TEXT_STYLE_WEIGHT.Lg]: css` ${pretendardSemibold.style}; - font-size: 1.6rem; + font-size: 1.8rem; `, }, [TEXT_STYLE_SIZE.title3]: { [TEXT_STYLE_WEIGHT.Sb]: css` ${pretendardSemibold.style}; - font-size: 1.5rem; + font-size: 1.7rem; `, [TEXT_STYLE_WEIGHT.Md]: css` ${pretendardMedium.style}; - font-size: 1.5rem; + font-size: 1.7rem; `, [TEXT_STYLE_WEIGHT.Reg]: css` ${pretendardRegular.style}; - font-size: 1.5rem; + font-size: 1.7rem; `, }, [TEXT_STYLE_SIZE.text1]: { [TEXT_STYLE_WEIGHT.Sb]: css` ${pretendardSemibold.style}; - font-size: 1.4rem; + font-size: 1.6rem; `, [TEXT_STYLE_WEIGHT.Md]: css` ${pretendardMedium.style}; - font-size: 1.4rem; + font-size: 1.6rem; `, [TEXT_STYLE_WEIGHT.Reg]: css` ${pretendardRegular.style}; - font-size: 1.4rem; + font-size: 1.6rem; `, /** 얘는 조금 특별하게 굵기 차이가 아닌 간격차이로 인지해주세용 */ [TEXT_STYLE_WEIGHT.Lg]: css` ${pretendardRegular.style}; - font-size: 1.4rem; + font-size: 1.6rem; `, }, [TEXT_STYLE_SIZE.text2]: { [TEXT_STYLE_WEIGHT.Sb]: css` ${pretendardSemibold.style}; - font-size: 1.2rem; + font-size: 1.4rem; `, [TEXT_STYLE_WEIGHT.Md]: css` ${pretendardMedium.style}; - font-size: 1.2rem; + font-size: 1.4rem; `, [TEXT_STYLE_WEIGHT.Reg]: css` ${pretendardRegular.style}; - font-size: 1.2rem; + font-size: 1.4rem; `, }, [TEXT_STYLE_SIZE.text3]: { [TEXT_STYLE_WEIGHT.Eb]: css` ${pretendardExtrabold.style}; - font-size: 1rem; + font-size: 1.2rem; `, [TEXT_STYLE_WEIGHT.Reg]: css` ${pretendardRegular.style}; - font-size: 1rem; + font-size: 1.2rem; `, [TEXT_STYLE_WEIGHT.Lg]: css` ${pretendardLight.style}; - font-size: 1rem; + font-size: 1.2rem; `, }, [TEXT_STYLE_SIZE.caption]: { [TEXT_STYLE_WEIGHT.Md]: css` ${pretendardMedium.style}; - font-size: 2.2rem; + font-size: 2.4rem; `, [TEXT_STYLE_WEIGHT.Reg]: css` ${pretendardRegular.style}; - font-size: 1.3rem; + font-size: 1.5rem; `, }, [TEXT_STYLE_SIZE.label]: { [TEXT_STYLE_WEIGHT.Md]: css` ${pretendardMedium.style}; - font-size: 0.8rem; + font-size: 1rem; `, [TEXT_STYLE_WEIGHT.Reg]: css` ${pretendardRegular.style}; - font-size: 0.8rem; + font-size: 1rem; `, }, }; From 8783a8d57a418d752b5f2f7bd3b080dedd34455d Mon Sep 17 00:00:00 2001 From: Jun99uu_Lee Date: Sun, 15 Oct 2023 13:23:02 +0900 Subject: [PATCH 12/17] test: temp login handler --- client/src/atoms/authInfoState.ts | 54 +++++++++++++++++++ .../components/Layouts/Navigator/index.tsx | 3 +- client/src/hooks/useAuth.tsx | 9 ++-- client/src/pages/login.tsx | 4 +- 4 files changed, 64 insertions(+), 6 deletions(-) diff --git a/client/src/atoms/authInfoState.ts b/client/src/atoms/authInfoState.ts index 51d4680..54802ba 100644 --- a/client/src/atoms/authInfoState.ts +++ b/client/src/atoms/authInfoState.ts @@ -2,3 +2,57 @@ import { AuthResponse } from 'Auth'; import { atom } from 'jotai'; export const authInfoState = atom(null); + +export const dummyAuthData: AuthResponse = { + success: true, + code: 'successCode_xyz123', + message: '로그인되었습니다.', + data: { + availableHomepages: [42, 69], + isPrivacyPolicyAgree: true, + privacyPolicyAgreePeriod: 3, + dept: { + id: 456, + code: 'deptCode_abc789', + name: '과학학부', + }, + accessToken: 'xyzabc789ghi', + parentDept: { + id: 457, + code: 'parentDeptCode_def456', + name: '자연대학', + }, + branch: { + id: 123, + name: '서부도서관', + alias: '서부', + libraryCode: 'libCode_ghi123', + sortOrder: 2, + }, + showMobileMain: false, + memberNo: 'member_xyz789', + alternativeId: 'altid_ghi123', + lastUpdated: '2023-07-27 15:23:45', + branchGroup: { + id: 24, + name: '서울대학교', + }, + isPortalLogin: false, + patronType: { + id: 34, + name: '교수', + }, + disableServices: ['ABC', 'DEF', 'GHI'], + hasFamily: false, + name: '홍길동', + printMemberNo: 'print_xyz456', + patronState: { + id: 789, + name: '퇴직', + }, + id: 987, + multiTypePatrons: [], + isExpired: false, + isFamilyLogin: false, + }, +}; diff --git a/client/src/components/Layouts/Navigator/index.tsx b/client/src/components/Layouts/Navigator/index.tsx index 388f0fa..46af120 100644 --- a/client/src/components/Layouts/Navigator/index.tsx +++ b/client/src/components/Layouts/Navigator/index.tsx @@ -62,8 +62,7 @@ const Navigator = ({ curRoute, handleRoute }: Props) => { }; const NavigatorWrapper = styled.div` - max-width: 50rem; - width: 100vw; + width: 100%; min-width: 25rem; height: 7rem; ${flex('row', 'center', 'center', 0)}; diff --git a/client/src/hooks/useAuth.tsx b/client/src/hooks/useAuth.tsx index 59a5d47..5650686 100644 --- a/client/src/hooks/useAuth.tsx +++ b/client/src/hooks/useAuth.tsx @@ -1,4 +1,4 @@ -import { authInfoState } from '@/atoms/authInfoState'; +import { authInfoState, dummyAuthData } from '@/atoms/authInfoState'; import { useAtom } from 'jotai'; import { useRouter } from 'next/router'; @@ -6,13 +6,16 @@ const useAuth = () => { const router = useRouter(); const [authInfo, setAuthInfo] = useAtom(authInfoState); - const handleLogin = () => {}; + const handleLogin = () => { + setAuthInfo(dummyAuthData); + router.replace('/'); + }; const checkAuth = () => { if (!authInfo) router.replace('/landing'); }; - return { authInfo, checkAuth }; + return { authInfo, checkAuth, handleLogin }; }; export default useAuth; diff --git a/client/src/pages/login.tsx b/client/src/pages/login.tsx index a7e32a6..aa514e4 100644 --- a/client/src/pages/login.tsx +++ b/client/src/pages/login.tsx @@ -1,6 +1,7 @@ import { RoundButton } from '@/components/Buttons'; import { TextInput } from '@/components/Field'; import { useHeader, useVh } from '@/hooks'; +import useAuth from '@/hooks/useAuth'; import { COLORS } from '@/styles/colors'; import { flex } from '@/styles/tokens'; import { TYPO } from '@/styles/typo'; @@ -13,6 +14,7 @@ import { useLayoutEffect } from 'react'; const Login = () => { const { setHeader } = useHeader(); const { fullPageStyle } = useVh(); + const { handleLogin } = useAuth(); useLayoutEffect(() => { setHeader('로그인'); @@ -31,7 +33,7 @@ const Login = () => { - + 비밀번호를 재설정하고 싶어요. From 723f4dee704441789910c475fa3698e9a4fb5fa6 Mon Sep 17 00:00:00 2001 From: Jun99uu_Lee Date: Sun, 15 Oct 2023 13:32:57 +0900 Subject: [PATCH 13/17] feat: Add all pages (template) --- .../components/Layouts/Frame/FrameHeader.tsx | 6 +++++- .../components/Layouts/Header/TitleHeader.tsx | 8 ++++++-- client/src/hooks/useAuth.tsx | 3 +-- client/src/pages/create/reserve.tsx | 18 ++++++++++++++++++ client/src/pages/create/template.tsx | 18 ++++++++++++++++++ client/src/pages/mypage.tsx | 18 ++++++++++++++++++ client/src/styles/tokens.tsx | 6 ++++++ 7 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 client/src/pages/create/reserve.tsx create mode 100644 client/src/pages/create/template.tsx create mode 100644 client/src/pages/mypage.tsx diff --git a/client/src/components/Layouts/Frame/FrameHeader.tsx b/client/src/components/Layouts/Frame/FrameHeader.tsx index 09218c7..79964fc 100644 --- a/client/src/components/Layouts/Frame/FrameHeader.tsx +++ b/client/src/components/Layouts/Frame/FrameHeader.tsx @@ -10,6 +10,10 @@ const FrameHeader = () => { router.back(); }; + const handleMyPage = () => { + router.push('/mypage'); + }; + switch (router.pathname) { case '/landing': return <>; @@ -17,7 +21,7 @@ const FrameHeader = () => { case '/template': case '/mate': case '/schedule': - return ; + return ; default: return ; } diff --git a/client/src/components/Layouts/Header/TitleHeader.tsx b/client/src/components/Layouts/Header/TitleHeader.tsx index 87ad0c5..18815b3 100644 --- a/client/src/components/Layouts/Header/TitleHeader.tsx +++ b/client/src/components/Layouts/Header/TitleHeader.tsx @@ -6,11 +6,15 @@ import HumanIcon from '@/assets/svg/human.svg'; import { css } from '@emotion/react'; import { mq } from '@/styles/breakpoints'; -const TitleHeader = () => { +interface Props { + mypageHandler?: () => void; +} + +const TitleHeader = ({ mypageHandler }: Props) => { return ( SSUDOBI - + ); }; diff --git a/client/src/hooks/useAuth.tsx b/client/src/hooks/useAuth.tsx index 5650686..9d3e75f 100644 --- a/client/src/hooks/useAuth.tsx +++ b/client/src/hooks/useAuth.tsx @@ -1,4 +1,4 @@ -import { authInfoState, dummyAuthData } from '@/atoms/authInfoState'; +import { authInfoState } from '@/atoms/authInfoState'; import { useAtom } from 'jotai'; import { useRouter } from 'next/router'; @@ -7,7 +7,6 @@ const useAuth = () => { const [authInfo, setAuthInfo] = useAtom(authInfoState); const handleLogin = () => { - setAuthInfo(dummyAuthData); router.replace('/'); }; diff --git a/client/src/pages/create/reserve.tsx b/client/src/pages/create/reserve.tsx new file mode 100644 index 0000000..d9557d9 --- /dev/null +++ b/client/src/pages/create/reserve.tsx @@ -0,0 +1,18 @@ +import { useHeader } from '@/hooks'; +import { PageContainer } from '@/styles/tokens'; +import { useLayoutEffect } from 'react'; + +/** + * 예약하기 페이지 + */ +const Reserve = () => { + const { setHeader } = useHeader(); + + useLayoutEffect(() => { + setHeader('예약하기'); + }, []); + + return ; +}; + +export default Reserve; diff --git a/client/src/pages/create/template.tsx b/client/src/pages/create/template.tsx new file mode 100644 index 0000000..0b48331 --- /dev/null +++ b/client/src/pages/create/template.tsx @@ -0,0 +1,18 @@ +import { useHeader } from '@/hooks'; +import { PageContainer } from '@/styles/tokens'; +import { useLayoutEffect } from 'react'; + +/** + * 템플릿 추가하기 페이지 + */ +const Template = () => { + const { setHeader } = useHeader(); + + useLayoutEffect(() => { + setHeader('템플릿 추가하기'); + }, []); + + return ; +}; + +export default Template; diff --git a/client/src/pages/mypage.tsx b/client/src/pages/mypage.tsx new file mode 100644 index 0000000..b02c7f6 --- /dev/null +++ b/client/src/pages/mypage.tsx @@ -0,0 +1,18 @@ +import { useHeader } from '@/hooks'; +import { PageContainer } from '@/styles/tokens'; +import { useLayoutEffect } from 'react'; + +/** + * 마이페이지 + */ +const Mypage = () => { + const { setHeader } = useHeader(); + + useLayoutEffect(() => { + setHeader('마이페이지'); + }, []); + + return ; +}; + +export default Mypage; diff --git a/client/src/styles/tokens.tsx b/client/src/styles/tokens.tsx index 9ef7a51..5a150e0 100644 --- a/client/src/styles/tokens.tsx +++ b/client/src/styles/tokens.tsx @@ -1,4 +1,5 @@ import { css } from '@emotion/react'; +import styled from '@emotion/styled'; type Direction = 'row' | 'column'; type JustifyContent = @@ -64,3 +65,8 @@ export const transition = (duration: string, animationType = 'linear') => { transition: all ${duration}; `; }; + +export const PageContainer = styled.div` + width: 100%; + padding: 1rem 2.7rem; +`; From 734a4cd5e6523691d6354dd1a30031fe05a7eed6 Mon Sep 17 00:00:00 2001 From: Jun99uu_Lee Date: Mon, 16 Oct 2023 17:10:44 +0900 Subject: [PATCH 14/17] feat: axios object setting --- client/src/@types/common.d.ts | 41 +++++++++++++ client/src/apis/AxiosCreate.ts | 87 ++++++++++++++++++++++++++++ client/src/utils/lib/tokenHandler.ts | 20 +++++++ 3 files changed, 148 insertions(+) create mode 100644 client/src/@types/common.d.ts create mode 100644 client/src/apis/AxiosCreate.ts create mode 100644 client/src/utils/lib/tokenHandler.ts diff --git a/client/src/@types/common.d.ts b/client/src/@types/common.d.ts new file mode 100644 index 0000000..3b39bbd --- /dev/null +++ b/client/src/@types/common.d.ts @@ -0,0 +1,41 @@ +declare module 'AxiosCommon' { + import { + AxiosInstance, + AxiosInterceptorManager, + AxiosRequestConfig, + AxiosResponse, + } from 'axios'; + + type CustomAxiosResponse = { + response?: T; + refreshToken?: string; + }; + + export interface CustomAxiosInterface extends AxiosInstance { + interceptors: { + request: AxiosInterceptorManager; + response: AxiosInterceptorManager>; + }; + + get(url: string, config?: AxiosRequestConfig): Promise; + delete(url: string, config?: AxiosRequestConfig): Promise; + post(url: string, data?: any, config?: AxiosRequestConfig): Promise; + put(url: string, data?: any, config?: AxiosRequestConfig): Promise; + patch(url: string, data?: any, config?: AxiosRequestConfig): Promise; + } + + export interface CommonRequest { + status: number; + message: string; + success?: boolean; + data?: any; + } + interface APIDataResponse { + data: T; + } + export interface CommonResponse { + data: APIDataResponse; + status: number; + statusText: string; + } +} diff --git a/client/src/apis/AxiosCreate.ts b/client/src/apis/AxiosCreate.ts new file mode 100644 index 0000000..d84a3dd --- /dev/null +++ b/client/src/apis/AxiosCreate.ts @@ -0,0 +1,87 @@ +import { getAccessToken, removeAccessToken } from '@/utils/lib/tokenHandler'; +import axios, { AxiosRequestConfig } from 'axios'; +import { CustomAxiosInterface, CommonResponse } from 'AxiosCommon'; + +const apiClient: CustomAxiosInterface = axios.create({ + headers: { + 'Content-Type': 'application/json', + }, + baseURL: `${process.env.NEXT_PUBLIC_BASE_URL}`, +}); + +// 요청 interceptor +apiClient.interceptors.request.use( + async (config: any) => { + const token = getAccessToken(); + config.headers = { + Accept: 'application/json, text/plain, */*', + 'pyxis-auth-token': `${token}`, + }; + return config; + }, + (error: unknown) => { + console.log(error); + return Promise.reject(error); + }, +); + +// 응답 interceptor +apiClient.interceptors.response.use( + async (config: any) => { + return config; + }, + async (error: any) => { + if (error.response.status === 400) { + removeAccessToken(); //만료시 토큰 제거 (업데이트가 없기 떄문) + } + return Promise.reject(error); + }, +); + +const Get = async (url: string, config?: AxiosRequestConfig): Promise => { + const response = await apiClient.get>(url, config); + return response.data.data; +}; + +const Post = async ( + url: string, + data?: any, + config?: AxiosRequestConfig, +): Promise => { + const response = await apiClient.post>(url, data, config); + return response.data.data; +}; + +const Put = async ( + url: string, + data?: any, + config?: AxiosRequestConfig, +): Promise => { + const response = await apiClient.put>(url, data, config); + return response.data.data; +}; + +const Patch = async ( + url: string, + data?: any, + config?: AxiosRequestConfig, +): Promise => { + const response = await apiClient.patch>(url, data, config); + return response.data.data; +}; + +const Delete = async ( + url: string, + config?: AxiosRequestConfig, +): Promise => { + const response = await apiClient.delete>(url, config); + return response.data.data; +}; + +export { + Get as get, + Post as post, + Put as put, + Patch as patch, + Delete as remove, +}; diff --git a/client/src/utils/lib/tokenHandler.ts b/client/src/utils/lib/tokenHandler.ts new file mode 100644 index 0000000..c26034d --- /dev/null +++ b/client/src/utils/lib/tokenHandler.ts @@ -0,0 +1,20 @@ +/** + * 액세스 토큰 가져오기 + */ +export const getAccessToken = () => { + return localStorage.getItem('ACESS_TOKEN'); +}; + +/** + * 액세스 토큰 업데이트 + */ +export const updateAccessToken = (token: string) => { + localStorage.setItem('ACCESS_TOKEN', token); +}; + +/** + * 토큰 삭제 (로그아웃) + */ +export const removeAccessToken = () => { + localStorage.removeItem('ACESS_TOKEN'); +}; From 0ec0edccb42bc3cb36b29196a043d8209f18de47 Mon Sep 17 00:00:00 2001 From: Jun99uu_Lee Date: Mon, 16 Oct 2023 17:10:55 +0900 Subject: [PATCH 15/17] chore: add tanstack query dep --- client/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/client/package.json b/client/package.json index 5fa1b3e..335ee00 100644 --- a/client/package.json +++ b/client/package.json @@ -16,6 +16,7 @@ "@fortawesome/fontawesome-svg-core": "^6.4.2", "@fortawesome/free-solid-svg-icons": "^6.4.2", "@fortawesome/react-fontawesome": "^0.2.0", + "@tanstack/react-query": "^4.36.1", "@types/lodash": "^4.14.199", "axios": "^1.5.1", "jotai": "^2.4.3", From 938a6bfc43f44459f19d538dd90ad42b81279091 Mon Sep 17 00:00:00 2001 From: Jun99uu_Lee Date: Mon, 16 Oct 2023 18:40:04 +0900 Subject: [PATCH 16/17] feat: Add useInput hook --- client/src/hooks/useInput.tsx | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 client/src/hooks/useInput.tsx diff --git a/client/src/hooks/useInput.tsx b/client/src/hooks/useInput.tsx new file mode 100644 index 0000000..bc72585 --- /dev/null +++ b/client/src/hooks/useInput.tsx @@ -0,0 +1,33 @@ +import { ChangeEvent, useState } from 'react'; +import { set } from 'lodash'; + +type UseInputReturnType = { + values: T; + handleChange: (event: ChangeEvent) => void; + setValue: (path: K, value: T[K]) => void; +}; + +const useInput = ( + initialValues: T, +): UseInputReturnType => { + const [values, setValues] = useState(initialValues); + + const handleChange = (event: ChangeEvent): void => { + const { name, value } = event.target; + const updatedValues = set({ ...values }, name, value); + setValues(updatedValues as T); + }; + + const setValue = (path: K, value: T[K]): void => { + const updatedValues = set({ ...values }, path as string, value); + setValues(updatedValues as T); + }; + + return { + values, + handleChange, + setValue, + }; +}; + +export default useInput; From 16049c6c8352fe678d34d91aa0c59713984c028a Mon Sep 17 00:00:00 2001 From: Jun99uu_Lee Date: Mon, 16 Oct 2023 18:40:21 +0900 Subject: [PATCH 17/17] feat: Add login handler + auto login handler --- client/src/apis/auth.ts | 23 +++++++++++ client/src/atoms/authInfoState.ts | 60 +++-------------------------- client/src/constants/queryKey.ts | 3 ++ client/src/hooks/index.tsx | 2 + client/src/hooks/useAuth.tsx | 52 ++++++++++++++++++++++--- client/src/pages/_app.tsx | 17 +++++--- client/src/pages/login.tsx | 30 ++++++++++++--- client/src/utils/lib/infoHandler.ts | 39 +++++++++++++++++++ 8 files changed, 155 insertions(+), 71 deletions(-) create mode 100644 client/src/apis/auth.ts create mode 100644 client/src/constants/queryKey.ts create mode 100644 client/src/utils/lib/infoHandler.ts diff --git a/client/src/apis/auth.ts b/client/src/apis/auth.ts new file mode 100644 index 0000000..3fb8796 --- /dev/null +++ b/client/src/apis/auth.ts @@ -0,0 +1,23 @@ +import { AuthData, AuthResponse } from 'Auth'; +import axios, { AxiosResponse } from 'axios'; + +class AuthApi { + /** 로그인 */ + login = async (loginId: string, password: string): Promise => { + const data: AxiosResponse = await axios({ + method: 'post', + url: `${process.env.NEXT_PUBLIC_BASE_URL}/login`, + headers: { + 'Content-Type': 'application/json', + }, + data: { + loginId, + password, + }, + }); + + return data.data.data; + }; +} + +export default AuthApi; diff --git a/client/src/atoms/authInfoState.ts b/client/src/atoms/authInfoState.ts index 54802ba..f6ccaf8 100644 --- a/client/src/atoms/authInfoState.ts +++ b/client/src/atoms/authInfoState.ts @@ -1,58 +1,8 @@ -import { AuthResponse } from 'Auth'; import { atom } from 'jotai'; -export const authInfoState = atom(null); - -export const dummyAuthData: AuthResponse = { - success: true, - code: 'successCode_xyz123', - message: '로그인되었습니다.', - data: { - availableHomepages: [42, 69], - isPrivacyPolicyAgree: true, - privacyPolicyAgreePeriod: 3, - dept: { - id: 456, - code: 'deptCode_abc789', - name: '과학학부', - }, - accessToken: 'xyzabc789ghi', - parentDept: { - id: 457, - code: 'parentDeptCode_def456', - name: '자연대학', - }, - branch: { - id: 123, - name: '서부도서관', - alias: '서부', - libraryCode: 'libCode_ghi123', - sortOrder: 2, - }, - showMobileMain: false, - memberNo: 'member_xyz789', - alternativeId: 'altid_ghi123', - lastUpdated: '2023-07-27 15:23:45', - branchGroup: { - id: 24, - name: '서울대학교', - }, - isPortalLogin: false, - patronType: { - id: 34, - name: '교수', - }, - disableServices: ['ABC', 'DEF', 'GHI'], - hasFamily: false, - name: '홍길동', - printMemberNo: 'print_xyz456', - patronState: { - id: 789, - name: '퇴직', - }, - id: 987, - multiTypePatrons: [], - isExpired: false, - isFamilyLogin: false, - }, +export type authInfoType = { + name: string; + sId: string; }; + +export const authInfoState = atom(null); diff --git a/client/src/constants/queryKey.ts b/client/src/constants/queryKey.ts new file mode 100644 index 0000000..0e70367 --- /dev/null +++ b/client/src/constants/queryKey.ts @@ -0,0 +1,3 @@ +export const QUERY_KEYS = { + login: 'LOGIN', +}; diff --git a/client/src/hooks/index.tsx b/client/src/hooks/index.tsx index e86ae8f..f6a181b 100644 --- a/client/src/hooks/index.tsx +++ b/client/src/hooks/index.tsx @@ -1,2 +1,4 @@ export { default as useVh } from './useVh'; export { default as useHeader } from './useHeader'; +export { default as useInput } from './useInput'; +export { default as useAuth } from './useAuth'; diff --git a/client/src/hooks/useAuth.tsx b/client/src/hooks/useAuth.tsx index 9d3e75f..2361d2b 100644 --- a/client/src/hooks/useAuth.tsx +++ b/client/src/hooks/useAuth.tsx @@ -1,20 +1,62 @@ +import AuthApi from '@/apis/auth'; import { authInfoState } from '@/atoms/authInfoState'; +import { getUserInfo, updateUserInfo } from '@/utils/lib/infoHandler'; +import { updateAccessToken } from '@/utils/lib/tokenHandler'; import { useAtom } from 'jotai'; import { useRouter } from 'next/router'; const useAuth = () => { + const authApi = new AuthApi(); + const router = useRouter(); const [authInfo, setAuthInfo] = useAtom(authInfoState); - const handleLogin = () => { - router.replace('/'); + /** + * 로그인 함수 + */ + const handleLogin = async (id: string, password: string) => { + try { + const data = await authApi.login(id, password); + console.log(data); + setAuthInfo({ + name: data.name, + sId: data.printMemberNo, + }); + updateAccessToken(data.accessToken); + updateUserInfo(data.name, data.printMemberNo, id, password); + router.replace('/'); + } catch (err) { + console.log(err); + } }; - const checkAuth = () => { - if (!authInfo) router.replace('/landing'); + /** + * 자동 로그인 함수 + */ + const autoLogin = async () => { + try { + const userInfo = getUserInfo(); + if (!userInfo) throw new Error('Empty user info'); + const data = await authApi.login(userInfo.loginId, userInfo.password); + console.log(data); + setAuthInfo({ + name: data.name, + sId: data.printMemberNo, + }); + updateAccessToken(data.accessToken); + updateUserInfo( + data.name, + data.printMemberNo, + userInfo.loginId, + userInfo.password, + ); + } catch (err) { + console.log(err); + router.replace('/landing'); + } }; - return { authInfo, checkAuth, handleLogin }; + return { authInfo, autoLogin, handleLogin }; }; export default useAuth; diff --git a/client/src/pages/_app.tsx b/client/src/pages/_app.tsx index a17f511..ce88842 100644 --- a/client/src/pages/_app.tsx +++ b/client/src/pages/_app.tsx @@ -7,9 +7,12 @@ import { Global, css } from '@emotion/react'; import reset from '@/styles/reset'; import { useRouter } from 'next/router'; import { COLORS } from '@/styles/colors'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; + +const queryClient = new QueryClient(); export default function App({ Component, pageProps }: AppProps) { - const { checkAuth } = useAuth(); + const { autoLogin } = useAuth(); const router = useRouter(); const getBgColor = (pathname: string) => { @@ -22,14 +25,16 @@ export default function App({ Component, pageProps }: AppProps) { }; useEffect(() => { - checkAuth(); + autoLogin(); }, []); return ( - - - - + + + + + + ); } diff --git a/client/src/pages/login.tsx b/client/src/pages/login.tsx index aa514e4..687ef06 100644 --- a/client/src/pages/login.tsx +++ b/client/src/pages/login.tsx @@ -1,13 +1,17 @@ import { RoundButton } from '@/components/Buttons'; import { TextInput } from '@/components/Field'; -import { useHeader, useVh } from '@/hooks'; -import useAuth from '@/hooks/useAuth'; +import { useAuth, useHeader, useInput, useVh } from '@/hooks'; import { COLORS } from '@/styles/colors'; import { flex } from '@/styles/tokens'; import { TYPO } from '@/styles/typo'; import styled from '@emotion/styled'; import { useLayoutEffect } from 'react'; +type FormData = { + id: string; + password: string; +}; + /** * 로그인 페이지 */ @@ -15,6 +19,7 @@ const Login = () => { const { setHeader } = useHeader(); const { fullPageStyle } = useVh(); const { handleLogin } = useAuth(); + const { values, handleChange } = useInput({ id: '', password: '' }); useLayoutEffect(() => { setHeader('로그인'); @@ -25,15 +30,30 @@ const Login = () => { 학번 - + 비밀번호 - + - + handleLogin(values.id, values.password)} + /> 비밀번호를 재설정하고 싶어요. diff --git a/client/src/utils/lib/infoHandler.ts b/client/src/utils/lib/infoHandler.ts new file mode 100644 index 0000000..ae62306 --- /dev/null +++ b/client/src/utils/lib/infoHandler.ts @@ -0,0 +1,39 @@ +export type StudentInfo = { + name: string; + sId: string; + loginId: string; + password: string; +}; + +/** + * 정보 가져오기 + */ +export const getUserInfo = (): StudentInfo | null => { + const data = localStorage.getItem('USER_INFO'); + return data ? JSON.parse(data) : null; +}; + +/** + * 정보 업데이트 + */ +export const updateUserInfo = ( + name: string, + sId: string, + loginId: string, + password: string, +) => { + const data = { + name, + sId, + loginId, + password, + }; + localStorage.setItem('USER_INFO', JSON.stringify(data)); +}; + +/** + * 정보 삭제 (로그아웃) + */ +export const removeUserInfo = () => { + localStorage.removeItem('USER_INFO'); +};