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';