From 9f4539f227fe89fb2a57308e42ba450a2d08e890 Mon Sep 17 00:00:00 2001 From: halion Date: Wed, 2 Oct 2024 00:31:07 +0900 Subject: [PATCH] =?UTF-8?q?[=EB=B0=B0=ED=8F=AC]=20=EB=A9=94=ED=83=80?= =?UTF-8?q?=ED=83=9C=EA=B7=B8,=20=EC=82=AC=EC=9D=B4=EB=93=9C=EB=B0=94,=20?= =?UTF-8?q?=EC=95=B0=ED=94=8C=EB=A6=AC=ED=8A=9C=EB=93=9C,=20=EC=9D=B4?= =?UTF-8?q?=EB=A9=94=EC=9D=BC=20valid=20check=20...=20(#88)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix:: 새 탭에서 열기 버그 fix - noopener, noreferrer 속성을 통해, 새 탭으로 연 창에서도 새탭 열기 가능하도록 * fix:: major category 선택 모달 디자인 * feat:: 새로운 시간표 추가 버튼 구현 * mod:: Timetable 컴포넌트 밖에서도 BottomSheet 열고 닫을 수 있도록 코드 수정 * mod:: BottomSheet Position 조정 * fix:: Attendance 진행 시 포인트 히스토리도 업데이트 * mod:: ticket -> key로 워딩 수정 * feat:: Reading Key Expiration Card 구현 & API 적용 * design:: Figma Style과 동기화 & 반응형 적용 * feat:: setInterval을 통한 자동 카운트 다운 * feat:: 캐릭터 변경 성공 시 스크롤 up * feat:: Change Password Error Handling * feat:: Change Profile Error Handling * mod:: Apply Btn 스타일 변경 * fix:: Response가 오지 않을 때 처리 * mod:: MyPage 게시글 모아보기에서 사진 안 보이도록 수정 * feat:: My Community Page Search Param을 통해 url 분기 * mod:: My Community 댓글 카드 디자인 수정 * feat:: 에 Amplitude 추가 * Feat/temp password (#67) * feat:: 비밀번호 찾기 페이지 연결 * feat:: 비밀번호 리셋 이후 모달 창으로 로그인 네비게이션 --------- Co-authored-by: halion * feat: make Dockerfile * mod: Dockerfile KU-key에 맞추어 수정 * feat:: 방송국 단체 youtube link 연결 및 단체 변경 (#71) * feat:: serve를 통한 build file 실행 * fix:: 오타 수정 * chore:: Create docker-image.yml * chore:: 도커 빌드 때 ts도 설치 * chore:: Panda init도 실행 * fix::yarn 깔기 전에 panda init? * fix:: Panda init 롤백 * chore:: build 때 tsc 제외 * chore:: Local 환경과 똑같이 세팅 * fix:: 순서 변경 * fix:: Drop Production * fix:: frozen lockfile & ignore scripts 속성 주기 * fix:: tsc -noEmit 설정 * test:: yarn dev로 실행해보기 * chore:: vite config에 절대경로 명시 * fix:: DockerFile * Revert "fix:: DockerFile" This reverts commit e0c3f37b143b032226cf3fb6fe1e5f935a2c0452. * Revert "chore:: vite config에 절대경로 명시" This reverts commit 75ee8d997b64fa527048d494950e6d9cfc8a6d3b. * chore:: yarn install & serve with serve * fix:: Panda CSS Config 경로 수정 * fix:: DockerFile 실행 전 panda config copy * fix:: dockerfile copy * fix: 처음에 Copy하기 * feat: main-deploy.yml * feat:: dev-deploy.yml * #38 (#68) * [#38] feat:: 삭제된 댓글 Username 지정 * feat:: 게시글 댓글 익명성에 따른 ui 반영 * fix:: isAuthor 인자 추가하기 * [#38] feat:: 삭제된 댓글 Username 지정 * feat:: 게시글 댓글 익명성에 따른 ui 반영 * fix:: isAuthor 인자 추가하기 * design:: Lecture 추가를 위한 버튼 삭제 & BottomSheet Drawer 정상화 * feat:: 빈 학기에 들어갈 시 자동 타임테이블 추가 :RealCookie: * feat:: My Timetable Page에서는 Null table이 보이지 않도록 처리 * fix:: merge conflict * Feat/comment delete api (#69) * [#38] feat:: 삭제된 댓글 Username 지정 * feat:: 게시글 댓글 익명성에 따른 ui 반영 * fix:: isAuthor 인자 추가하기 * feat:: 댓글 삭제 api 연결 - 삭제된 댓글 기준 util button 감추기 - 댓글 삭제 이후 invalidation queryKey 커스텀 훅으로 핸들링 * feat:: 댓글 작성시 최상단에 배치 - 리렌더링할 때 백엔드 api에 따라 배치 * fix:: queryKey parse Type error * feat:: 삭제된 댓글 UI 버튼 visibility 만 조정 * mod: PR Review 반영 - Bottom Sheet State는Sheet 컴포넌트 내에서 관리 * fix: Dummy Timetable이어도 서버로 요청 보내는 문제 * mod: INIT data 중복 제거 * chore: Add Amplitude Library * mod: Amplitude init script tag에서 tsx provider로 * feat:: Docker 버리고 S3 정적 배포 * test: deploy 돌아가는지 테스트 * fix: aws cli 설치 추가 * fix: Cli 설치과정 삭제, CloudFront invalidation 로직 변경 * test: dev 브랜치로 testing하던거 정상화 * feat: .env 파일 등록 * test: dev -> main * typo: change deploy.yml name * rename: Academic Calendar 로직 & 컴포넌트 Calendar로 합병 * feat: Banner API * mod: isLoading으로 스켈레톤 관리 * fix: 사진 로딩이 덜 되어 화면 깜빡거리는 현상 * fix: 스켈레톤 배너 width가 제대로 안 먹는 버그 * feat: MetaTag Component * feat: Club Page 메타태그 적용 * mod: Change Club Description * feat: Academic Schedule Page 메타태그 적용 * feat: 사이드 탭 구현 (#79) * feat: side-tab component 구현 * chore: default 캐릭터 이미지 교체 * feat: side-tab nav 연결 * feat: 사이드 탭 유저 정보 보여주기 * chore: 불필요한 type export 없애기 * chore: sheet 필수 요소 title, description srOnly 로 주입 * chore: import 이름 수정 * feat: pr 리뷰 반영 * fix: 회원가입 이메일 인증코드 valid check (#85) * feat: menu에 로그인 항목 넣기 (#87) * feat: menu에 로그인 항목 넣기 * feat: input fontsize smDown 인 경우 16px 로 수정 * fix: Footer 라우팅 버그, 메뉴 수정 * fix: Routing 달라질 때 기본 Scroll Up * fix: timetable 라우팅 내에서는 스크롤이 초기화되지 않는 버그 * mod: PR 반영 - 불필요한 useEffect 제거 * fix: curPath 바뀔 때 항상 Scroll Up - useScrollUp 훅 만료 * fix: useScrollUp 훅 만료 --------- Co-authored-by: Seungmin Cha <75214259+Virtuso1225@users.noreply.github.com> --- index.html | 2 +- package.json | 2 + panda.config.ts | 40 ++++++ src/App.tsx | 5 + src/assets/CharacterDefault.png | Bin 13566 -> 15033 bytes src/components/Footer.tsx | 51 +++++--- src/components/Header.tsx | 63 ++++++---- src/components/HeaderMenu.tsx | 119 ++++++++++++++++++ src/components/MainLayout.tsx | 6 +- src/components/MetaTag.tsx | 41 ++++++ src/components/header/NavLinkButton.tsx | 1 + src/components/header/SideTabLogInLink.tsx | 34 +++++ src/components/header/SideTabProfile.tsx | 73 +++++++++++ src/components/ui/profile/CharacterConfig.ts | 5 + src/components/ui/sheet/index.tsx | 92 ++++++++++++++ src/lib/recipes/index.ts | 2 + src/lib/recipes/input.ts | 2 +- src/lib/recipes/sheet.ts | 52 ++++++++ src/lib/router/footer-route.ts | 7 ++ src/pages/ClubPage.tsx | 9 +- src/pages/CommunityPage/PostViewPage.tsx | 2 - .../CourseReviewPage/ReviewDetailPage.tsx | 2 - .../CourseReviewPage/WriteReviewPage.tsx | 2 - src/pages/RegisterPage.tsx | 6 + src/pages/SchedulePage.tsx | 6 + src/util/AmplitudeProvider.tsx | 9 ++ src/util/ScrollToTop.tsx | 15 +++ src/util/useScrollUp.ts | 9 -- yarn.lock | 108 +++++++++++++++- 29 files changed, 702 insertions(+), 63 deletions(-) create mode 100644 src/components/HeaderMenu.tsx create mode 100644 src/components/MetaTag.tsx create mode 100644 src/components/header/SideTabLogInLink.tsx create mode 100644 src/components/header/SideTabProfile.tsx create mode 100644 src/components/ui/sheet/index.tsx create mode 100644 src/lib/recipes/sheet.ts create mode 100644 src/lib/router/footer-route.ts create mode 100644 src/util/AmplitudeProvider.tsx create mode 100644 src/util/ScrollToTop.tsx delete mode 100644 src/util/useScrollUp.ts diff --git a/index.html b/index.html index 2c3461c7..a5776a97 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - Vite + React + TS + KU-key
diff --git a/package.json b/package.json index e31a3c66..3e83c016 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,10 @@ "preview": "vite preview" }, "dependencies": { + "@amplitude/analytics-browser": "^2.11.6", "@hookform/resolvers": "^3.3.4", "@radix-ui/react-checkbox": "^1.0.4", + "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-menubar": "^1.1.1", diff --git a/panda.config.ts b/panda.config.ts index 8b860e5f..30ba45ce 100644 --- a/panda.config.ts +++ b/panda.config.ts @@ -15,6 +15,7 @@ import { shadowRecipe, tagRecipe, textStyles, + sheetRecipe, } from './src/lib/recipes/index' import { tokenToRem } from './src/lib/constants/tokenToRem' @@ -44,6 +45,7 @@ export default defineConfig({ reactionButton: reactionTagRecipe, carouselButton: carouselButtonRecipe, postCard: postCardRecipe, + sheet: sheetRecipe, }, slotRecipes: { menubar: menubar, @@ -94,6 +96,44 @@ export default defineConfig({ from: { transform: 'scale(1)', opacity: 1 }, to: { transform: 'scale(0.95)', opacity: 0 }, }, + slideInFromTop: { + from: { transform: 'translateY(-100%)' }, + to: { transform: 'translateY(0)' }, + }, + slideOutToTop: { + from: { transform: 'translateY(0)' }, + to: { transform: 'translateY(-100%)' }, + }, + slideInFromBottom: { + from: { transform: 'translateY(100%)' }, + to: { transform: 'translateY(0)' }, + }, + slideOutToBottom: { + from: { transform: 'translateY(0)' }, + to: { transform: 'translateY(100%)' }, + }, + slideInFromLeft: { + from: { transform: 'translateX(-100%)' }, + to: { transform: 'translateX(0)' }, + }, + slideOutToLeft: { + from: { transform: 'translateX(0)' }, + to: { transform: 'translateX(-100%)' }, + }, + slideInFromRight: { + from: { transform: 'translateX(100%)' }, + to: { transform: 'translateX(0)' }, + }, + slideOutToRight: { + from: { transform: 'translateX(0)' }, + to: { transform: 'translateX(100%)' }, + }, + }, + breakpoints: { + xs: '390px', + sm: '580px', + md: '900px', + lg: '1200px', }, }, }, diff --git a/src/App.tsx b/src/App.tsx index a3e2d695..fab22479 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,7 +3,10 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { useRoutes } from 'react-router-dom' import routes from '@/lib/router/router' +import AmplitudeProvider from '@/util/AmplitudeProvider' import AuthProvider from '@/util/auth/AuthProvider' +import ScrollToTop from '@/util/ScrollToTop' + const queryClient = new QueryClient({ defaultOptions: { queries: { @@ -19,6 +22,8 @@ function App() { {/* */} + + {router} ) diff --git a/src/assets/CharacterDefault.png b/src/assets/CharacterDefault.png index 37c6aff1d1dcbee312c0d75c603081b7cb501129..adb88066b03455de880baf3df9ae249555318ac1 100644 GIT binary patch literal 15033 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 diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index 364c09b5..dff7b856 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -1,10 +1,16 @@ import { css } from '@styled-system/css' +import { useCallback, useState } from 'react' import { Link } from 'react-router-dom' // TODO: ADD Instagram & Notion Link // import instagramIcon from '@/assets/instagram.svg' import KUkeyLogo from '@/assets/KU-keyLogo.svg' import mailIcon from '@/assets/mail.svg' +import NoticeModal from '@/components/ui/modal/NoticeModal' +import { HEADER_MESSAGE } from '@/lib/messages/header' +import { footerRouteConfig } from '@/lib/router/footer-route' +import { useAuth } from '@/util/auth/useAuth' +import { useModal } from '@/util/useModal' // import notionIcon from '@/assets/notion.svg' const supportApps = [ @@ -26,8 +32,31 @@ const tabs = css({ }) const Footer = () => { + const { isAuthenticated, authState } = useAuth() + const { isOpen: isModalOpen, handleOpen: handleModalOpen } = useModal(true) + const [modalContent, setModalContent] = useState(HEADER_MESSAGE.NOT_VERIFIED_USER) + + const handleNavClick = useCallback( + (e: React.MouseEvent, route: string) => { + if (route === 'matching') { + e.preventDefault() + setModalContent(HEADER_MESSAGE.NOT_READY) + handleModalOpen() + return + } + if (!isAuthenticated) return // 미로그인 유저 + if (!authState) { + // 인증 안 된 유저 + e.preventDefault() + setModalContent(HEADER_MESSAGE.NOT_VERIFIED_USER) + handleModalOpen() + } + }, + [authState, handleModalOpen, isAuthenticated], + ) + return ( -
{ h: 25, })} > - - MY PAGE - - - TIMETABLE - - - COMMUNITY - - - 1:1 MATCHING - + {footerRouteConfig.map(nav => ( + handleNavClick(e, nav.route)}> + {nav.navName} + + ))}
- + + ) } diff --git a/src/components/Header.tsx b/src/components/Header.tsx index a6e8b2e0..e20728ec 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -7,10 +7,12 @@ import { useLogOut } from '@/api/hooks/auth' import KUkeyLogo from '@/assets/KU-keyLogo.svg' import { NavLinkButton } from '@/components/header/NavLinkButton' import NotifyWindow from '@/components/header/NotifyWindow' +import HeaderMenu from '@/components/HeaderMenu' import NoticeModal from '@/components/ui/modal/NoticeModal' import { HEADER_MESSAGE } from '@/lib/messages/header' import { headerRouteConfig } from '@/lib/router/header-route' import { useAuth } from '@/util/auth/useAuth' +import { useMediaQuery } from '@/util/useMediaQuery' import { useModal } from '@/util/useModal' const Header = () => { @@ -24,6 +26,7 @@ const Header = () => { const { isOpen: isModalOpen, handleOpen: handleModalOpen } = useModal(true) const [modalContent, setModalContent] = useState(HEADER_MESSAGE.NOT_VERIFIED_USER) const navigate = useNavigate() + const mediaQuery = useMediaQuery('(max-width: 900px)') const handleUserButton = useCallback(() => { isAuthenticated ? mutateSignOut() : navigate('/login') }, [isAuthenticated, mutateSignOut, navigate]) @@ -86,7 +89,7 @@ const Header = () => { bg: 'white', justifyContent: 'space-between', alignItems: 'center', - px: { base: '150px', mdDown: 5 }, + px: { base: '150px', lgDown: '100px', mdDown: '60px', smDown: '20px' }, })} >
- {isAuthenticated && ( -
- - handleNavClick(e, 'mypage')} - > - - -
- )} +
+ {isAuthenticated && ( + <> + + handleNavClick(e, 'mypage')} + > + + + + )} + {mediaQuery && ( + + )} +
+ diff --git a/src/components/HeaderMenu.tsx b/src/components/HeaderMenu.tsx new file mode 100644 index 00000000..2aad44a8 --- /dev/null +++ b/src/components/HeaderMenu.tsx @@ -0,0 +1,119 @@ +import { css, cva, cx } from '@styled-system/css' +import { Menu } from 'lucide-react' +import { useCallback, useState } from 'react' +import { Link } from 'react-router-dom' + +import SideTabLogInLink from '@/components/header/SideTabLogInLink' +import SideTabProfile from '@/components/header/SideTabProfile' +import { Sheet, SheetContent, SheetDescription, SheetTitle, SheetTrigger } from '@/components/ui/sheet' +import { headerRouteConfig } from '@/lib/router/header-route' + +interface HeaderProps { + curPath: string + handleNavClick: (e: React.MouseEvent, navName: string) => void + handleUserButton: () => void + isAuthenticated: boolean +} +const HeaderMenu = ({ handleNavClick, curPath, handleUserButton, isAuthenticated }: HeaderProps) => { + const curPathRoot = curPath.split('/')[1] + const [isSheetOpen, setIsSheetOpen] = useState(false) + const handleSheetNavClick = useCallback( + (e: React.MouseEvent, navName: string) => { + navName !== '1:1 Matching' && setIsSheetOpen(p => !p) + handleNavClick(e, navName) + }, + [handleNavClick], + ) + const handleSheetCloseLogOut = useCallback(() => { + setIsSheetOpen(false), handleUserButton() + }, [handleUserButton]) + return ( + setIsSheetOpen(p => !p)}> + + + + + {isAuthenticated ? : } + Navigation SideTab + For Mobile or medium size screen + + + + ) +} + +export default HeaderMenu + +const menuButton = cva({ + base: { + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + transition: 'all 0.2s ease-out', + gap: 2.5, + }, + variants: { + isSelected: { + true: { color: 'red.2' }, + false: { color: 'darkGray.1' }, + }, + }, +}) diff --git a/src/components/MainLayout.tsx b/src/components/MainLayout.tsx index 95cda3fa..c3ec75c7 100644 --- a/src/components/MainLayout.tsx +++ b/src/components/MainLayout.tsx @@ -1,11 +1,11 @@ import { css } from '@styled-system/css' import { useEffect } from 'react' -import { Helmet } from 'react-helmet-async' import { Outlet } from 'react-router-dom' import { useCheckVerified } from '@/api/hooks/auth' import Footer from '@/components/Footer' import Header from '@/components/Header' +import MetaTag from '@/components/MetaTag' import { useAuth } from '@/util/auth/useAuth' const MainLayout = () => { @@ -16,9 +16,7 @@ const MainLayout = () => { }, [verified, setVerified]) return (
- - KU-key - +
diff --git a/src/components/MetaTag.tsx b/src/components/MetaTag.tsx new file mode 100644 index 00000000..abf11c05 --- /dev/null +++ b/src/components/MetaTag.tsx @@ -0,0 +1,41 @@ +import { Helmet } from 'react-helmet-async' + +interface MetaTagProps { + title: string + description?: string + keywords?: string + imgSrc?: string +} +const MetaTag = ({ + title, + description = 'KU-key is a service designed to help international students on exchange to Korea University have a more convenient school life. Meet friends in KU-key and manage your school life conveniently!', + keywords, + imgSrc = 'https://kukeyrun.s3.amazonaws.com/fe/metaImg.jpg', +}: MetaTagProps) => { + const adjKeywords = `${keywords === undefined ? '' : `${keywords}, `}exchange student, Korea University, KU, KU-key, kukey, Korea Univ, 고려대학교, 쿠키, exchange, timetable, everytime` + + return ( + + {title} + + + + + + + + + + + + + + + + + + + ) +} + +export default MetaTag diff --git a/src/components/header/NavLinkButton.tsx b/src/components/header/NavLinkButton.tsx index 906e0390..d6f06e30 100644 --- a/src/components/header/NavLinkButton.tsx +++ b/src/components/header/NavLinkButton.tsx @@ -3,6 +3,7 @@ import { AnimatePresence, motion } from 'framer-motion' import { ChevronDown } from 'lucide-react' import { forwardRef } from 'react' import { Link } from 'react-router-dom' + interface NavLinkProps { isSelected: boolean targetRoute: string diff --git a/src/components/header/SideTabLogInLink.tsx b/src/components/header/SideTabLogInLink.tsx new file mode 100644 index 00000000..90b7f000 --- /dev/null +++ b/src/components/header/SideTabLogInLink.tsx @@ -0,0 +1,34 @@ +import { css } from '@styled-system/css' +import { Link } from 'react-router-dom' + +import { characterConfig } from '@/components/ui/profile/CharacterConfig' + +interface SideTabLogInLinkProps { + handleSheetNavClick: (e: React.MouseEvent, navName: string) => void +} +const SideTabLogInLink = ({ handleSheetNavClick }: SideTabLogInLinkProps) => { + return ( + handleSheetNavClick(e, 'login')} + > +

you need to login

+ profile + + ) +} + +export default SideTabLogInLink diff --git a/src/components/header/SideTabProfile.tsx b/src/components/header/SideTabProfile.tsx new file mode 100644 index 00000000..e1026b2b --- /dev/null +++ b/src/components/header/SideTabProfile.tsx @@ -0,0 +1,73 @@ +import { css } from '@styled-system/css' +import { hasFlag } from 'country-flag-icons' +import getUnicodeFlagIcon from 'country-flag-icons/unicode' + +import { useGetMyProfile } from '@/api/hooks/user' +import Sugar from '@/assets/Sugar_md.png' +import { Chip } from '@/components/ui/chip' +import { characterConfig } from '@/components/ui/profile/CharacterConfig' + +const SideTabProfile = () => { + const { data: myProfileData } = useGetMyProfile() + const variantsArray: ('default' | 'red3' | 'red4')[] = ['default', 'red3', 'red4'] + + return ( +
+
+
+

+ {myProfileData.username} +

+

+ {hasFlag(myProfileData.country.toUpperCase()) && getUnicodeFlagIcon(myProfileData.country.toUpperCase())} +

+
+
+ sugar +

{myProfileData.point}

+
+
+ {myProfileData.languages.map((lan, ind) => ( + + {lan.toUpperCase()} + + ))} +
+
+ profile +
+ ) +} + +export default SideTabProfile diff --git a/src/components/ui/profile/CharacterConfig.ts b/src/components/ui/profile/CharacterConfig.ts index 8c48fced..97688780 100644 --- a/src/components/ui/profile/CharacterConfig.ts +++ b/src/components/ui/profile/CharacterConfig.ts @@ -1,6 +1,11 @@ import * as Characters from '@/components/ui/profile/profile-img' import { CharacterType } from '@/types/community' +/** + * + * @param characterType + * @param level: 1 ~ 5 + */ export const characterConfig: Record> = { character1: { 1: Characters.Character1Lv1, diff --git a/src/components/ui/sheet/index.tsx b/src/components/ui/sheet/index.tsx new file mode 100644 index 00000000..0248a6c0 --- /dev/null +++ b/src/components/ui/sheet/index.tsx @@ -0,0 +1,92 @@ +'use client' + +import * as SheetPrimitive from '@radix-ui/react-dialog' +import { css, cx, RecipeVariantProps } from '@styled-system/css' +import { sheet } from '@styled-system/recipes' +import { X } from 'lucide-react' +import { forwardRef } from 'react' + +const Sheet = SheetPrimitive.Root + +const SheetTrigger = SheetPrimitive.Trigger + +const SheetClose = SheetPrimitive.Close + +const SheetPortal = SheetPrimitive.Portal + +const SheetOverlay = forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ) +SheetOverlay.displayName = SheetPrimitive.Overlay.displayName + +type SheetContentProps = React.ComponentPropsWithoutRef & + RecipeVariantProps & { closeButton?: boolean } + +const SheetContent = forwardRef, SheetContentProps>( + ({ side = 'right', className, children, closeButton, ...props }, ref) => ( + + + + {children} + + + Close + + + + ), +) +SheetContent.displayName = SheetPrimitive.Content.displayName + +const SheetHeader = ({ className, ...props }: React.HTMLAttributes) => ( +
+) +SheetHeader.displayName = 'SheetHeader' + +const SheetFooter = ({ className, ...props }: React.HTMLAttributes) => ( +
+) +SheetFooter.displayName = 'SheetFooter' + +const SheetTitle = forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ) +SheetTitle.displayName = SheetPrimitive.Title.displayName + +const SheetDescription = forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ) +SheetDescription.displayName = SheetPrimitive.Description.displayName + +export { + Sheet, + SheetPortal, + SheetOverlay, + SheetTrigger, + SheetClose, + SheetContent, + SheetHeader, + SheetFooter, + SheetTitle, + SheetDescription, +} diff --git a/src/lib/recipes/index.ts b/src/lib/recipes/index.ts index d14baaf4..debbb549 100644 --- a/src/lib/recipes/index.ts +++ b/src/lib/recipes/index.ts @@ -10,6 +10,7 @@ import { postCardRecipe } from '@/lib/recipes/post-card' import { postTextPreviewRecipe } from '@/lib/recipes/post-text-preview' import { reactionTagRecipe } from '@/lib/recipes/reaction-tag' import { shadowRecipe } from '@/lib/recipes/shadow' +import { sheetRecipe } from '@/lib/recipes/sheet' import { tagRecipe } from '@/lib/recipes/tag' import { textStyles } from '@/lib/recipes/textStyles' export { @@ -27,4 +28,5 @@ export { postCardRecipe, menubar, clubTagRecipe, + sheetRecipe, } diff --git a/src/lib/recipes/input.ts b/src/lib/recipes/input.ts index 0361fdad..e77ef44d 100644 --- a/src/lib/recipes/input.ts +++ b/src/lib/recipes/input.ts @@ -15,7 +15,7 @@ export const inputRecipe = defineRecipe({ lineHeight: '100%', _file: { border: 0, bg: 'transparent', fontSize: 'sm', fontWeight: 'medium' }, _placeholder: { color: 'lightGray.1' }, - smDown: { fontSize: 12, fontWeight: 600 }, + smDown: { fontSize: 16, fontWeight: 500 }, minH: '39px', }, variants: { diff --git a/src/lib/recipes/sheet.ts b/src/lib/recipes/sheet.ts new file mode 100644 index 00000000..8c143ac9 --- /dev/null +++ b/src/lib/recipes/sheet.ts @@ -0,0 +1,52 @@ +import { defineRecipe } from '@pandacss/dev' + +export const sheetRecipe = defineRecipe({ + className: 'sheet', + description: 'Sheet component', + base: { + pos: 'fixed', + zIndex: 50, + gap: 4, + p: '30px', + transition: 'all ease-in-out', + _open: { animation: 'animateIn 0.2s ease-in' }, + _closed: { animation: 'animateOut 0.2s ease-out' }, + bg: 'white', + }, + + variants: { + side: { + top: { + insetX: 0, + top: 0, + _open: { animation: 'slideInFromTop 0.2s ease-out' }, + _closed: { animation: 'slideOutToTop 0.2s ease-out' }, + }, + bottom: { + insetX: 0, + bottom: 0, + _open: { animation: 'slideInFromBottom 0.2s ease-out' }, + _closed: { animation: 'slideOutToBottom 0.2s ease-out' }, + }, + left: { + insetY: 0, + left: 0, + h: 'full', + w: '3/4', + _open: { animation: 'slideInFromLeft 0.2s ease-out' }, + _closed: { animation: 'slideOutToLeft 0.2s ease-out' }, + }, + right: { + insetY: 0, + right: 0, + h: 'full', + w: '85%', + _open: { animation: 'slideInFromRight 0.2s ease-out' }, + _closed: { animation: 'slideOutToRight 0.2s ease-out' }, + }, + }, + }, + defaultVariants: { + side: 'right', + }, +}) diff --git a/src/lib/router/footer-route.ts b/src/lib/router/footer-route.ts new file mode 100644 index 00000000..76a1d156 --- /dev/null +++ b/src/lib/router/footer-route.ts @@ -0,0 +1,7 @@ +export const footerRouteConfig = [ + { route: 'mypage', navName: 'MY PAGE' }, + { route: 'timetable', navName: 'TIMETABLE' }, + { route: 'timetable/friend', navName: 'FRIENDS' }, + { route: 'community', navName: 'COMMUNITY' }, + { route: 'matching', navName: '1:1 MATCHING' }, +] diff --git a/src/pages/ClubPage.tsx b/src/pages/ClubPage.tsx index ee4cf1ff..c0a6fad2 100644 --- a/src/pages/ClubPage.tsx +++ b/src/pages/ClubPage.tsx @@ -6,14 +6,12 @@ import CategorySelector from '@/components/club/CategorySelector' import ClubCard from '@/components/club/ClubCard' import { CategoryType } from '@/components/club/constants' import SearchArea from '@/components/club/SearchArea' +import MetaTag from '@/components/MetaTag' import { Checkbox } from '@/components/ui/checkbox' import { useAuth } from '@/util/auth/useAuth' -import useScrollUp from '@/util/useScrollUp' import { useSearch } from '@/util/useSearch' const ClubPage = () => { - useScrollUp() - const isLogin = useAuth().authState ?? false const { searchParam, handleSetParam, deleteParam } = useSearch() @@ -65,6 +63,11 @@ const ClubPage = () => { return ( <> +
{ - useScrollUp() return (
{ - useScrollUp() const data = useLocation().state as ReviewType const infoData = useAtomValue(courseSummary) return ( diff --git a/src/pages/CourseReviewPage/WriteReviewPage.tsx b/src/pages/CourseReviewPage/WriteReviewPage.tsx index aca364c2..985bffc1 100644 --- a/src/pages/CourseReviewPage/WriteReviewPage.tsx +++ b/src/pages/CourseReviewPage/WriteReviewPage.tsx @@ -21,7 +21,6 @@ import { teamProjectArray, } from '@/util/reviewUtil' import { getCurSemester, makeSemesterDropdownList, timetablePreprocess } from '@/util/timetableUtil' -import useScrollUp from '@/util/useScrollUp' interface WriteReviewForm { rate: number @@ -38,7 +37,6 @@ interface WriteReviewForm { } const WriteReviewPage = () => { - useScrollUp() const { courseCode = '', prof = '' } = useParams() const navigate = useNavigate() diff --git a/src/pages/RegisterPage.tsx b/src/pages/RegisterPage.tsx index 49fd4e2b..b70f25d6 100644 --- a/src/pages/RegisterPage.tsx +++ b/src/pages/RegisterPage.tsx @@ -71,6 +71,12 @@ const RegisterPage = memo(() => { const handleNextPage = () => { if (page === 1) { + if (valid.email === 'unknown') emailForm.trigger() + if (valid.email !== 'valid') { + setValid(v => ({ ...v, email: 'invalid' })) + emailForm.setError('emailCode', { message: 'Invalid code', type: 'validate' }) + return + } emailForm.handleSubmit(() => setPage(2))() } if (page === 2) { diff --git a/src/pages/SchedulePage.tsx b/src/pages/SchedulePage.tsx index aca45d23..3c829eb3 100644 --- a/src/pages/SchedulePage.tsx +++ b/src/pages/SchedulePage.tsx @@ -4,6 +4,7 @@ import { useCallback, useState } from 'react' import { useGetAcademicCalendar } from '@/api/hooks/calendar' import koreaUniv from '@/assets/koreaUniv.png' import AcademicCalendar from '@/components/calendar/AcademicCalendar' +import MetaTag from '@/components/MetaTag' import Dropdown from '@/components/timetable/Dropdown' import { useAcademicSemester } from '@/util/academicCalendar' import { makeSemesterDropdownList } from '@/util/timetableUtil' @@ -27,6 +28,11 @@ const SchedulePage = () => { return ( <> +
{ + init(import.meta.env.VITE_API_AMPLITUDE_API_KEY) + + return <> +} + +export default AmplitudeProvider diff --git a/src/util/ScrollToTop.tsx b/src/util/ScrollToTop.tsx new file mode 100644 index 00000000..718151e2 --- /dev/null +++ b/src/util/ScrollToTop.tsx @@ -0,0 +1,15 @@ +import { useEffect } from 'react' +import { useLocation } from 'react-router-dom' + +const ScrollToTop = () => { + const location = useLocation() + const curPath = location.pathname + + useEffect(() => { + window.scrollTo(0, 0) + }, [curPath]) + + return <> +} + +export default ScrollToTop diff --git a/src/util/useScrollUp.ts b/src/util/useScrollUp.ts deleted file mode 100644 index 5c3e80b3..00000000 --- a/src/util/useScrollUp.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { useEffect } from 'react' - -const useScrollUp = () => { - useEffect(() => { - window.scrollTo(0, 0) - }, []) -} - -export default useScrollUp diff --git a/yarn.lock b/yarn.lock index 0527fa76..11a13c90 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,6 +7,76 @@ resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== +"@amplitude/analytics-browser@^2.11.6": + version "2.11.6" + resolved "https://registry.yarnpkg.com/@amplitude/analytics-browser/-/analytics-browser-2.11.6.tgz#7283ad6b29e918f3d2d1143abd15cdadd82fbb85" + integrity sha512-EvXK5vitHFE7Pic50Oz/EDlnvvGdFBnSagEZurnCe9u8bViWagyhfLuM+So1OCTbsEORbXK1rIhkvtKH+ChdjQ== + dependencies: + "@amplitude/analytics-client-common" "^2.3.3" + "@amplitude/analytics-core" "^2.5.2" + "@amplitude/analytics-remote-config" "^0.4.0" + "@amplitude/analytics-types" "^2.8.2" + "@amplitude/plugin-autocapture-browser" "^1.0.2" + "@amplitude/plugin-page-view-tracking-browser" "^2.3.2" + tslib "^2.4.1" + +"@amplitude/analytics-client-common@>=1 <3", "@amplitude/analytics-client-common@^2.3.3": + version "2.3.3" + resolved "https://registry.yarnpkg.com/@amplitude/analytics-client-common/-/analytics-client-common-2.3.3.tgz#7a23d72d8884b2723043274f13c7ece35104d62d" + integrity sha512-HOvD2A8DH0y2LATrQ0AhFSOCk6yZayebu4+UsEBT76Q7EEYtnc59WMCRRIi5Zv6OqHpOvABumF7g3HmL6ehTYA== + dependencies: + "@amplitude/analytics-connector" "^1.4.8" + "@amplitude/analytics-core" "^2.5.2" + "@amplitude/analytics-types" "^2.8.2" + tslib "^2.4.1" + +"@amplitude/analytics-connector@^1.4.8": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@amplitude/analytics-connector/-/analytics-connector-1.5.0.tgz#89a78b8c6463abe4de1d621db4af6c62f0d62b0a" + integrity sha512-T8mOYzB9RRxckzhL0NTHwdge9xuFxXEOplC8B1Y3UX3NHa3BLh7DlBUZlCOwQgMc2nxDfnSweDL5S3bhC+W90g== + +"@amplitude/analytics-core@>=1 <3", "@amplitude/analytics-core@^2.5.2": + version "2.5.2" + resolved "https://registry.yarnpkg.com/@amplitude/analytics-core/-/analytics-core-2.5.2.tgz#502cb294dad94ec4dd8330e19228d9101ebaa0ad" + integrity sha512-4ojYUL7LA+qrlaz1n1nxpsbpgS1k6DOrQ3fBiQOuDJE8Av0aZfylDksFPnZvD1+MMdIm/ONkVAYfEaW3x/uH3Q== + dependencies: + "@amplitude/analytics-types" "^2.8.2" + tslib "^2.4.1" + +"@amplitude/analytics-remote-config@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@amplitude/analytics-remote-config/-/analytics-remote-config-0.4.0.tgz#e9835836ef40c6b2e72bc8c7a88803dda5559556" + integrity sha512-ilp9Dz8Z92V9Wilmz8XIbvEbtuVaN65+jM06JP8I7wL8eNOHVIi4HcI151BzIyekjbprbS1w18Ps3dj2sHlFXA== + dependencies: + "@amplitude/analytics-client-common" ">=1 <3" + "@amplitude/analytics-core" ">=1 <3" + "@amplitude/analytics-types" ">=1 <3" + tslib "^2.4.1" + +"@amplitude/analytics-types@>=1 <3", "@amplitude/analytics-types@^2.8.2": + version "2.8.2" + resolved "https://registry.yarnpkg.com/@amplitude/analytics-types/-/analytics-types-2.8.2.tgz#aee2b0d42c962d8408056b45b957f18dbb2529ef" + integrity sha512-SWFXIMxhFm1/k3PUvxvYLY1iwzS28yd9A6pa5pEnrbaAZwM+E/24ucxs59VGp1N5qlIsvF0aVGSoKzN4ydh4eA== + +"@amplitude/plugin-autocapture-browser@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@amplitude/plugin-autocapture-browser/-/plugin-autocapture-browser-1.0.2.tgz#9f983ebab82142311dfe35e77a54c539e8293279" + integrity sha512-zk5cSquX0OO/zWsw3E+lHHvdCOJdZZvlKRjrt6J2llSs1hxBNMwV5peliXr8uO5d8oIcHjUrscKJK07mfjx6Fg== + dependencies: + "@amplitude/analytics-client-common" ">=1 <3" + "@amplitude/analytics-types" ">=1 <3" + rxjs "^7.8.1" + tslib "^2.4.1" + +"@amplitude/plugin-page-view-tracking-browser@^2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.3.2.tgz#92e80abbac3bfc078080fc98ce2a4a035c0a96d0" + integrity sha512-02ngtIYsbdjOgW48JY+RC0rtV9VqnNLew40f+zKmod5yeY4tkITMnHP93saELz7cTfjwH0aoPbtBSwRCP0YvRw== + dependencies: + "@amplitude/analytics-client-common" "^2.3.3" + "@amplitude/analytics-types" "^2.8.2" + tslib "^2.4.1" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465" @@ -795,6 +865,26 @@ resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.0.tgz#6df8d983546cfd1999c8512f3a8ad85a6e7fcee8" integrity sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A== +"@radix-ui/react-dialog@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.1.1.tgz#4906507f7b4ad31e22d7dad69d9330c87c431d44" + integrity sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg== + dependencies: + "@radix-ui/primitive" "1.1.0" + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-context" "1.1.0" + "@radix-ui/react-dismissable-layer" "1.1.0" + "@radix-ui/react-focus-guards" "1.1.0" + "@radix-ui/react-focus-scope" "1.1.0" + "@radix-ui/react-id" "1.1.0" + "@radix-ui/react-portal" "1.1.1" + "@radix-ui/react-presence" "1.1.0" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-slot" "1.1.0" + "@radix-ui/react-use-controllable-state" "1.1.0" + aria-hidden "^1.1.1" + react-remove-scroll "2.5.7" + "@radix-ui/react-direction@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.0.1.tgz#9cb61bf2ccf568f3421422d182637b7f47596c9b" @@ -1946,9 +2036,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001587: - version "1.0.30001607" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001607.tgz#b91e8e033f6bca4e13d3d45388d87fa88931d9a5" - integrity sha512-WcvhVRjXLKFB/kmOFVwELtMxyhq3iM/MvmXcyCe2PNf166c39mptscOc/45TTS96n2gpNV2z7+NakArTWZCQ3w== + version "1.0.30001664" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001664.tgz" + integrity sha512-AmE7k4dXiNKQipgn7a2xg558IRqPN3jMQY/rOsbxDhrd0tyChwbITBfiwtnqz8bi2M5mIWbxAYBvk7W7QBUS2g== chalk@^2.4.2: version "2.4.2" @@ -4171,6 +4261,13 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" +rxjs@^7.8.1: + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + dependencies: + tslib "^2.1.0" + safe-array-concat@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" @@ -4443,6 +4540,11 @@ tslib@^2.4.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== +tslib@^2.4.1: + version "2.7.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" + integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"