diff --git a/src/front/App.tsx b/src/front/App.tsx index ce6e4bd..9fcd756 100644 --- a/src/front/App.tsx +++ b/src/front/App.tsx @@ -3,7 +3,7 @@ import { LoginContext, useInitLoginContext } from '@context/LoginContext'; import LoginedRoute from './LoginedRoute'; import Header from '@components/Header'; import Footer from '@components/Footer'; -import Main from '@pages/Main'; +import Home from '@pages/Home'; import Login from '@pages/Login'; import Signup from '@pages/Signup'; import MyWordbook from '@pages/MyWordbook'; @@ -17,7 +17,7 @@ function App() {
- } /> + } /> } isLoginPage={false} />} /> } isLoginPage={false} />} /> } />} /> diff --git a/src/front/assets/createWordbook.gif b/src/front/assets/createWordbook.gif new file mode 100644 index 0000000..62166f9 Binary files /dev/null and b/src/front/assets/createWordbook.gif differ diff --git a/src/front/assets/edit.png b/src/front/assets/edit.png new file mode 100644 index 0000000..9132ce0 Binary files /dev/null and b/src/front/assets/edit.png differ diff --git a/src/front/assets/study.gif b/src/front/assets/study.gif new file mode 100644 index 0000000..15938ed Binary files /dev/null and b/src/front/assets/study.gif differ diff --git a/src/front/assets/test.gif b/src/front/assets/test.gif new file mode 100644 index 0000000..3f78f9a Binary files /dev/null and b/src/front/assets/test.gif differ diff --git a/src/front/components/AddWordbook.tsx b/src/front/components/AddWordbook.tsx index 383002c..977aa97 100644 --- a/src/front/components/AddWordbook.tsx +++ b/src/front/components/AddWordbook.tsx @@ -22,10 +22,7 @@ function AddWordbookList({setNewWordbook}: {setNewWordbook: React.Dispatch - - menu_book - - + diff --git a/src/front/components/Footer.tsx b/src/front/components/Footer.tsx index 32777a9..570221b 100644 --- a/src/front/components/Footer.tsx +++ b/src/front/components/Footer.tsx @@ -2,18 +2,22 @@ import { styled } from 'styled-components'; const FooterContainer = styled.footer` background-color: #f8f9fa; - padding: 20px; margin-top: auto; - text-align: center; - width: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 80px; `; function Footer() { return ( - © 2024. Raipen all rights reserved. -
- contact: raipendalk@gmail.com +
+ © 2024. Raipen all rights reserved. +
+ contact: raipendalk@gmail.com +
); } diff --git a/src/front/components/Header.tsx b/src/front/components/Header.tsx index 1585a6c..3427cf8 100644 --- a/src/front/components/Header.tsx +++ b/src/front/components/Header.tsx @@ -59,7 +59,7 @@ function Header() { const { isLogined, loading, logout} = useContext(LoginContext); return ( - + 로고 diff --git a/src/front/components/index.tsx b/src/front/components/index.tsx index 242c4dd..1320522 100644 --- a/src/front/components/index.tsx +++ b/src/front/components/index.tsx @@ -1,4 +1,4 @@ -import {styled, css} from 'styled-components'; +import {styled, css, keyframes } from 'styled-components'; const FlexRowCenter = css` display: flex; @@ -100,6 +100,7 @@ export const HeaderButton = ReverseButtonWithHoverAnimation; export const ButtonContainingIcon = styled.button<{ $margin?: string }>` ${ButtonCss}; ${MainColorBackground}; + word-break: keep-all; padding: 5px 20px; ${props => props.$margin && `margin: ${props.$margin};`} gap: 10px; @@ -123,11 +124,33 @@ export const MainContainer = styled.main<{ $background?: string, $flexdirection? width: 100%; display: flex; flex-direction: ${props => props.$flexdirection || 'column'}; - background-color: ${props => props.$background || 'white'}; + background: ${props => props.$background || 'none'}; gap: 20px; flex-wrap: wrap; `; +const showUp = keyframes` + 0% { + transform: translateY(100%); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } +`; + +export const HomeContainer = styled(MainContainer)` + flex-grow: 1; + overflow: hidden; + flex-wrap: nowrap; + position: relative; + height: calc(100vh - 70px); + &>*{ + animation: ${showUp} 0.8s ease-in-out; + } +`; + export const LoginContainer = styled.form` padding: 20px 30px; margin: 0 auto; @@ -166,6 +189,7 @@ export const WordbookListContainer = styled.div` margin-left: auto; @media (max-width: 600px) { width: 100%; + padding: 0; } `; @@ -392,3 +416,58 @@ export const InputWithLabelContainer = styled.div` &>input { } `; + +const PageLink = styled.div` + width: fit-content; + gap: 5px; + padding: 5px 10px; + ${clickable}; + border-radius: 5px; + &:hover { + color: var(--main-color); + background-color: RGBA(0,0,0,0.1); + } + &>span { + text-decoration: underline; + text-underline-offset: 2px; + } + &>.material-icons-sharp { + font-size: 1rem; + text-decoration: none; + margin-right: 5px; + } +`; + +export const NewPageLink = ({text}:{text: string|undefined}) => { + return ( + +
open_in_new
+ {text} +
+ ); +} + +const BounceAnimation = keyframes` + 0%, 100% { + transform: translate(-50%, 0); + } + 50% { + transform: translate(-50%, 10px); + } +`; + +export const ScrollBottom = styled.div` + ${clickable}; + position: absolute; + bottom: 10px; + left: 50%; + font-size: 3rem; + width: fit-content; + animation: ${BounceAnimation} 1s infinite; +`; + +export const ImageContainer = styled.img` + border: 3px solid var(--main-color); + border-radius: 10px; +`; + diff --git a/src/front/hooks/useMainPage.ts b/src/front/hooks/useMainPage.ts new file mode 100644 index 0000000..ea7ed48 --- /dev/null +++ b/src/front/hooks/useMainPage.ts @@ -0,0 +1,73 @@ +import { useEffect, useState, useCallback, useMemo } from 'react'; + +function useMainPage(pageList: Array) { + const [page, setPage] = useState(0); + const [isScrolling, setIsScrolling] = useState(false); + + const pageUp = useCallback(() => { + if (page < pageList.length - 1) setPage(page + 1); + }, [page]); + + const pageDown = useCallback(() => { + if (page > 0) setPage(page - 1); + }, [page]); + + const pageControl = useCallback((type: 'up' | 'down') => { + if (isScrolling) return; + setIsScrolling(true); + if (type === 'up') pageUp(); + else pageDown(); + setTimeout(() => { + setIsScrolling(false); + }, 1000); + }, [isScrolling, pageUp, pageDown]); + + const wheelEvent = useCallback((e: WheelEvent) => { + if(page === pageList.length - 1 && e.deltaY > 0 && !isScrolling) return; + e.preventDefault(); + if (e.deltaY > 0) return pageControl('up'); + pageControl('down'); + }, [pageControl]); + + const touchEvent = useCallback(() => { + let startY = 0; + const touchStart = (e: TouchEvent) => { + startY = e.touches[0].clientY; + } + const touchEnd = (e: TouchEvent) => { + const endY = e.changedTouches[0].clientY; + if(startY === 0) return; + if (startY - endY > 50) return pageControl('up'); + if (startY - endY < -50) return pageControl('down'); + } + return { touchStart, touchEnd }; + }, [pageControl]); + + const touchMove = useCallback((e: TouchEvent) => { + if(page === pageList.length - 1 && !isScrolling) return; + if(page === 0 && !isScrolling) return; + e.preventDefault(); + }, [isScrolling, page]); + + useEffect(() => { + window.addEventListener('wheel', wheelEvent, { passive: false }); + const { touchStart, touchEnd } = touchEvent(); + window.addEventListener('touchstart', touchStart); + window.addEventListener('touchend', touchEnd); + window.addEventListener('touchmove', touchMove, { passive: false }); + return () => { + window.removeEventListener('wheel', wheelEvent); + window.removeEventListener('touchstart', touchStart); + window.removeEventListener('touchend', touchEnd); + window.removeEventListener('touchmove', touchMove); + }; + }, [wheelEvent, touchEvent]); + + useEffect(()=> { + scrollTo(0,0); + }, [page]); + + return useMemo(()=>({page,info:pageList[page], pageUp}), [page, pageList, pageUp]); +} + +export default useMainPage; diff --git a/src/front/pages/Home.tsx b/src/front/pages/Home.tsx new file mode 100644 index 0000000..9250c78 --- /dev/null +++ b/src/front/pages/Home.tsx @@ -0,0 +1,54 @@ +import { HomeContainer,NewPageLink, ScrollBottom,ImageContainer, ButtonContainingIcon } from '@components'; +import useMainPage from '@hooks/useMainPage'; +import { pageList, ContentsType } from '@utils/homeList'; +import { Link } from 'react-router-dom'; + +function Home() { + const {page,info,pageUp} = useMainPage(pageList); + + const a = ( + <> +

{info.title}

+ {info.contents.map((content, index) => { + switch(content.type) { + case ContentsType.LINK: + return ( + + + + ); + case ContentsType.TEXT: + return

{content.text}

; + case ContentsType.IMAGE: + return ; + case ContentsType.BUTTON: + return ( + + + {content.text} + + + ); + } + })} + {page!==pageList.length -1 && + keyboard_double_arrow_down + } + + ) + + if(page%2 === 0) return ( + + + {a} + + ); + + return ( + + {a} + + ); +} + +export default Home; diff --git a/src/front/pages/Main.tsx b/src/front/pages/Main.tsx deleted file mode 100644 index c44ad05..0000000 --- a/src/front/pages/Main.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { MainContainer } from '@components'; -import { useEffect, useState } from 'react'; -import { Link } from 'react-router-dom'; - -function Main() { - const [page, setPage] = useState(0); - const [isScrolling, setIsScrolling] = useState(false); - const pageList = [ -
-

page1

- 단어장 -
, -
-

page2

- 단어장 -
, -
-

page3

- 단어장 -
, - ]; - useEffect(() => { - window.scrollTo(0, 0); - const wheelEvent = (e: WheelEvent) => { - e.preventDefault(); - if(isScrolling) return; - if(e.deltaY > 0 && page == pageList.length - 1) return; - if(e.deltaY < 0 && page == 0) return; - setIsScrolling(true); - if(e.deltaY > 0) setPage(page + 1); - else setPage(page - 1); - setTimeout(() => { - setIsScrolling(false); - }, 1000); - } - window.addEventListener('wheel', wheelEvent, {passive: false}); - return () => { - window.removeEventListener('wheel', wheelEvent); - }; - }, [page, isScrolling]); - - console.log(page); - - return ( - - {pageList[page]} - - ); -} - -export default Main; diff --git a/src/front/utils/homeList.ts b/src/front/utils/homeList.ts new file mode 100644 index 0000000..42dce7e --- /dev/null +++ b/src/front/utils/homeList.ts @@ -0,0 +1,74 @@ +import createWordbook from '../assets/createWordbook.gif'; +import edit from '../assets/edit.png'; +import study from '../assets/study.gif'; +import test from '../assets/test.gif'; + +export const enum ContentsType { + LINK = 0, + TEXT = 1, + IMAGE = 2, + BUTTON = 3, +} +export const pageList = [ + { + background: 'linear-gradient(180deg, #f8f9fa 0%, #FFD6E0 100%)', + title: '가장 효율적인 영단어 암기 서비스', + contents: [ + { type: ContentsType.TEXT, text: '"토익독학 길잡이"님의 단어 암기 노하우를 Voca Hub로 사용해보세요.\n'+ + '단어를 자주 보는 것만으로 단어를 보고 바로 떠올리는 능력을 키울 수 있습니다.\n'+ + '단어장을 만들고, 단어를 외우고, 테스트를 통해 단어를 확인하세요.\n'+ + '뜻을 가려두고 단어의 뜻이 떠오르지 않을 때마다 체크를 추가하고 다시 외워보세요.' }, + { type: ContentsType.LINK, src: 'https://www.youtube.com/watch?v=HD79q6aXjPA',text: '"토익독학 길잡이"님의 영단어 암기법 영상 보러가기' }, + { type: ContentsType.BUTTON, text: '시작하기', link: '/mywordbook' }, + ] + },{ + background: "#FFD6E0", + title: '단어장 만들기', + contents: [ + { type: ContentsType.TEXT, text: '"내 단어장"에서 단어장을 만들어 보세요.\n'+ + '단어장은 "Day1", "Day2"처럼 암기하려는 책의 목차도 좋고, "Day 1-1회독"과 같이 암기 횟수를 표시해도 좋습니다.\n'+ + '매일 새로운 단어장을 만들고 단어를 외워보세요. 프로필에 공부한 날이 기록됩니다.' }, + { type: ContentsType.IMAGE, src: createWordbook }, + ] + },{ + background: '#eedcff', + title: '단어 추가하기', + contents: [ + { type: ContentsType.TEXT, text: '단어장을 만들었다면 단어를 추가해보세요.\n'+ + '영단어와 뜻을 입력해보세요. 단어마다 여러 개의 뜻을 입력할 수 있고, 비슷한 뜻은 한 칸에 ,(쉼표)로 구분해서 입력할 수도 있습니다.\n'+ + 'tab키를 사용하여 더 빠르게 입력할 수 있습니다.' }, + { type: ContentsType.IMAGE, src: edit }, + ] + },{ + background: '#d1ecff', + title: '단어 암기하기', + contents: [ + { type: ContentsType.TEXT, text: '단어장을 만들고 단어를 추가했다면, 단어를 외워보세요.\n'+ + '뜻을 모두 켜두고 단어를 외운 후, 뜻을 가려두고 단어의 뜻을 떠올려 보세요.\n'+ + '단어의 뜻이 떠오르지 않을 때마다 체크를 추가하고 체크한 단어들만 다시 외우는 과정을 반복해보세요.' }, + { type: ContentsType.IMAGE, src: study }, + ] + },{ + background: '#ccfdde', + title: '테스트', + contents: [ + { type: ContentsType.TEXT, text: '여러 번 체크한 단어들을 다 외웠다면, 테스트를 통해 전체 단어들을 확인해보세요.\n'+ + '단어를 보고 뜻을 맞추는 테스트로 뜻의 순서는 상관 없습니다.\n'+ + '뜻이 여러 개인 단어는 뜻을 모두 맞추어야 통과됩니다.' }, + { type: ContentsType.IMAGE, src: test }, + ] + },{ + background: 'linear-gradient(180deg, #ffc5d2 0%, #f8f9fa 100%)', + title: 'Voca Hub 시작하기', + contents: [ + { type: ContentsType.TEXT, text: '이제 Voca Hub를 시작해보세요.\n'+ + '단어장을 만들고, 단어를 추가하고, 단어를 외우고, 테스트를 통해 단어를 확인하세요.\n'+ + '단어를 외우는 방법은 다양합니다. 단어와 뜻을 소리내서 읽어보기, 단어 쓰기, 예문 찾아보기 등등 여러가지 방법을 사용해 단어를 외우고 Voca Hub을 통해 단어가 바로 떠오르는지 확인해보세요.' }, + { type: ContentsType.BUTTON, text: 'VOCA HUB 시작하기', link: '/mywordbook' }, + ] + }, +] as { + background: string; + title: string; + contents: { type: ContentsType; text?: string; src?: string; link?: string }[]; +}[];