diff --git a/src/App.tsx b/src/App.tsx index 6657a9fa..7ccada3d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,7 +6,7 @@ import PWABadge from '~/PWABadge' import { router } from '~/router' import GlobalStyle from '~/styles/globalStyle' import { darkTheme, lightTheme } from '~/styles/theme' -import Loader from '~components/Loader' +import PageLoader from '~components/PageLoader' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { ReactQueryDevtools } from '@tanstack/react-query-devtools' @@ -29,7 +29,7 @@ function App() { - }> + }> diff --git a/src/assets/dog_run1.svg b/src/assets/dog_run1.svg new file mode 100644 index 00000000..39b7d60d --- /dev/null +++ b/src/assets/dog_run1.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/dog_run2.svg b/src/assets/dog_run2.svg new file mode 100644 index 00000000..04be8f77 --- /dev/null +++ b/src/assets/dog_run2.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/dog_run3.svg b/src/assets/dog_run3.svg new file mode 100644 index 00000000..0a68883c --- /dev/null +++ b/src/assets/dog_run3.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/dog_run4.svg b/src/assets/dog_run4.svg new file mode 100644 index 00000000..c2cb03ef --- /dev/null +++ b/src/assets/dog_run4.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/Button/ActionButton.ts b/src/components/Button/ActionButton.ts index 9f2eadde..41477277 100644 --- a/src/components/Button/ActionButton.ts +++ b/src/components/Button/ActionButton.ts @@ -59,4 +59,10 @@ export const ActionButton = styled.button` font-weight: ${({ $fontWeight }) => $fontWeight}; cursor: not-allowed; } + &:hover { + filter: brightness(1.1); + } + &:active { + filter: brightness(1.05); + } ` diff --git a/src/components/ErrorFallback/index.tsx b/src/components/ErrorFallback/index.tsx index ce24d21a..066f1158 100644 --- a/src/components/ErrorFallback/index.tsx +++ b/src/components/ErrorFallback/index.tsx @@ -1,6 +1,5 @@ import { FallbackProps } from 'react-error-boundary' -import { ActionButton } from '~components/Button/ActionButton' -import { Typo15, Typo17 } from '~components/Typo' +import { Typo15 } from '~components/Typo' import * as S from './styles' type ErrorFallbackProps = FallbackProps @@ -8,16 +7,8 @@ type ErrorFallbackProps = FallbackProps export default function ErrorFallback({ error, resetErrorBoundary }: ErrorFallbackProps) { return ( - {error.message}... - - 오류가 발생했네요! -
- 아래 버튼을 통해 다시 요청해보세요! -
- - resetErrorBoundary()}> - 다시 불러오기! - + {error.message}... + resetErrorBoundary()}>다시 불러오기!
) } diff --git a/src/components/ErrorFallback/styles.ts b/src/components/ErrorFallback/styles.ts index 64fcf625..77e0cc4c 100644 --- a/src/components/ErrorFallback/styles.ts +++ b/src/components/ErrorFallback/styles.ts @@ -1,3 +1,15 @@ import { styled } from 'styled-components' -export const ErrorFallback = styled.div`` +export const ErrorFallback = styled.div` + padding: 20px; + margin: 0 auto; + width: 100%; +` +export const ResetButton = styled.button` + padding: 5px; + background-color: ${({ theme }) => theme.colors.brand.default}; + color: ${({ theme }) => theme.colors.grayscale.gc_4}; + width: 100%; + border-radius: 10px; + margin-top: 4px; +` diff --git a/src/components/InfiniteScrollTrigger.ts b/src/components/InfiniteScrollTrigger.ts index b9ffe56a..ed4cf3f6 100644 --- a/src/components/InfiniteScrollTrigger.ts +++ b/src/components/InfiniteScrollTrigger.ts @@ -4,5 +4,8 @@ type InfiniteScrollTriggerProps = { $height?: number } export const InfiniteScrollTrigger = styled.div` - height: ${({ $height = 20 }) => $height + 'px'}; + height: ${({ $height = 30 }) => $height + 'px'}; + display: flex; + justify-content: center; + align-items: center; ` diff --git a/src/components/Loader/index.tsx b/src/components/Loader/index.tsx deleted file mode 100644 index f6c1f0d7..00000000 --- a/src/components/Loader/index.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import * as S from './styles' - -export default function Loader() { - return Loader... -} diff --git a/src/components/Loader/styles.ts b/src/components/Loader/styles.ts deleted file mode 100644 index 6beff9da..00000000 --- a/src/components/Loader/styles.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { styled } from 'styled-components' - -export const Loader = styled.div`` diff --git a/src/components/NotificationList/index.tsx b/src/components/NotificationList/index.tsx index 0d7ec2da..683f5d70 100644 --- a/src/components/NotificationList/index.tsx +++ b/src/components/NotificationList/index.tsx @@ -1,9 +1,9 @@ import useInfiniteNotificationList from '~apis/notification/useInfiniteNotificationList' -import Loader from '~components/Loader' +import { InfiniteScrollTrigger } from '~components/InfiniteScrollTrigger' import NotificationItem from '~components/NotificationItem' +import { Spinner } from '~components/Spinner' import useObserver from '~hooks/useObserver' import * as S from './styles' -import { InfiniteScrollTrigger } from '~components/InfiniteScrollTrigger' export default function NotificationList() { const { observerRef } = useObserver({ @@ -23,7 +23,9 @@ export default function NotificationList() { /> )) )} - {isFetchingNextPage && } + + {isFetchingNextPage && } + ) } diff --git a/src/components/PageLoader/index.tsx b/src/components/PageLoader/index.tsx new file mode 100644 index 00000000..938c0101 --- /dev/null +++ b/src/components/PageLoader/index.tsx @@ -0,0 +1,18 @@ +import DogRun1 from '~assets/dog_run1.svg?react' +import DogRun2 from '~assets/dog_run2.svg?react' +import DogRun3 from '~assets/dog_run3.svg?react' +import DogRun4 from '~assets/dog_run4.svg?react' +import * as S from './styles' + +export default function PageLoader() { + return ( + + + + + + + + + ) +} diff --git a/src/components/PageLoader/styles.ts b/src/components/PageLoader/styles.ts new file mode 100644 index 00000000..a6516377 --- /dev/null +++ b/src/components/PageLoader/styles.ts @@ -0,0 +1,48 @@ +import { styled, keyframes } from 'styled-components' + +export const PageLoader = styled.div` + position: fixed; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: ${({ theme }) => theme.colors.brand.lighten_3}; +` + +const dogAnimation = keyframes` + 0%, 24% { opacity: 1; } + 25%, 100% { opacity: 0; } +` + +export const DogAnimation = styled.div` + position: absolute; + left: 50%; + top: 50%; + translate: -50% -50%; + width: 50%; + height: 50%; + + & > svg { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + opacity: 0; + animation: ${dogAnimation} 1s steps(1) infinite; + } + + & > svg:nth-child(2) { + animation-delay: 0.25s; + } + + & > svg:nth-child(3) { + animation-delay: 0.5s; + } + + & > svg:nth-child(4) { + width: 92%; + height: 92%; + animation-delay: 0.75s; + } +` diff --git a/src/components/Spinner.ts b/src/components/Spinner.ts new file mode 100644 index 00000000..ee829fa5 --- /dev/null +++ b/src/components/Spinner.ts @@ -0,0 +1,36 @@ +// styles.js +import styled from 'styled-components' + +export const Spinner = styled.div<{ + $size?: number + $stroke?: number +}>` + border: ${({ $stroke }) => $stroke || 4}px solid ${({ theme }) => theme.colors.brand.default}; + border-top: ${({ $stroke }) => $stroke || 4}px solid transparent; + border-radius: 50%; + width: ${props => props.$size || 30}px; + height: ${props => props.$size || 30}px; + animation: spin 0.9s linear infinite; + + @keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } + } +` + +export const PageLoader = styled.div` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.8); + display: flex; + justify-content: center; + align-items: center; + z-index: 9999; +` diff --git a/src/modals/ChatModal/index.tsx b/src/modals/ChatModal/index.tsx index 32f7745a..8f661456 100644 --- a/src/modals/ChatModal/index.tsx +++ b/src/modals/ChatModal/index.tsx @@ -10,9 +10,10 @@ import { useFetchProfile } from '~apis/member/useFetchProfile' import { QueryErrorResetBoundary } from '@tanstack/react-query' import { ErrorBoundary } from 'react-error-boundary' import { Suspense } from 'react' -import Loader from '~components/Loader' +import PageLoader from '~components/PageLoader' import ErrorFallback from '~components/ErrorFallback' import { FAMILY_ROLE } from '~constants/familyRole' +import { Spinner } from '~components/Spinner' type ChatModalProps = { chatRoomId: number @@ -23,13 +24,15 @@ export default function ChatModal({ chatRoomId, opponentMemberId }: ChatModalPro return ( + + + }> + + + + - }> - - - - - }> + }> diff --git a/src/modals/ChatModal/styles.ts b/src/modals/ChatModal/styles.ts index 4582f538..213b492e 100644 --- a/src/modals/ChatModal/styles.ts +++ b/src/modals/ChatModal/styles.ts @@ -37,3 +37,16 @@ export const EllipsisWrapper = styled.div` position: absolute; right: 20px; ` + +export const FallbackWrapper = styled.div` + position: absolute; + height: ${HEADER_HEIGHT_LG}px; + left: 0; + top: 0; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + z-index: 10; + background-color: ${({ theme }) => theme.colors.brand.lighten_2}; +` diff --git a/src/modals/NotificationModal/index.tsx b/src/modals/NotificationModal/index.tsx index eb7cf9e2..3e2999bf 100644 --- a/src/modals/NotificationModal/index.tsx +++ b/src/modals/NotificationModal/index.tsx @@ -6,7 +6,7 @@ import { QueryErrorResetBoundary } from '@tanstack/react-query' import { Suspense } from 'react' import { ErrorBoundary } from 'react-error-boundary' import ErrorFallback from '~components/ErrorFallback' -import Loader from '~components/Loader' +import PageLoader from '~components/PageLoader' export default function NotificationModal() { const { popModal } = useModalStore() @@ -16,7 +16,7 @@ export default function NotificationModal() { {({ reset }) => ( - }> + }> diff --git a/src/modals/WalkAnalysisModal/index.tsx b/src/modals/WalkAnalysisModal/index.tsx index 7369d422..ae655742 100644 --- a/src/modals/WalkAnalysisModal/index.tsx +++ b/src/modals/WalkAnalysisModal/index.tsx @@ -9,7 +9,7 @@ import { useCurrentMonthWalks, useFamilyWalks, useMonthlyWalks, useTotalWalks } import { QueryErrorResetBoundary } from '@tanstack/react-query' import { ErrorBoundary } from 'react-error-boundary' import ErrorFallback from '~components/ErrorFallback' -import Loader from '~components/Loader' +import PageLoader from '~components/PageLoader' interface ChartData { month: string @@ -58,7 +58,7 @@ export default function WalkAnalysisModal() { {({ reset }) => ( - }> + }> 산책 분석 diff --git a/src/pages/HomePage/index.tsx b/src/pages/HomePage/index.tsx index df6c11dc..115b62d0 100644 --- a/src/pages/HomePage/index.tsx +++ b/src/pages/HomePage/index.tsx @@ -3,6 +3,8 @@ import { Suspense, useEffect } from 'react' import { ErrorBoundary } from 'react-error-boundary' import { Helmet } from 'react-helmet-async' import { useNavigate, useSearchParams } from 'react-router-dom' +import { useWebSocket } from '~/WebSocketContext' +import { FetchChatMessageListResponse } from '~apis/chat/fetchChatMessageList' import { useHomePageData } from '~apis/main/useHomePageData' import DogHand from '~assets/dog_hand.svg?react' import BellIcon from '~assets/icons/bell_icon.svg?react' @@ -10,18 +12,16 @@ import ClockIcon from '~assets/icons/clock_icon.svg?react' import GPSIcon from '~assets/icons/gps_icon.svg?react' import { ActionButton } from '~components/Button/ActionButton' import ErrorFallback from '~components/ErrorFallback' -import Loader from '~components/Loader' +import PageLoader from '~components/PageLoader' import Profile from '~components/Profile' import { Separator } from '~components/Separator' import { Typo14, Typo17, Typo24 } from '~components/Typo' import { FAMILY_ROLE } from '~constants/familyRole' +import { queryKey } from '~constants/queryKey' import NotificationModal from '~modals/NotificationModal' import { useModalStore } from '~stores/modalStore' -import * as S from './styles' -import { useWebSocket } from '~/WebSocketContext' -import { FetchChatMessageListResponse } from '~apis/chat/fetchChatMessageList' -import { queryKey } from '~constants/queryKey' import { APIResponse, CommonAPIResponse } from '~types/api' +import * as S from './styles' function HomeContent() { const { isConnected, subscribe } = useWebSocket() @@ -121,13 +121,13 @@ function HomeContent() { }) } }, [isConnected]) + return ( <> pushModal()} /> - 오늘은 {data?.familyRole ? FAMILY_ROLE[data.familyRole] : ''}랑 @@ -206,7 +206,7 @@ export default function HomePage() { {({ reset }) => ( - }> + }> diff --git a/src/pages/LogPage/index.tsx b/src/pages/LogPage/index.tsx index 23003344..983017a3 100644 --- a/src/pages/LogPage/index.tsx +++ b/src/pages/LogPage/index.tsx @@ -14,7 +14,7 @@ import { useWalkDetail } from './useWalkInfo' import { QueryErrorResetBoundary } from '@tanstack/react-query' import { ErrorBoundary } from 'react-error-boundary' import ErrorFallback from '~components/ErrorFallback' -import Loader from '~components/Loader' +import PageLoader from '~components/PageLoader' export default function LogPage() { const images = [NoWalkSummaryImg, NoWalkSummaryImg2] @@ -40,7 +40,7 @@ export default function LogPage() { {({ reset }) => ( - }> + }> {walkDetails?.map(walkDetail => ( {({ reset }) => ( - }> + }> diff --git a/src/pages/SocialPage/index.tsx b/src/pages/SocialPage/index.tsx index 494240dd..99f5a370 100644 --- a/src/pages/SocialPage/index.tsx +++ b/src/pages/SocialPage/index.tsx @@ -5,7 +5,7 @@ import * as S from './styles' import { ErrorBoundary } from 'react-error-boundary' import ErrorFallback from '~components/ErrorFallback' import { QueryErrorResetBoundary } from '@tanstack/react-query' -import Loader from '~components/Loader' +import PageLoader from '~components/PageLoader' export default function SocialPage() { const [selectedTab, setSelectedTab] = useState<'friendList' | 'dangTalk'>('friendList') @@ -33,7 +33,7 @@ export default function SocialPage() { {({ reset }) => ( - }> + }> diff --git a/src/styles/globalStyle.ts b/src/styles/globalStyle.ts index 308dbe60..d08df507 100644 --- a/src/styles/globalStyle.ts +++ b/src/styles/globalStyle.ts @@ -10,7 +10,7 @@ const GlobalStyle = createGlobalStyle` margin: 0; padding: 0; } - + /* SUIT 폰트 패밀리 설정 */ @font-face { font-family: 'SUIT';