diff --git a/public/assets/genre/art.svg b/public/assets/genre/art.svg new file mode 100644 index 00000000..2d427daf --- /dev/null +++ b/public/assets/genre/art.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/public/assets/genre/humanity.svg b/public/assets/genre/humanity.svg new file mode 100644 index 00000000..f3b69c48 --- /dev/null +++ b/public/assets/genre/humanity.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/assets/genre/literature.svg b/public/assets/genre/literature.svg new file mode 100644 index 00000000..f2499f44 --- /dev/null +++ b/public/assets/genre/literature.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/public/assets/genre/science.svg b/public/assets/genre/science.svg new file mode 100644 index 00000000..4b248d36 --- /dev/null +++ b/public/assets/genre/science.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/public/assets/genre/socialScience.svg b/public/assets/genre/socialScience.svg new file mode 100644 index 00000000..0f39618f --- /dev/null +++ b/public/assets/genre/socialScience.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/public/genres.json b/public/genres.json new file mode 100644 index 00000000..0571d14e --- /dev/null +++ b/public/genres.json @@ -0,0 +1,37 @@ +[ + { + "id": "literature", + "title": "문학", + "subTitle": "문학가", + "iconUrl": "/assets/genre/literature.svg", + "color": "#A0F8E8" + }, + { + "id": "science", + "title": "과학", + "subTitle": "과학자", + "iconUrl": "/assets/genre/science.svg", + "color": "#C8A5FF" + }, + { + "id": "socialScience", + "title": "사회과학", + "subTitle": "사회학자", + "iconUrl": "/assets/genre/socialScience.svg", + "color": "#FDB770" + }, + { + "id": "art", + "title": "예술", + "subTitle": "예술가", + "iconUrl": "/assets/genre/art.svg", + "color": "#FF8BAC" + }, + { + "id": "humanity", + "title": "인문학", + "subTitle": "철학자", + "iconUrl": "/assets/genre/humanity.svg", + "color": "#A1D5FF" + } +] diff --git a/src/assets/genre/art.svg b/src/assets/genre/art.svg new file mode 100644 index 00000000..2d427daf --- /dev/null +++ b/src/assets/genre/art.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/assets/genre/humanity.svg b/src/assets/genre/humanity.svg new file mode 100644 index 00000000..f3b69c48 --- /dev/null +++ b/src/assets/genre/humanity.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/genre/literature.svg b/src/assets/genre/literature.svg new file mode 100644 index 00000000..f2499f44 --- /dev/null +++ b/src/assets/genre/literature.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/assets/genre/science.svg b/src/assets/genre/science.svg new file mode 100644 index 00000000..4b248d36 --- /dev/null +++ b/src/assets/genre/science.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/assets/genre/socialScience.svg b/src/assets/genre/socialScience.svg new file mode 100644 index 00000000..0f39618f --- /dev/null +++ b/src/assets/genre/socialScience.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/assets/login/google.svg b/src/assets/login/google.svg new file mode 100644 index 00000000..699d5976 --- /dev/null +++ b/src/assets/login/google.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/login/kakao.svg b/src/assets/login/kakao.svg new file mode 100644 index 00000000..3de57c1a --- /dev/null +++ b/src/assets/login/kakao.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/login/logo.svg b/src/assets/login/logo.svg new file mode 100644 index 00000000..66fcdee7 --- /dev/null +++ b/src/assets/login/logo.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/navbar/feed-active.svg b/src/assets/navbar/feed-active.svg new file mode 100644 index 00000000..8aac0fb4 --- /dev/null +++ b/src/assets/navbar/feed-active.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/navbar/feed.svg b/src/assets/navbar/feed.svg new file mode 100644 index 00000000..449b1e2a --- /dev/null +++ b/src/assets/navbar/feed.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/navbar/group-active.svg b/src/assets/navbar/group-active.svg new file mode 100644 index 00000000..2dc1938e --- /dev/null +++ b/src/assets/navbar/group-active.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/navbar/group.svg b/src/assets/navbar/group.svg new file mode 100644 index 00000000..54fdbb17 --- /dev/null +++ b/src/assets/navbar/group.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/navbar/my-active.svg b/src/assets/navbar/my-active.svg new file mode 100644 index 00000000..6e680fc0 --- /dev/null +++ b/src/assets/navbar/my-active.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/navbar/my.svg b/src/assets/navbar/my.svg new file mode 100644 index 00000000..b5b27c62 --- /dev/null +++ b/src/assets/navbar/my.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/navbar/search-active.svg b/src/assets/navbar/search-active.svg new file mode 100644 index 00000000..51b9eccb --- /dev/null +++ b/src/assets/navbar/search-active.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/navbar/search.svg b/src/assets/navbar/search.svg new file mode 100644 index 00000000..24e7cff9 --- /dev/null +++ b/src/assets/navbar/search.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/NavBar.tsx b/src/components/NavBar.tsx new file mode 100644 index 00000000..8fbe632d --- /dev/null +++ b/src/components/NavBar.tsx @@ -0,0 +1,89 @@ +import { useNavigate, useLocation } from 'react-router-dom'; +import styled from '@emotion/styled'; +import FeedIcon from '../assets/navbar/feed.svg'; +import GroupIcon from '../assets/navbar/group.svg'; +import SearchIcon from '../assets/navbar/search.svg'; +import MyIcon from '../assets/navbar/my.svg'; +import FeedIconActive from '../assets/navbar/feed-active.svg'; +import GroupIconActive from '../assets/navbar/group-active.svg'; +import SearchIconActive from '../assets/navbar/search-active.svg'; +import MyIconActive from '../assets/navbar/my-active.svg'; + +const NavWrapper = styled.div` + position: fixed; + bottom: 0; + left: 0; + right: 0; + display: flex; + min-width: 360px; + max-width: 767px; + height: 75px; + margin: 0 auto; + + border-radius: 12px 12px 0px 0px; + border-top: 1px solid #888; + border-right: 1px solid #888; + border-left: 1px solid #888; + background: #121212; + padding: 16px 32px; + justify-content: space-between; + align-items: center; +`; + +const NavItem = styled.div<{ active?: boolean }>` + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 5px; + color: ${({ active }) => (active ? '#6868FF' : '#888')}; + font-size: 12px; + cursor: pointer; + + svg { + display: flex; + width: 24px; + height: 24px; + padding: 4px 2px; + justify-content: center; + align-items: center; + } +`; + +type RouteItem = { + path: string; + label: string; + icon: string; + activeIcon: string; +}; + +const items: RouteItem[] = [ + { path: '/feed', label: '피드', icon: FeedIcon, activeIcon: FeedIconActive }, + { path: '/group', label: '모임', icon: GroupIcon, activeIcon: GroupIconActive }, + { path: '/search', label: '검색', icon: SearchIcon, activeIcon: SearchIconActive }, + { path: '/my', label: '내 정보', icon: MyIcon, activeIcon: MyIconActive }, +]; + +const NavBar = () => { + const navigate = useNavigate(); + const { pathname } = useLocation(); + + return ( + + {items.map(item => { + const isActive = pathname === item.path; + const src = isActive ? item.activeIcon : item.icon; + + return ( + navigate(item.path)}> + {item.label} +
{item.label}
+
+ ); + })} +
+ ); +}; + +export default NavBar; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index c48c0aaf..195a6ff2 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -4,18 +4,22 @@ import { Route, RouterProvider, } from 'react-router-dom'; +import Login from './login/Login'; import Signup from './signup/Signup'; import SignupGenre from './signup/SignupGenre'; import SignupNickname from './signup/SignupNickname'; +import SignupDone from './signup/SignupDone'; const Router = () => { const router = createBrowserRouter( createRoutesFromElements( <> + } /> }> } /> } /> + } /> , ), ); diff --git a/src/pages/login/Login.tsx b/src/pages/login/Login.tsx new file mode 100644 index 00000000..35cb829f --- /dev/null +++ b/src/pages/login/Login.tsx @@ -0,0 +1,76 @@ +import styled from '@emotion/styled'; +import logo from '../../assets/login/logo.svg'; +import KaKao from '../../assets/login/kakao.svg'; +import Google from '../../assets/login/google.svg'; + +const Wrapper = styled.div` + display: flex; + position: relative; + flex-direction: column; + align-items: center; + justify-content: center; + min-width: 320px; + max-width: 767px; + height: 100vh; + margin: 0 auto; + background-color: #121212; +`; + +const ButtonBox = styled.div` + display: flex; + flex-direction: column; + position: absolute; + bottom: 129.75px; + align-items: center; + gap: 20px; + width: 100%; + padding: 0 20px; + color: #121212; + font-size: 16px; + font-style: normal; + font-weight: 600; + line-height: 24px; +`; + +const SocialButton = styled.div<{ bg: string }>` + width: 100%; + display: flex; + justify-content: center; + align-items: center; + padding: 10px 12px; + border-radius: 12px; + background-color: ${({ bg }) => bg}; + gap: 8px; + + color: #121212; + font-size: 16px; + font-weight: 600; + line-height: 24px; + cursor: pointer; +`; + +const Login = () => { + const handleKakaoLogin = () => { + return; + }; + + const handleGoogleLogin = () => { + return; + }; + + return ( + + + + + 카카오계정 로그인 + + + 구글계정 로그인 + + + + ); +}; + +export default Login; diff --git a/src/pages/signup/Header.tsx b/src/pages/signup/Header.tsx index 4f4ad09b..e07af210 100644 --- a/src/pages/signup/Header.tsx +++ b/src/pages/signup/Header.tsx @@ -7,7 +7,7 @@ const HeaderWrapper = styled.div` left: 0; right: 0; z-index: 100; - max-width: 768px; + max-width: 767px; margin: 0 auto; padding: 16px 20px; @@ -17,7 +17,7 @@ const HeaderWrapper = styled.div` font-style: normal; font-weight: 700; } - + /* .next { width: 49px; height: 28px; @@ -32,7 +32,24 @@ const HeaderWrapper = styled.div` font-style: normal; font-weight: 600; line-height: 20px; - } + } */ +`; + +const NextButton = styled.div<{ active: boolean }>` + cursor: ${({ active }) => (active ? 'pointer' : 'default')}; + width: 49px; + height: 28px; + padding: 4px 12px; + align-items: center; + border-radius: 20px; + background: ${({ active }) => (active ? '#6868FF' : '#888')}; + + color: #fefefe; + text-align: center; + font-size: 14px; + font-style: normal; + font-weight: 600; + line-height: 20px; `; const InnerHeader = styled.div` @@ -45,23 +62,35 @@ const InnerHeader = styled.div` `; type HeaderProps = { - title: string; + title?: string; leftIcon?: React.ReactNode; rightButton?: React.ReactNode; + isNextActive?: boolean; onLeftClick?: (e: React.MouseEvent) => void; onRightClick?: (e: React.MouseEvent) => void; }; -const Header = ({ leftIcon, title, rightButton, onLeftClick, onRightClick }: HeaderProps) => ( +const Header = ({ + leftIcon, + title, + rightButton, + isNextActive = false, + onLeftClick, + onRightClick, +}: HeaderProps) => (
{leftIcon}
{title}
-
- {rightButton} -
+ {rightButton ? ( + + {rightButton} + + ) : ( +
+ )} ); diff --git a/src/pages/signup/Signup.styled.ts b/src/pages/signup/Signup.styled.ts index 6a202f3b..9277f720 100644 --- a/src/pages/signup/Signup.styled.ts +++ b/src/pages/signup/Signup.styled.ts @@ -5,7 +5,7 @@ export const Container = styled.div` flex-direction: column; background-color: #121212; min-width: 360px; - max-width: 768px; + max-width: 767px; height: 100vh; margin: 0 auto; padding: 96px 20px 0 20px; @@ -33,6 +33,144 @@ export const Container = styled.div` font-style: normal; font-weight: 400; } + + .content { + display: flex; + flex-direction: column; + align-items: center; + gap: 76px; + padding-top: 40px; + + .userInfo { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + .profile { + display: flex; + width: 54px; + height: 54px; + justify-content: center; + align-items: center; + border-radius: 54px; + border: 0.5px solid #adadad; + overflow: hidden; + + img { + width: 54px; + height: 54px; + flex-shrink: 0; + } + } + + .username { + color: #fefefe; + font-size: 18px; + font-style: normal; + font-weight: 600; + line-height: 24px; + letter-spacing: 0.018px; + padding: 8px 0 4px 0; + } + + .subname { + color: #ff8bac; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: normal; + } + } + } + + .startBtn { + color: #fefefe; + font-size: 16px; + font-style: normal; + font-weight: 600; + line-height: 24px; + + display: flex; + padding: 10px 12px; + justify-content: center; + align-items: center; + width: 180px; + height: 44px; + gap: 8px; + border-radius: 12px; + background: #6868ff; + cursor: pointer; + } + + .genreGrid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(132px, 1fr)); + gap: 16px; + + .genreCard { + display: flex; + position: relative; + overflow: hidden; + height: 100px; + min-width: 130px; + max-width: 180px; + padding: 29px 20px; + flex-direction: column; + align-items: flex-end; + gap: 10px; + border-radius: 12px; + border: 1px solid #3d3d3d; + text-align: center; + cursor: pointer; + transition: border-color 0.2s; + justify-content: center; + + .textbox { + z-index: 1; + display: flex; + flex-direction: column; + gap: 5px; + } + + img.bg { + position: absolute; + width: 80px; + height: 70px; + bottom: 0px; + left: 5px; + opacity: 0.2; + z-index: 0; + } + + .genreTitle { + color: #adadad; + font-size: 16px; + font-style: normal; + font-weight: 600; + line-height: 24px; + } + .genreSub { + text-align: center; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: normal; + } + + &.active { + border: 1px solid #fefefe; + /* background: linear-gradient(0deg, rgba(18, 18, 18, 0.3) 0%, rgba(18, 18, 18, 0.3) 100%); + background-color: lightgray; */ + .genreTitle { + color: #fefefe; + } + img.bg { + opacity: 0.8; + } + } + } + } `; export const InputBox = styled.div` diff --git a/src/pages/signup/SignupDone.tsx b/src/pages/signup/SignupDone.tsx new file mode 100644 index 00000000..c2353a1c --- /dev/null +++ b/src/pages/signup/SignupDone.tsx @@ -0,0 +1,38 @@ +import { useNavigate } from 'react-router-dom'; +import Header from './Header'; +import { Container } from './Signup.styled'; +import leftarrow from '../../assets/leftArrow.svg'; +import art from '../../assets/genre/art.svg'; + +const SignupDone = () => { + const navigate = useNavigate(); + const handleBackClick = () => { + navigate(-1); + }; + + const handleNextClick = () => { + navigate('/feed'); + }; + + return ( + +
} onLeftClick={handleBackClick} /> +
안녕하세요, 희용희용님
+
이제 Thip에서 활동할 준비를 모두 마쳤어요!
+
+
+
+ +
+
희용희용
+
예술가
+
+
+ 지금 바로 Thip 시작하기 +
+
+ + ); +}; + +export default SignupDone; diff --git a/src/pages/signup/SignupGenre.tsx b/src/pages/signup/SignupGenre.tsx index cdca6918..d2ee6c20 100644 --- a/src/pages/signup/SignupGenre.tsx +++ b/src/pages/signup/SignupGenre.tsx @@ -1,17 +1,36 @@ -import { Container } from './Signup.styled'; +import { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; +import { Container } from './Signup.styled'; import Header from './Header'; import leftarrow from '../../assets/leftArrow.svg'; +interface Genre { + id: string; + title: string; + subTitle: string; + iconUrl: string; + color: string; +} + const SignupGenre = () => { + const [genres, setGenres] = useState([]); + const [selectedId, setSelectedId] = useState(null); const navigate = useNavigate(); + useEffect(() => { + fetch('/genres.json') + .then(res => res.json()) + .then((data: Genre[]) => setGenres(data)) + .catch(console.error); + }, []); + const handleBackClick = () => { navigate(-1); }; const handleNextClick = () => { - navigate('/signup'); + if (!selectedId) return; + navigate('/signupdone', { state: { genreId: selectedId } }); }; return ( @@ -22,11 +41,29 @@ const SignupGenre = () => { rightButton={
다음
} onLeftClick={handleBackClick} onRightClick={handleNextClick} + isNextActive={!!selectedId} />
관심있는 장르를 선택해주세요.
이후 마이페이지에서 변경이 가능해요.
아래에서 하나를 선택해주세요.
+
+ {genres.map(g => ( +
setSelectedId(g.id)} + > + {g.title} +
+
{g.title}
+
+ {g.subTitle} +
+
+
+ ))} +
); diff --git a/src/pages/signup/SignupNickname.tsx b/src/pages/signup/SignupNickname.tsx index 7166c78c..c22f77b2 100644 --- a/src/pages/signup/SignupNickname.tsx +++ b/src/pages/signup/SignupNickname.tsx @@ -20,7 +20,9 @@ const SignupNickname = () => { }; const handleInputChange = (e: React.ChangeEvent) => { - setNickname(e.target.value); + const inputValue = e.target.value; + const filteredValue = inputValue.replace(/[^ㄱ-ㅎ가-힣a-zA-Z0-9]/g, ''); + setNickname(filteredValue); }; return ( @@ -30,6 +32,7 @@ const SignupNickname = () => { rightButton={
다음
} onLeftClick={handleBackClick} onRightClick={handleNextClick} + isNextActive={isNextActive} />
닉네임(필수)