Repository: JECT-Study/JECT-Official-WebSite-Client
Pull request #192: feat: 매일 오전 5시~7시 점검 페이지 전환 기능 구현
Author: login: rkdcodus
Labels: name: ✨feature
description: 구현, 개선 사항 관련 부분
Created: 2025-04-18T15:50:30Z | Merged: 2025-04-19T08:46:59Z

## 💡 작업 내용

- [x] useRedirectMaintenance 훅 생성 
- [x] Maintenance 페이지 모바일용 푸터 스타일 추가

## 💡 자세한 설명

### 1️⃣ useRedirectMaintenance 훅

오전 5시~7시 사이에 페이지가 로드되면 점검 페이지로 이동합니다. 
setTimeout을 이용하여 `checkAndRedirect` 함수를 다시 호출하여 점검시간인지 확인합니다.
`checkAndRedirect` 함수라 재귀로 돌아가는 구조입니다. 

```ts
const useRedirectMaintenance = (startHours: number, endHours: number, redirectPath: PathValues) => {
  const timeoutRef = useRef<number | null>(null); // 생성한 타이머 id를 저장해둘 ref 

  useEffect(() => {
    const checkAndRedirect = () => { // 점검 페이지로 리다이렉트할건지 체크하는 함수 
      const now = new Date();
      const hours = now.getHours();

      if (hours >= startHours && hours < endHours) { // 점검 시간이라면 리다이렉트한 후 함수 종료
        return router.navigate(redirectPath);
      }
      
      // 점검 시간이 아니라면 
      const nextTime = new Date(); // 남은 시간을 체크하기 위해 현재 시간 불러옴

      if (hours < startHours) { // 시작 시간 전이라면 nextTime을 시작 시간으로 맞춤
        nextTime.setHours(startHours, 0, 0, 0); 
      } else if (hours >= endHours) { // 끝나는 시간 후라면 다음 날 시작시간으로 맞춤.
        nextTime.setDate(nextTime.getDate() + 1);
        nextTime.setHours(startHours, 0, 0, 0);
      }

      const delay = nextTime.getTime() - now.getTime(); // 현재 시간으로부터 점검 시작 시간까지의 남은 시간 계산

      const timerTime = TIME_THRESHOLD < delay ? delay / 2 : delay; // 남은 시간이 10분 내외라면 그대로 진행, 10분보다 많이 남았다면 절반으로 줄임.

      if (timeoutRef.current !== null) clearTimeout(timeoutRef.current); // setTimeout 설정 전 남아있는 이전 타이머 종료시키기

      timeoutRef.current = setTimeout(() => { // 지정한 timerTime 에 따라 checkAndRedirect() 호출 타이머 걸기
        void checkAndRedirect();
      }, timerTime); 
    };

    void checkAndRedirect(); // 첫 로드 시 일단 한번 실행

    return () => {
      if (timeoutRef.current !== null) clearTimeout(timeoutRef.current); // 언마운트 시 타이머 종료 
    };
  }, [startHours, endHours, redirectPath]);
};
```
페이지에 계속 남아있을 경우 checkAndRedirect 함수가 타이머에 의해 재호출되면서 점검시간인지 확인하는 로직입니다.

테스트해봤을 때 새로고침 시 ( 점검 시간에 페이지 접근 시)에 점검 페이지로 잘 바뀌는 것을 확인했습니다. 

### 2️⃣ 시간 설정
App.tsx 에서 해당 훅을 호출했고 새벽 5시~7시로 설정했습니다. 
이동할 점검 페이지는 모바일로 접근했을 경우의 디자인을 보여주기 위해 임시로 푸터만 스타일 추가했습니다.

## 📗 참고 자료 (선택)

## 📢 리뷰 요구 사항 (선택)
checkAndRedirect 함수의 로직이 전체적으로 괜찮은지 확인받고싶습니다..! 
(빠진 예외가 있을 것 같아서)

## ✅ 셀프 체크리스트

- [x] 머지할 브랜치 확인했나요?
- [x] 이슈는 close 했나요?
- [x] Reviewers, Labels, Projects를 등록했나요?
- [x] 기능이 잘 동작하나요?
- [x] 불필요한 코드는 제거했나요?

closes #190 


Linked issue #190: feat: 일정 시간동안 점검 페이지 전환되도록 구현
## 💡 구현 기능
매일 오전 5시~7시에 전체 페이지가 점검 페이지로 변환되도록 구현합니다.

## ✅ 체크리스트
- [ ] 시간에 맞춰 점검 페이지 전환 기능 

## ⏰ 예상 소요 시간


Diff:
diff --git a/src/App.tsx b/src/App.tsx
index 9fb4e4fbe..94ad2aae4 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -6,7 +6,9 @@ import { useState } from 'react';
 import { isMobile } from 'react-device-detect';
 import { RouterProvider } from 'react-router-dom';
 
+import { PATH } from './constants/path';
 import { useGlobalErrorHandler } from './hooks/useGlobalErrorHandler';
+import useRedirectMaintenance from './hooks/useRedirectMaintenance';
 import TempMobile from './pages/TempMobile';
 import router from './router';
 
@@ -25,6 +27,8 @@ function App() {
       }),
   );
 
+  useRedirectMaintenance(5, 7, PATH.maintenance);
+
   if (isMobile) {
     return <TempMobile />;
   }
diff --git a/src/components/common/footer/MobileFooter.tsx b/src/components/common/footer/MobileFooter.tsx
new file mode 100644
index 000000000..7c21377f5
--- /dev/null
+++ b/src/components/common/footer/MobileFooter.tsx
@@ -0,0 +1,34 @@
+import Youtube from '@/assets/svg/footerYoutube.svg?react';
+import Github from '@/assets/svg/github.svg?react';
+import NewTabLink from '@/components/apply/NewTabLink';
+import Logo from '@/components/common/logo/Logo';
+import { JECT_EMAIL } from '@/constants/footer';
+
+function MobileFooter() {
+  return (
+    <div className='bg-surface-standard-dark'>
+      <footer className='bg-surface-tinted-dark text-object-alternative-dark gap-md flex min-h-[8.875rem] flex-col p-(--gap-md)'>
+        <div>
+          <Logo height={16} fillColor='fill-object-normal-dark' />
+        </div>
+        <p className='body-2xs'>
+          젝트는 IT 사이드 프로젝트 동아리입니다.
+          <br /> 몰입하며 진행하는 즐거운 프로젝트를 지향하고 있어요.
+        </p>
+        <div className='flex items-center'>
+          <p className='body-2xs flex-1'>{JECT_EMAIL}</p>
+          <div className='flex h-[1.5rem] gap-[1.25rem]'>
+            <NewTabLink href='https://github.com/JECT-Study'>
+              <Github width={24} />
+            </NewTabLink>
+            <NewTabLink href='https://www.youtube.com/@ject_it_club'>
+              <Youtube height={24} />
+            </NewTabLink>
+          </div>
+        </div>
+      </footer>
+    </div>
+  );
+}
+
+export default MobileFooter;
diff --git a/src/hooks/useRedirectMaintenance.ts b/src/hooks/useRedirectMaintenance.ts
new file mode 100644
index 000000000..c7774ca0c
--- /dev/null
+++ b/src/hooks/useRedirectMaintenance.ts
@@ -0,0 +1,60 @@
+import { useEffect, useRef } from 'react';
+
+import { PATH } from '@/constants/path';
+import router from '@/router';
+import { PathValues } from '@/types/ui/path';
+
+const TIME_THRESHOLD = 10 * 60 * 1000;
+
+const useRedirectMaintenance = (startHours: number, endHours: number, redirectPath: PathValues) => {
+  const timeoutRef = useRef<number | null>(null);
+
+  useEffect(() => {
+    const checkAndRedirect = () => {
+      const now = new Date();
+      const nextTime = new Date(now);
+      const hours = now.getHours();
+      const isInMaintenance = hours >= startHours && hours < endHours;
+      const isOnMaintenancePage = location.pathname === redirectPath;
+
+      if (!isOnMaintenancePage && isInMaintenance) {
+        const currentPath = location.pathname;
+
+        sessionStorage.setItem('recoverPath', currentPath);
+        void router.navigate(redirectPath);
+      } else if (!isInMaintenance && isOnMaintenancePage) {
+        const recoverPath = sessionStorage.getItem('recoverPath') ?? PATH.main;
+
+        sessionStorage.removeItem('recoverPath');
+        void router.navigate(recoverPath);
+      }
+
+      if (hours < startHours) {
+        nextTime.setHours(startHours, 0, 0, 0);
+      } else if (hours >= endHours) {
+        nextTime.setDate(nextTime.getDate() + 1);
+        nextTime.setHours(startHours, 0, 0, 0);
+      } else {
+        nextTime.setHours(endHours, 0, 0, 0);
+      }
+
+      const delay = nextTime.getTime() - now.getTime();
+
+      const timerTime = TIME_THRESHOLD < delay ? delay - TIME_THRESHOLD : delay;
+
+      if (timeoutRef.current !== null) clearTimeout(timeoutRef.current);
+
+      timeoutRef.current = setTimeout(() => {
+        void checkAndRedirect();
+      }, timerTime);
+    };
+
+    void checkAndRedirect();
+
+    return () => {
+      if (timeoutRef.current !== null) clearTimeout(timeoutRef.current);
+    };
+  }, [startHours, endHours, redirectPath]);
+};
+
+export default useRedirectMaintenance;
diff --git a/src/pages/Maintenance.tsx b/src/pages/Maintenance.tsx
index 8d639252f..5732a1f96 100644
--- a/src/pages/Maintenance.tsx
+++ b/src/pages/Maintenance.tsx
@@ -1,5 +1,8 @@
+import { isMobile } from 'react-device-detect';
+
 import coneImage from '@/assets/images/cone.png';
 import Footer from '@/components/common/footer/Footer';
+import MobileFooter from '@/components/common/footer/MobileFooter';
 import PagesContainer from '@/components/layout/PagesContainer';
 
 function Maintenance() {
@@ -14,7 +17,7 @@ function Maintenance() {
           </div>
         </div>
       </PagesContainer>
-      <Footer />
+      {isMobile ? <MobileFooter /> : <Footer />}
     </div>
   );
 }
diff --git a/src/pages/TempMobile.tsx b/src/pages/TempMobile.tsx
index 32b56f3aa..9b27365cf 100644
--- a/src/pages/TempMobile.tsx
+++ b/src/pages/TempMobile.tsx
@@ -1,9 +1,6 @@
 import coneImage from '@/assets/images/cone.png';
-import Youtube from '@/assets/svg/footerYoutube.svg?react';
-import Github from '@/assets/svg/github.svg?react';
-import NewTabLink from '@/components/apply/NewTabLink';
+import MobileFooter from '@/components/common/footer/MobileFooter';
 import Logo from '@/components/common/logo/Logo';
-import { JECT_EMAIL } from '@/constants/footer';
 
 function TempMobile() {
   return (
@@ -22,28 +19,7 @@ function TempMobile() {
           </p>
         </div>
       </div>
-      <div className='bg-surface-standard-dark'>
-        <footer className='bg-surface-tinted-dark text-object-alternative-dark gap-md flex min-h-[8.875rem] flex-col p-(--gap-md)'>
-          <div>
-            <Logo height={16} fillColor='fill-object-normal-dark' />
-          </div>
-          <p className='body-2xs'>
-            젝트는 IT 사이드 프로젝트 동아리입니다.
-            <br /> 몰입하며 진행하는 즐거운 프로젝트를 지향하고 있어요.
-          </p>
-          <div className='flex items-center'>
-            <p className='body-2xs flex-1'>{JECT_EMAIL}</p>
-            <div className='flex h-[1.5rem] gap-[1.25rem]'>
-              <NewTabLink href='https://github.com/JECT-Study'>
-                <Github width={24} />
-              </NewTabLink>
-              <NewTabLink href='https://www.youtube.com/@ject_it_club'>
-                <Youtube height={24} />
-              </NewTabLink>
-            </div>
-          </div>
-        </footer>
-      </div>
+      <MobileFooter />
     </div>
   );
 }


Reviews:
user: login: WonJuneKim
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T22:47:57Z
user: login: rkdcodus
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-19T05:15:40Z
user: login: rkdcodus
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-19T05:49:41Z
user: login: rkdcodus
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-19T07:12:00Z
user: login: WonJuneKim
body: 수고 많으셨습니다~ 추가 코멘트만 한번 확인해주세요!
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-19T08:05:42Z
user: login: rkdcodus
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-19T08:46:02Z

Review comments:
diff_hunk: @@ -0,0 +1,48 @@
+import { useEffect, useRef } from 'react';
+
+import router from '@/router';
+import { PathValues } from '@/types/ui/path';
+
+const TIME_THRESHOLD = 10 * 60 * 1000;
+
+const useRedirectMaintenance = (startHours: number, endHours: number, redirectPath: PathValues) => {
+  const timeoutRef = useRef<number | null>(null);
+
+  useEffect(() => {
+    const checkAndRedirect = () => {
+      const now = new Date();
+      const hours = now.getHours();
+
+      if (hours >= startHours && hours < endHours) {
+        return router.navigate(redirectPath);
path: src/hooks/useRedirectMaintenance.ts
user: login: WonJuneKim
body: useNavigate를 직접 사용하지 않으신 이유가 있는지 질문드려요!
created_at: 2025-04-18T22:26:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,48 @@
+import { useEffect, useRef } from 'react';
+
+import router from '@/router';
+import { PathValues } from '@/types/ui/path';
+
+const TIME_THRESHOLD = 10 * 60 * 1000;
+
+const useRedirectMaintenance = (startHours: number, endHours: number, redirectPath: PathValues) => {
+  const timeoutRef = useRef<number | null>(null);
+
+  useEffect(() => {
+    const checkAndRedirect = () => {
+      const now = new Date();
+      const hours = now.getHours();
+
+      if (hours >= startHours && hours < endHours) {
+        return router.navigate(redirectPath);
+      }
+
+      const nextTime = new Date();
path: src/hooks/useRedirectMaintenance.ts
user: login: WonJuneKim
body: 13line 에서 선언한 시각을 활용해
```suggestion
      const nextTime = new Date(now);
```
로 설정하면 객체 생성이 한 번만 일어나고, 선언 부에 함께 선언할 수 있겠네요!
created_at: 2025-04-18T22:35:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,48 @@
+import { useEffect, useRef } from 'react';
+
+import router from '@/router';
+import { PathValues } from '@/types/ui/path';
+
+const TIME_THRESHOLD = 10 * 60 * 1000;
+
+const useRedirectMaintenance = (startHours: number, endHours: number, redirectPath: PathValues) => {
+  const timeoutRef = useRef<number | null>(null);
+
+  useEffect(() => {
+    const checkAndRedirect = () => {
+      const now = new Date();
+      const hours = now.getHours();
+
+      if (hours >= startHours && hours < endHours) {
+        return router.navigate(redirectPath);
+      }
+
+      const nextTime = new Date();
+
+      if (hours < startHours) {
+        nextTime.setHours(startHours, 0, 0, 0);
+      } else if (hours >= endHours) {
+        nextTime.setDate(nextTime.getDate() + 1);
+        nextTime.setHours(startHours, 0, 0, 0);
+      }
+
+      const delay = nextTime.getTime() - now.getTime();
+
+      const timerTime = TIME_THRESHOLD < delay ? delay / 2 : delay;
+
+      if (timeoutRef.current !== null) clearTimeout(timeoutRef.current);
+
+      timeoutRef.current = setTimeout(() => {
+        void checkAndRedirect();
+      }, timerTime);
+    };
+
+    void checkAndRedirect();
path: src/hooks/useRedirectMaintenance.ts
user: login: WonJuneKim
body: 예외적인 상황보다는 개선점이 보이는데, 스케쥴링을 짤 때 개인적으로는 10분이라는 고정 값을 활용할 경우 `점검 시간 10분전 / 점검 시간` 으로만 처리되어도 충분해 보이네요. 

스케쥴링 방식에는 useEffect + 의존성 배열로 처리하는 방식도 있고, 찾아보니 여러가지가 있는 거 같은데, 결국 클라이언트 단에서는 한계가 있어서 + 결국 100% 엄밀하게는 불가능 하기 때문에 타이머 호출 자체를 축소시키는 방안도 좋아보여요.

또한 이 라우팅 로직의 경우 근본적으로는 서버에서 특정 응답 값에 대한 Error page 로 포워딩(aws 내 버킷 또는 cloudfront 설정) 하는 방식이 더 적절하다고 생각됩니다!!
created_at: 2025-04-18T22:44:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -14,7 +21,32 @@ function Maintenance() {
           </div>
         </div>
       </PagesContainer>
-      <Footer />
+      {isMobile ? (
+        <div className='bg-surface-standard-dark'>
+          <footer className='bg-surface-tinted-dark text-object-alternative-dark gap-md flex min-h-[8.875rem] flex-col p-(--gap-md)'>
+            <div>
+              <Logo height={16} fillColor='fill-object-normal-dark' />
path: src/pages/Maintenance.tsx
user: login: WonJuneKim
body: 일단 해당 페이지도 라우트 처리 해놓으면 좋을 것 같아요! 
created_at: 2025-04-18T22:47:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,48 @@
+import { useEffect, useRef } from 'react';
+
+import router from '@/router';
+import { PathValues } from '@/types/ui/path';
+
+const TIME_THRESHOLD = 10 * 60 * 1000;
+
+const useRedirectMaintenance = (startHours: number, endHours: number, redirectPath: PathValues) => {
+  const timeoutRef = useRef<number | null>(null);
+
+  useEffect(() => {
+    const checkAndRedirect = () => {
+      const now = new Date();
+      const hours = now.getHours();
+
+      if (hours >= startHours && hours < endHours) {
+        return router.navigate(redirectPath);
path: src/hooks/useRedirectMaintenance.ts
user: login: rkdcodus
body: 이 훅이 RouterProvider 내부에서 사용하지 않다 보니까 useNavigate를 사용할 수 없었습니다!

created_at: 2025-04-19T05:15:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,48 @@
+import { useEffect, useRef } from 'react';
+
+import router from '@/router';
+import { PathValues } from '@/types/ui/path';
+
+const TIME_THRESHOLD = 10 * 60 * 1000;
+
+const useRedirectMaintenance = (startHours: number, endHours: number, redirectPath: PathValues) => {
+  const timeoutRef = useRef<number | null>(null);
+
+  useEffect(() => {
+    const checkAndRedirect = () => {
+      const now = new Date();
+      const hours = now.getHours();
+
+      if (hours >= startHours && hours < endHours) {
+        return router.navigate(redirectPath);
+      }
+
+      const nextTime = new Date();
+
+      if (hours < startHours) {
+        nextTime.setHours(startHours, 0, 0, 0);
+      } else if (hours >= endHours) {
+        nextTime.setDate(nextTime.getDate() + 1);
+        nextTime.setHours(startHours, 0, 0, 0);
+      }
+
+      const delay = nextTime.getTime() - now.getTime();
+
+      const timerTime = TIME_THRESHOLD < delay ? delay / 2 : delay;
+
+      if (timeoutRef.current !== null) clearTimeout(timeoutRef.current);
+
+      timeoutRef.current = setTimeout(() => {
+        void checkAndRedirect();
+      }, timerTime);
+    };
+
+    void checkAndRedirect();
path: src/hooks/useRedirectMaintenance.ts
user: login: rkdcodus
body: 맞아요 근본적으로는 서버에서 값을 받아서 처리하는게 가장 적절한 방법이라 논의를 한 번 해보는 것도 좋을 것 같네요!

created_at: 2025-04-19T05:49:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,48 @@
+import { useEffect, useRef } from 'react';
+
+import router from '@/router';
+import { PathValues } from '@/types/ui/path';
+
+const TIME_THRESHOLD = 10 * 60 * 1000;
+
+const useRedirectMaintenance = (startHours: number, endHours: number, redirectPath: PathValues) => {
+  const timeoutRef = useRef<number | null>(null);
+
+  useEffect(() => {
+    const checkAndRedirect = () => {
+      const now = new Date();
+      const hours = now.getHours();
+
+      if (hours >= startHours && hours < endHours) {
+        return router.navigate(redirectPath);
+      }
+
+      const nextTime = new Date();
+
+      if (hours < startHours) {
+        nextTime.setHours(startHours, 0, 0, 0);
+      } else if (hours >= endHours) {
+        nextTime.setDate(nextTime.getDate() + 1);
+        nextTime.setHours(startHours, 0, 0, 0);
+      }
+
+      const delay = nextTime.getTime() - now.getTime();
+
+      const timerTime = TIME_THRESHOLD < delay ? delay / 2 : delay;
+
+      if (timeoutRef.current !== null) clearTimeout(timeoutRef.current);
+
+      timeoutRef.current = setTimeout(() => {
+        void checkAndRedirect();
+      }, timerTime);
+    };
+
+    void checkAndRedirect();
path: src/hooks/useRedirectMaintenance.ts
user: login: rkdcodus
body: 일단 점검 시간 10분전, 점검 시간만 처리되는게 충분하다는 의견에 동의하여 이렇게 수정해두었습니다! [c551858](https://github.com/JECT-Study/JECT-Official-WebSite-Client/pull/192/commits/c5518584bf74977482b78466e78ed683e243c4ee)
추가로, 점검이 끝나는 시간도 타이머 설정해 자동으로 원래 있던 페이지로 돌아오도록 추가 구현했습니다! [
b0969e7](https://github.com/JECT-Study/JECT-Official-WebSite-Client/pull/192/commits/b0969e7f109474c14e0364fdc694fe6f88a70ac6)
created_at: 2025-04-19T07:12:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,34 @@
+import Youtube from '@/assets/svg/footerYoutube.svg?react';
+import Github from '@/assets/svg/github.svg?react';
+import NewTabLink from '@/components/apply/NewTabLink';
+import Logo from '@/components/common/logo/Logo';
+import { JECT_EMAIL } from '@/constants/footer';
+
+function MobileFooter() {
path: src/components/common/footer/MobileFooter.tsx
user: login: WonJuneKim
body: 별도 컴포넌트로 분리하신 부분 좋네요~
created_at: 2025-04-19T08:00:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,60 @@
+import { useEffect, useRef } from 'react';
+
+import { PATH } from '@/constants/path';
+import router from '@/router';
+import { PathValues } from '@/types/ui/path';
+
+const TIME_THRESHOLD = 10 * 60 * 1000;
+
+const useRedirectMaintenance = (startHours: number, endHours: number, redirectPath: PathValues) => {
+  const timeoutRef = useRef<number | null>(null);
+
+  useEffect(() => {
+    const checkAndRedirect = () => {
+      const now = new Date();
+      const nextTime = new Date(now);
+      const hours = now.getHours();
+      const isInMaintenance = hours >= startHours && hours < endHours;
+      const isOnMaintenancePage = location.pathname === redirectPath;
+
+      if (!isOnMaintenancePage && isInMaintenance) {
+        const currentPath = location.pathname;
+
+        sessionStorage.setItem('recoverPath', currentPath);
+        void router.navigate(redirectPath);
+      } else if (!isInMaintenance && isOnMaintenancePage) {
+        const recoverPath = sessionStorage.getItem('recoverPath') ?? PATH.main;
+
+        sessionStorage.removeItem('recoverPath');
+        void router.navigate(recoverPath);
+      }
path: src/hooks/useRedirectMaintenance.ts
user: login: WonJuneKim
body: 추가된 부분 확인했습니다~
created_at: 2025-04-19T08:02:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,60 @@
+import { useEffect, useRef } from 'react';
+
+import { PATH } from '@/constants/path';
+import router from '@/router';
+import { PathValues } from '@/types/ui/path';
+
+const TIME_THRESHOLD = 10 * 60 * 1000;
+
+const useRedirectMaintenance = (startHours: number, endHours: number, redirectPath: PathValues) => {
+  const timeoutRef = useRef<number | null>(null);
+
+  useEffect(() => {
+    const checkAndRedirect = () => {
+      const now = new Date();
+      const nextTime = new Date(now);
+      const hours = now.getHours();
+      const isInMaintenance = hours >= startHours && hours < endHours;
+      const isOnMaintenancePage = location.pathname === redirectPath;
+
+      if (!isOnMaintenancePage && isInMaintenance) {
+        const currentPath = location.pathname;
+
+        sessionStorage.setItem('recoverPath', currentPath);
+        void router.navigate(redirectPath);
+      } else if (!isInMaintenance && isOnMaintenancePage) {
+        const recoverPath = sessionStorage.getItem('recoverPath') ?? PATH.main;
+
+        sessionStorage.removeItem('recoverPath');
+        void router.navigate(recoverPath);
+      }
+
+      if (hours < startHours) {
+        nextTime.setHours(startHours, 0, 0, 0);
+      } else if (hours >= endHours) {
+        nextTime.setDate(nextTime.getDate() + 1);
+        nextTime.setHours(startHours, 0, 0, 0);
+      } else {
+        nextTime.setHours(endHours, 0, 0, 0);
+      }
+
+      const delay = nextTime.getTime() - now.getTime();
+
+      const timerTime = TIME_THRESHOLD < delay ? delay - TIME_THRESHOLD : delay;
+
+      if (timeoutRef.current !== null) clearTimeout(timeoutRef.current);
+
+      timeoutRef.current = setTimeout(() => {
+        void checkAndRedirect();
+      }, timerTime);
+    };
+
+    void checkAndRedirect();
path: src/hooks/useRedirectMaintenance.ts
user: login: WonJuneKim
body: 절반의 시간에 대해서 반복적으로 호출되는 부분 변경 점 확인했어요. 이 후 서버 분들과 어떤 응답값으로 처리해줄 건지 논의되면 되겠네요 😄 
created_at: 2025-04-19T08:03:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -14,7 +17,7 @@ function Maintenance() {
           </div>
         </div>
       </PagesContainer>
-      <Footer />
+      {isMobile ? <MobileFooter /> : <Footer />}
path: src/pages/Maintenance.tsx
user: login: WonJuneKim
body: 사실 그리고 이 부분은 이전 코멘트에서 Footer 별도 컴포넌트 화가 아니라 Maintance.tsx 에 대한 추가 라우팅 처리에 대한 코멘트 였습니다! 

503 과 같은 응답을 내려줬을 때 Error 에 대응되는 index.html 또는 error.html 대신 해당 라우트로 연결하는 구조를 생각했었거든요. 일단 이 부분은 서버와 논의가 아직인 상황이라 논의가 완료되면 추가 이슈로 진행해도 좋아보이네요. ㅎㅎ
created_at: 2025-04-19T08:05:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -14,7 +17,7 @@ function Maintenance() {
           </div>
         </div>
       </PagesContainer>
-      <Footer />
+      {isMobile ? <MobileFooter /> : <Footer />}
path: src/pages/Maintenance.tsx
user: login: rkdcodus
body: 아 Maintenance 컴포넌트를 라우팅 하는걸 말씀하신건가요?? /maintenance로 이동하도록 되어있긴 합니다!
created_at: 2025-04-19T08:46:02Z
author_association: CONTRIBUTOR

PR comments:
user: login: vercel[bot]
created_at: 2025-04-18T15:50:35Z
body: [vc]: #qqil8rVXmZbvsHrPjUl6NHoluEvhhm7cHd8ixChJLdM=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJqZWN0LW9mZmljaWFsLXdlYi1zaXRlLWNsaWVudCIsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiJqZWN0LW9mZmljaWFsLXdlYi1zaXRlLWNsaWVudC1naXQtZmVhdC0xOS00MTFhM2EtamVjdHMtcHJvamVjdHMudmVyY2VsLmFwcCJ9LCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vamVjdHMtcHJvamVjdHMvamVjdC1vZmZpY2lhbC13ZWItc2l0ZS1jbGllbnQvQ0Izd2FUeDFDa1o4QWdjenZxeGljREZ4S3Q0OSIsInByZXZpZXdVcmwiOiJqZWN0LW9mZmljaWFsLXdlYi1zaXRlLWNsaWVudC1naXQtZmVhdC0xOS00MTFhM2EtamVjdHMtcHJvamVjdHMudmVyY2VsLmFwcCIsIm5leHRDb21taXRTdGF0dXMiOiJERVBMT1lFRCIsInJvb3REaXJlY3RvcnkiOm51bGx9XX0=
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **ject-official-web-site-client** | ✅ Ready ([Inspect](https://vercel.com/jects-projects/ject-official-web-site-client/CB3waTx1CkZ8AgczvqxicDFxKt49)) | [Visit Preview](https://ject-official-web-site-client-git-feat-19-411a3a-jects-projects.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/ject-official-web-site-client-git-feat-19-411a3a-jects-projects.vercel.app?via=pr-comment-feedback-link) | Apr 19, 2025 7:10am |


author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-18T15:52:21Z
body: # [Qodana](https://qodana.cloud/projects/8E44j/reports/YY7jw) for JS

**It seems all right 👌**

No new problems were found according to the checks applied

💡 Qodana analysis was run in the pull request mode: only the changed files were checked
☁️ [View the detailed Qodana report](https://qodana.cloud/projects/8E44j/reports/YY7jw)
<details>
<summary>Detected 158 dependencies</summary>

# Third-party software list

This page lists the third-party software dependencies used in project

| Dependency                                                                                                       | Version      | Licenses                                                         |
|------------------------------------------------------------------------------------------------------------------|--------------|------------------------------------------------------------------|
| [@ampproject/remapping](https://www.npmjs.com/package/@ampproject/remapping)                                     | 2.3.0        | [Apache-2.0](http://www.apache.org/licenses/)                    |
| [@babel/code-frame](https://www.npmjs.com/package/@babel/code-frame)                                             | 7.26.2       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@babel/compat-data](https://www.npmjs.com/package/@babel/compat-data)                                           | 7.26.5       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@babel/core](https://www.npmjs.com/package/@babel/core)                                                         | 7.26.0       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@babel/generator](https://www.npmjs.com/package/@babel/generator)                                               | 7.26.5       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@babel/helper-compilation-targets](https://www.npmjs.com/package/@babel/helper-compilation-targets)             | 7.26.5       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@babel/helper-module-imports](https://www.npmjs.com/package/@babel/helper-module-imports)                       | 7.25.9       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@babel/helper-module-transforms](https://www.npmjs.com/package/@babel/helper-module-transforms)                 | 7.26.0       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@babel/helper-validator-identifier](https://www.npmjs.com/package/@babel/helper-validator-identifier)           | 7.25.9       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@babel/helper-validator-option](https://www.npmjs.com/package/@babel/helper-validator-option)                   | 7.25.9       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@babel/helpers](https://www.npmjs.com/package/@babel/helpers)                                                   | 7.26.0       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@babel/parser](https://www.npmjs.com/package/@babel/parser)                                                     | 7.26.5       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@babel/template](https://www.npmjs.com/package/@babel/template)                                                 | 7.25.9       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@babel/traverse](https://www.npmjs.com/package/@babel/traverse)                                                 | 7.26.5       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@babel/types](https://www.npmjs.com/package/@babel/types)                                                       | 7.26.5       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@esbuild/linux-x64](https://www.npmjs.com/package/@esbuild/linux-x64)                                           | 0.24.2       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@hookform/resolvers](https://www.npmjs.com/package/@hookform/resolvers)                                         | 4.1.3        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@jridgewell/gen-mapping](https://www.npmjs.com/package/@jridgewell/gen-mapping)                                 | 0.3.8        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@jridgewell/set-array](https://www.npmjs.com/package/@jridgewell/set-array)                                     | 1.2.1        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@jridgewell/sourcemap-codec](https://www.npmjs.com/package/@jridgewell/sourcemap-codec)                         | 1.5.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@jridgewell/trace-mapping](https://www.npmjs.com/package/@jridgewell/trace-mapping)                             | 0.3.25       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@rollup/rollup-linux-x64-gnu](https://www.npmjs.com/package/@rollup/rollup-linux-x64-gnu)                       | 4.31.0       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@rollup/rollup-linux-x64-musl](https://www.npmjs.com/package/@rollup/rollup-linux-x64-musl)                     | 4.31.0       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@sentry-internal/browser-utils](https://www.npmjs.com/package/@sentry-internal/browser-utils)                   | 9.12.0       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@sentry-internal/feedback](https://www.npmjs.com/package/@sentry-internal/feedback)                             | 9.12.0       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@sentry-internal/replay-canvas](https://www.npmjs.com/package/@sentry-internal/replay-canvas)                   | 9.12.0       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@sentry-internal/replay](https://www.npmjs.com/package/@sentry-internal/replay)                                 | 9.12.0       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@sentry/babel-plugin-component-annotate](https://www.npmjs.com/package/@sentry/babel-plugin-component-annotate) | 3.3.1        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@sentry/browser](https://www.npmjs.com/package/@sentry/browser)                                                 | 9.12.0       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@sentry/bundler-plugin-core](https://www.npmjs.com/package/@sentry/bundler-plugin-core)                         | 3.3.1        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@sentry/cli-linux-x64](https://www.npmjs.com/package/@sentry/cli-linux-x64)                                     | 2.42.2       | [BSD-3-Clause](http://www.opensource.org/licenses/BSD-3-Clause)  |
| [@sentry/cli](https://www.npmjs.com/package/@sentry/cli)                                                         | 2.42.2       | [BSD-3-Clause](http://www.opensource.org/licenses/BSD-3-Clause)  |
| [@sentry/core](https://www.npmjs.com/package/@sentry/core)                                                       | 9.12.0       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@sentry/react](https://www.npmjs.com/package/@sentry/react)                                                     | 9.12.0       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@sentry/vite-plugin](https://www.npmjs.com/package/@sentry/vite-plugin)                                         | 3.3.1        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@standard-schema/utils](https://www.npmjs.com/package/@standard-schema/utils)                                   | 0.3.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@tailwindcss/node](https://www.npmjs.com/package/@tailwindcss/node)                                             | 4.0.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@tailwindcss/oxide-linux-x64-gnu](https://www.npmjs.com/package/@tailwindcss/oxide-linux-x64-gnu)               | 4.0.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@tailwindcss/oxide-linux-x64-musl](https://www.npmjs.com/package/@tailwindcss/oxide-linux-x64-musl)             | 4.0.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@tailwindcss/oxide](https://www.npmjs.com/package/@tailwindcss/oxide)                                           | 4.0.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@tailwindcss/vite](https://www.npmjs.com/package/@tailwindcss/vite)                                             | 4.0.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@tanstack/query-core](https://www.npmjs.com/package/@tanstack/query-core)                                       | 5.66.0       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@tanstack/react-query](https://www.npmjs.com/package/@tanstack/react-query)                                     | 5.66.0       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@types/cookie](https://www.npmjs.com/package/@types/cookie)                                                     | 0.6.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@types/estree](https://www.npmjs.com/package/@types/estree)                                                     | 1.0.6        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@types/node](https://www.npmjs.com/package/@types/node)                                                         | 22.13.1      | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@types/prop-types](https://www.npmjs.com/package/@types/prop-types)                                             | 15.7.14      | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@types/react](https://www.npmjs.com/package/@types/react)                                                       | 18.3.18      | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [acorn](https://www.npmjs.com/package/acorn)                                                                     | 8.14.0       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [agent-base](https://www.npmjs.com/package/agent-base)                                                           | 6.0.2        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [asynckit](https://www.npmjs.com/package/asynckit)                                                               | 0.4.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [axios](https://www.npmjs.com/package/axios)                                                                     | 1.8.1        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [balanced-match](https://www.npmjs.com/package/balanced-match)                                                   | 1.0.2        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [brace-expansion](https://www.npmjs.com/package/brace-expansion)                                                 | 2.0.1        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [browserslist](https://www.npmjs.com/package/browserslist)                                                       | 4.24.4       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [call-bind-apply-helpers](https://www.npmjs.com/package/call-bind-apply-helpers)                                 | 1.0.1        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [caniuse-lite](https://www.npmjs.com/package/caniuse-lite)                                                       | 1.0.30001695 | [CC-BY-4.0](http://creativecommons.org/licenses/by/4.0/)         |
| [chokidar](https://www.npmjs.com/package/chokidar)                                                               | 3.6.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [clsx](https://www.npmjs.com/package/clsx)                                                                       | 2.1.1        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [combined-stream](https://www.npmjs.com/package/combined-stream)                                                 | 1.0.8        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [convert-source-map](https://www.npmjs.com/package/convert-source-map)                                           | 2.0.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [cookie](https://www.npmjs.com/package/cookie)                                                                   | 1.0.2        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [crypto-js](https://www.npmjs.com/package/crypto-js)                                                             | 4.2.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [csstype](https://www.npmjs.com/package/csstype)                                                                 | 3.1.3        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [debug](https://www.npmjs.com/package/debug)                                                                     | 4.4.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [delayed-stream](https://www.npmjs.com/package/delayed-stream)                                                   | 1.0.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [detect-libc](https://www.npmjs.com/package/detect-libc)                                                         | 1.0.3        | [Apache-2.0](http://www.apache.org/licenses/)                    |
| [dotenv](https://www.npmjs.com/package/dotenv)                                                                   | 16.5.0       | [BSD-2-Clause](http://www.opensource.org/licenses/BSD-2-Clause)  |
| [dunder-proto](https://www.npmjs.com/package/dunder-proto)                                                       | 1.0.1        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [electron-to-chromium](https://www.npmjs.com/package/electron-to-chromium)                                       | 1.5.86       | [ISC](https://www.isc.org/licenses/)                             |
| [enhanced-resolve](https://www.npmjs.com/package/enhanced-resolve)                                               | 5.18.0       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [es-define-property](https://www.npmjs.com/package/es-define-property)                                           | 1.0.1        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [es-errors](https://www.npmjs.com/package/es-errors)                                                             | 1.3.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [es-object-atoms](https://www.npmjs.com/package/es-object-atoms)                                                 | 1.1.1        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [es-set-tostringtag](https://www.npmjs.com/package/es-set-tostringtag)                                           | 2.1.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [esbuild](https://www.npmjs.com/package/esbuild)                                                                 | 0.24.2       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [escalade](https://www.npmjs.com/package/escalade)                                                               | 3.2.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [find-up](https://www.npmjs.com/package/find-up)                                                                 | 5.0.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [follow-redirects](https://www.npmjs.com/package/follow-redirects)                                               | 1.15.9       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [form-data](https://www.npmjs.com/package/form-data)                                                             | 4.0.2        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [fs.realpath](https://www.npmjs.com/package/fs.realpath)                                                         | 1.0.0        | [ISC](https://www.isc.org/licenses/)                             |
| [function-bind](https://www.npmjs.com/package/function-bind)                                                     | 1.1.2        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [gensync](https://www.npmjs.com/package/gensync)                                                                 | 1.0.0-beta.2 | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [get-intrinsic](https://www.npmjs.com/package/get-intrinsic)                                                     | 1.2.7        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [get-proto](https://www.npmjs.com/package/get-proto)                                                             | 1.0.1        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [glob](https://www.npmjs.com/package/glob)                                                                       | 9.3.5        | [ISC](https://www.isc.org/licenses/)                             |
| [gopd](https://www.npmjs.com/package/gopd)                                                                       | 1.2.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [graceful-fs](https://www.npmjs.com/package/graceful-fs)                                                         | 4.2.11       | [ISC](https://www.isc.org/licenses/)                             |
| [gsap](https://www.npmjs.com/package/gsap)                                                                       | 3.12.7       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [has-symbols](https://www.npmjs.com/package/has-symbols)                                                         | 1.1.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [has-tostringtag](https://www.npmjs.com/package/has-tostringtag)                                                 | 1.0.2        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [hasown](https://www.npmjs.com/package/hasown)                                                                   | 2.0.2        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [hoist-non-react-statics](https://www.npmjs.com/package/hoist-non-react-statics)                                 | 3.3.2        | [BSD-3-Clause](http://www.opensource.org/licenses/BSD-3-Clause)  |
| [https-proxy-agent](https://www.npmjs.com/package/https-proxy-agent)                                             | 5.0.1        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [isexe](https://www.npmjs.com/package/isexe)                                                                     | 2.0.0        | [ISC](https://www.isc.org/licenses/)                             |
| [jiti](https://www.npmjs.com/package/jiti)                                                                       | 2.4.2        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [js-tokens](https://www.npmjs.com/package/js-tokens)                                                             | 4.0.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [jsesc](https://www.npmjs.com/package/jsesc)                                                                     | 3.1.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [json5](https://www.npmjs.com/package/json5)                                                                     | 2.2.3        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [lightningcss-linux-x64-gnu](https://www.npmjs.com/package/lightningcss-linux-x64-gnu)                           | 1.29.1       | [MPL-2.0](http://mpl.mozilla.org/2012/01/03/announcing-mpl-2-0/) |
| [lightningcss-linux-x64-musl](https://www.npmjs.com/package/lightningcss-linux-x64-musl)                         | 1.29.1       | [MPL-2.0](http://mpl.mozilla.org/2012/01/03/announcing-mpl-2-0/) |
| [lightningcss](https://www.npmjs.com/package/lightningcss)                                                       | 1.29.1       | [MPL-2.0](http://mpl.mozilla.org/2012/01/03/announcing-mpl-2-0/) |
| [locate-path](https://www.npmjs.com/package/locate-path)                                                         | 6.0.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [loose-envify](https://www.npmjs.com/package/loose-envify)                                                       | 1.4.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [lottie-react](https://www.npmjs.com/package/lottie-react)                                                       | 2.4.1        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [lottie-web](https://www.npmjs.com/package/lottie-web)                                                           | 5.12.2       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [lru-cache](https://www.npmjs.com/package/lru-cache)                                                             | 10.4.3       | [ISC](https://www.isc.org/licenses/)                             |
| [lru-cache](https://www.npmjs.com/package/lru-cache)                                                             | 5.1.1        | [ISC](https://www.isc.org/licenses/)                             |
| [magic-string](https://www.npmjs.com/package/magic-string)                                                       | 0.30.8       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [math-intrinsics](https://www.npmjs.com/package/math-intrinsics)                                                 | 1.1.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [mime-db](https://www.npmjs.com/package/mime-db)                                                                 | 1.52.0       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [mime-types](https://www.npmjs.com/package/mime-types)                                                           | 2.1.35       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [minimatch](https://www.npmjs.com/package/minimatch)                                                             | 8.0.4        | [ISC](https://www.isc.org/licenses/)                             |
| [minipass](https://www.npmjs.com/package/minipass)                                                               | 4.2.8        | [ISC](https://www.isc.org/licenses/)                             |
| [minipass](https://www.npmjs.com/package/minipass)                                                               | 7.1.2        | [ISC](https://www.isc.org/licenses/)                             |
| [ms](https://www.npmjs.com/package/ms)                                                                           | 2.1.3        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [nanoid](https://www.npmjs.com/package/nanoid)                                                                   | 3.3.8        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [node-fetch](https://www.npmjs.com/package/node-fetch)                                                           | 2.7.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [node-releases](https://www.npmjs.com/package/node-releases)                                                     | 2.0.19       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [p-limit](https://www.npmjs.com/package/p-limit)                                                                 | 3.1.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [p-locate](https://www.npmjs.com/package/p-locate)                                                               | 5.0.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [path-exists](https://www.npmjs.com/package/path-exists)                                                         | 4.0.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [path-scurry](https://www.npmjs.com/package/path-scurry)                                                         | 1.11.1       | [BlueOak-1.0.0](https://blueoakcouncil.org/license/1.0.0)        |
| [picocolors](https://www.npmjs.com/package/picocolors)                                                           | 1.1.1        | [ISC](https://www.isc.org/licenses/)                             |
| [postcss](https://www.npmjs.com/package/postcss)                                                                 | 8.5.1        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [progress](https://www.npmjs.com/package/progress)                                                               | 2.0.3        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [proxy-from-env](https://www.npmjs.com/package/proxy-from-env)                                                   | 1.1.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [react-device-detect](https://www.npmjs.com/package/react-device-detect)                                         | 2.2.3        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [react-dom](https://www.npmjs.com/package/react-dom)                                                             | 18.3.1       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [react-hook-form](https://www.npmjs.com/package/react-hook-form)                                                 | 7.54.2       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [react-is](https://www.npmjs.com/package/react-is)                                                               | 16.13.1      | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [react-router-dom](https://www.npmjs.com/package/react-router-dom)                                               | 7.1.5        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [react-router](https://www.npmjs.com/package/react-router)                                                       | 7.1.5        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [react](https://www.npmjs.com/package/react)                                                                     | 18.3.1       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [rollup](https://www.npmjs.com/package/rollup)                                                                   | 4.31.0       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [scheduler](https://www.npmjs.com/package/scheduler)                                                             | 0.23.2       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [semver](https://www.npmjs.com/package/semver)                                                                   | 6.3.1        | [ISC](https://www.isc.org/licenses/)                             |
| [set-cookie-parser](https://www.npmjs.com/package/set-cookie-parser)                                             | 2.7.1        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [source-map-js](https://www.npmjs.com/package/source-map-js)                                                     | 1.2.1        | [BSD-3-Clause](http://www.opensource.org/licenses/BSD-3-Clause)  |
| [tailwindcss](https://www.npmjs.com/package/tailwindcss)                                                         | 4.0.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [tapable](https://www.npmjs.com/package/tapable)                                                                 | 2.2.1        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [tr46](https://www.npmjs.com/package/tr46)                                                                       | 0.0.3        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [turbo-stream](https://www.npmjs.com/package/turbo-stream)                                                       | 2.4.0        | [ISC](https://www.isc.org/licenses/)                             |
| [ua-parser-js](https://www.npmjs.com/package/ua-parser-js)                                                       | 1.0.40       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [undici-types](https://www.npmjs.com/package/undici-types)                                                       | 6.20.0       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [unplugin](https://www.npmjs.com/package/unplugin)                                                               | 1.0.1        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [update-browserslist-db](https://www.npmjs.com/package/update-browserslist-db)                                   | 1.1.2        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [vite](https://www.npmjs.com/package/vite)                                                                       | 6.0.11       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [webidl-conversions](https://www.npmjs.com/package/webidl-conversions)                                           | 3.0.1        | [BSD-2-Clause](http://www.opensource.org/licenses/BSD-2-Clause)  |
| [webpack-sources](https://www.npmjs.com/package/webpack-sources)                                                 | 3.2.3        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [webpack-virtual-modules](https://www.npmjs.com/package/webpack-virtual-modules)                                 | 0.5.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [whatwg-url](https://www.npmjs.com/package/whatwg-url)                                                           | 5.0.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [which](https://www.npmjs.com/package/which)                                                                     | 2.0.2        | [ISC](https://www.isc.org/licenses/)                             |
| [yallist](https://www.npmjs.com/package/yallist)                                                                 | 3.1.1        | [ISC](https://www.isc.org/licenses/)                             |
| [yaml](https://www.npmjs.com/package/yaml)                                                                       | 2.7.0        | [ISC](https://www.isc.org/licenses/)                             |
| [yocto-queue](https://www.npmjs.com/package/yocto-queue)                                                         | 0.1.0        | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [zod](https://www.npmjs.com/package/zod)                                                                         | 3.24.2       | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [zustand](https://www.npmjs.com/package/zustand)                                                                 | 5.0.3        | [MIT](http://opensource.org/licenses/mit-license.php)            |
</details>
<details>
<summary>Contact Qodana team</summary>

Contact us at [qodana-support@jetbrains.com](mailto:qodana-support@jetbrains.com)
  - Or via our issue tracker: https://jb.gg/qodana-issue
  - Or share your feedback: https://jb.gg/qodana-discussions
</details>
<!-- JetBrains/qodana-action@v2024.3.4 : Qodana for JS -->
author_association: CONTRIBUTOR