diff --git a/apps/client/.eslintignore b/apps/client/.eslintignore new file mode 100644 index 00000000..cdc980d0 --- /dev/null +++ b/apps/client/.eslintignore @@ -0,0 +1,9 @@ +# shadcn/ui 컴포넌트 폴더 무시 +src/components/ui/* + +# node_modules는 기본적으로 무시되지만, 명시적으로 추가할 수도 있습니다 +node_modules/ + +# 다른 무시하고 싶은 파일/폴더들 +dist/ +build/ \ No newline at end of file diff --git a/apps/client/.eslintrc b/apps/client/.eslintrc index c62c6898..d1d35433 100644 --- a/apps/client/.eslintrc +++ b/apps/client/.eslintrc @@ -1,35 +1,45 @@ { "parser": "@typescript-eslint/parser", + "parserOptions": { - "project": ["./apps/client/tsconfig.json"], + "project": ["./tsconfig.json"], "ecmaVersion": 12, "sourceType": "module", "ecmaFeatures": { "jsx": true } }, + "env": { "browser": true, "es2021": true }, - "extends": [ - "eslint:recommended", - "plugin:react/recommended", - "plugin:react-hooks/recommended", - "plugin:@typescript-eslint/recommended", - "prettier" - ], + + "extends": ["airbnb", "airbnb/hooks", "plugin:@typescript-eslint/recommended", "prettier"], + "settings": { "react": { "version": "detect" } }, + "plugins": ["prettier"], + "rules": { + // React 관련 규칙 "react/react-in-jsx-scope": "off", "react/no-unescaped-entities": "off", "react/prop-types": "off", - "react-hooks/exhaustive-deps": "warn", + "react/jsx-filename-extension": [ + "warn", + { + "extensions": [".js", ".jsx", ".ts", ".tsx"] + } + ], + "react/require-default-props": "off", + "react/jsx-props-no-spreading": "off", + + // TypeScript 관련 규칙 "@typescript-eslint/no-explicit-any": "warn", "@typescript-eslint/no-unused-vars": [ "error", @@ -38,6 +48,22 @@ "varsIgnorePattern": "^_", // _ 로 시작하는 변수는 무시 "ignoreRestSiblings": true } + ], + + // Import/Export 관련 규칙 + "import/no-unresolved": "off", + "import/extensions": ["off"], + "import/prefer-default-export": "off", + + // 접근성 관련 규칙 + "jsx-a11y/media-has-caption": "off", + + // 기타 규칙 + "no-param-reassign": [ + "warn", + { + "props": false + } ] } } diff --git a/apps/client/package.json b/apps/client/package.json index 731560f5..8dc66bf8 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -33,10 +33,16 @@ "@types/node": "^20.3.1", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", + "@typescript-eslint/eslint-plugin": "^7.18.0", + "@typescript-eslint/parser": "^7.18.0", "@vitejs/plugin-react-swc": "^3.5.0", "autoprefixer": "^10.4.20", "eslint": "*", + "eslint-config-airbnb": "^19.0.4", + "eslint-config-airbnb-typescript": "^18.0.0", "eslint-config-prettier": "*", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-prettier": "*", "eslint-plugin-react": "^7.37.2", "eslint-plugin-react-hooks": "^5.0.0", @@ -44,6 +50,7 @@ "postcss": "^8.4.47", "prettier": "*", "tailwindcss": "^3.4.14", + "typescript": "*", "vite": "^5.4.10" } } diff --git a/apps/client/src/Router.tsx b/apps/client/src/Router.tsx index f621b769..56ce88f5 100644 --- a/apps/client/src/Router.tsx +++ b/apps/client/src/Router.tsx @@ -1,11 +1,11 @@ import { createBrowserRouter } from 'react-router-dom'; -import App from './App'; import Home from '@pages/Home'; import Profile from '@pages/Profile'; import Live from '@pages/Live'; import Broadcast from '@pages/Broadcast'; import Auth from '@pages/Auth'; import Record from '@pages/Record'; +import App from './App'; import ProtectedRoute from './ProtectedRoute'; const routerOptions = { diff --git a/apps/client/src/components/ChatContainer/ChatEndModal.tsx b/apps/client/src/components/ChatContainer/ChatEndModal.tsx index ade764ff..327fda81 100644 --- a/apps/client/src/components/ChatContainer/ChatEndModal.tsx +++ b/apps/client/src/components/ChatContainer/ChatEndModal.tsx @@ -1,9 +1,9 @@ import Modal from '@components/Modal'; import { useNavigate } from 'react-router-dom'; -interface ChatEndModalProps { +type ChatEndModalProps = { setShowModal: (b: boolean) => void; -} +}; function ChatEndModal({ setShowModal }: ChatEndModalProps) { const navigate = useNavigate(); @@ -17,7 +17,7 @@ function ChatEndModal({ setShowModal }: ChatEndModalProps) {

방송이 종료되었습니다.

-
diff --git a/apps/client/src/components/ChatContainer/index.tsx b/apps/client/src/components/ChatContainer/index.tsx index c636e2b1..39202d90 100644 --- a/apps/client/src/components/ChatContainer/index.tsx +++ b/apps/client/src/components/ChatContainer/index.tsx @@ -1,25 +1,26 @@ import { useState, useEffect, useRef, useContext } from 'react'; import { Card, CardHeader, CardTitle, CardContent, CardFooter } from '@components/ui/card'; import { Input } from '@components/ui/input'; -import { SmileIcon } from '@/components/Icons'; import { useSocket } from '@hooks/useSocket'; import ErrorCharacter from '@components/ErrorCharacter'; -import { AuthContext } from '@/contexts/AuthContext'; import { createPortal } from 'react-dom'; +import { AuthContext } from '@/contexts/AuthContext'; +import { SmileIcon } from '@/components/Icons'; import ChatEndModal from './ChatEndModal'; -interface Chat { +type Chat = { + chatId?: string; camperId: string; name: string; message: string; -} +}; const chatServerUrl = import.meta.env.VITE_CHAT_SERVER_URL; -const ChatContainer = ({ roomId, isProducer }: { roomId: string; isProducer: boolean }) => { +function ChatContainer({ roomId, isProducer }: { roomId: string; isProducer: boolean }) { const { isLoggedIn } = useContext(AuthContext); // 채팅 방 입장 - const [isJoinedRoom, setIsJoinedRoom] = useState(false); + const isJoinedRoomRef = useRef(false); // 채팅 전송 const { socket, isConnected, socketError } = useSocket(chatServerUrl); const [chattings, setChattings] = useState([]); @@ -35,57 +36,65 @@ const ChatContainer = ({ roomId, isProducer }: { roomId: string; isProducer: boo // 채팅 종료 const [showModal, setShowModal] = useState(false); - const setUpRoom = async (isProducer: boolean) => { - if (isProducer) { - socket?.emit('createRoom', { roomId: roomId }); - } else { - // 채팅방 입장 - socket?.emit('joinRoom', { roomId: roomId }, () => {}); - // 채팅방 종료 이벤트 - socket?.on('chatClosed', () => { - setShowModal(true); - }); - } - setIsJoinedRoom(true); - }; - const handleInputChange = (e: React.ChangeEvent) => { setInputValue(e.target.value); }; - const hanldeKeyDownEnter = (e: React.KeyboardEvent) => { - if (isComposing) return; - if (e.key === 'Enter') { - handleSendChat(); - } - }; - const handleSendChat = () => { if (inputValue.trim() && socket) { - socket.emit('chat', { roomId: roomId, message: inputValue }); + socket.emit('chat', { roomId, message: inputValue }); } setInputValue(''); }; - const handleReceiveChat = (response: Chat) => { - const { camperId, name, message } = response; - setChattings(prev => [...prev, { camperId, name, message }]); + const hanldeKeyDownEnter = (e: React.KeyboardEvent) => { + if (isComposing) return; + if (e.key === 'Enter') { + handleSendChat(); + } }; const handleClickEmoticon = () => { alert('구현 예정'); }; + // 채팅방 입장 + useEffect(() => { + if (!isConnected || !socket || !roomId || isJoinedRoomRef.current) return; + const setUpRoom = async () => { + if (isProducer) { + socket?.emit('createRoom', { roomId }); + } else { + // 채팅방 입장 + socket?.emit('joinRoom', { roomId }, () => {}); + // 채팅방 종료 이벤트 + } + isJoinedRoomRef.current = true; + }; + setUpRoom(); + }, [isConnected, socket, roomId, isProducer]); + + // 채팅 이벤트 등록/해제 useEffect(() => { - if (!isConnected || !socket || !roomId || isJoinedRoom) return; - setUpRoom(isProducer); + if (!socket || !isConnected) return () => {}; + + const handleReceiveChat = (response: Chat) => { + const { camperId, name, message } = response; + setChattings(prev => [...prev, { chatId: `${Date.now()}-${camperId}`, camperId, name, message }]); + }; + + const handleChatClosed = () => { + setShowModal(true); + }; socket?.on('chat', handleReceiveChat); + socket?.on('chatClosed', handleChatClosed); return () => { socket?.off('chat', handleReceiveChat); + socket?.off('chatClosed'); }; - }, [isConnected, roomId, socket]); + }, [socket, isConnected]); // 자동 스크롤 useEffect(() => { @@ -106,8 +115,8 @@ const ChatContainer = ({ roomId, isProducer }: { roomId: string; isProducer: boo <>
- {chattings.map((chat, index) => ( -
+ {chattings.map((chat: Chat) => ( +
{chat.camperId} {chat.message}
@@ -128,6 +137,7 @@ const ChatContainer = ({ roomId, isProducer }: { roomId: string; isProducer: boo disabled={!isLoggedIn} />
-
diff --git a/apps/client/src/components/Header/index.tsx b/apps/client/src/components/Header/index.tsx index a6b5e3f7..ef262751 100644 --- a/apps/client/src/components/Header/index.tsx +++ b/apps/client/src/components/Header/index.tsx @@ -2,9 +2,9 @@ import { useContext, useEffect, useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { Character, Logo } from '@components/Icons'; import { Avatar, AvatarFallback, AvatarImage } from '@components/ui/avatar'; +import { cn } from '@utils/utils'; import { AuthContext } from '@/contexts/AuthContext'; import axiosInstance from '@/services/axios'; -import { cn } from '@utils/utils'; import LogInButton from './LogInButton'; import { Button } from '../ui/button'; import { useAuth } from '@/hooks/useAuth'; @@ -72,10 +72,10 @@ function Header() { return (
-
+
+
{isLoggedIn ? (
diff --git a/apps/client/src/components/IconButton/index.tsx b/apps/client/src/components/IconButton/index.tsx index de50e95b..f37a498b 100644 --- a/apps/client/src/components/IconButton/index.tsx +++ b/apps/client/src/components/IconButton/index.tsx @@ -1,15 +1,16 @@ -interface IconButtonProps { +type IconButtonProps = { children: React.ReactNode; title?: string; ariaLabel?: string; onClick?: () => void; disabled?: boolean; className?: string; -} +}; function IconButton({ children, title, ariaLabel, onClick, disabled, className }: IconButtonProps) { return ( - -
- ) : ( -
-
{currentTitle}
- -
- )} - + +
+ ); + } + + return ( +
+
{currentTitle}
+ +
); } diff --git a/apps/client/src/pages/Broadcast/RecordButton.tsx b/apps/client/src/pages/Broadcast/RecordButton.tsx index 1504bd89..12e5a12f 100644 --- a/apps/client/src/pages/Broadcast/RecordButton.tsx +++ b/apps/client/src/pages/Broadcast/RecordButton.tsx @@ -1,18 +1,18 @@ -import Modal from '@/components/Modal'; -import { Button } from '@/components/ui/button'; import { useEffect, useState } from 'react'; import { createPortal } from 'react-dom'; import { useForm } from 'react-hook-form'; import { Socket } from 'socket.io-client'; +import { Button } from '@/components/ui/button'; +import Modal from '@/components/Modal'; -interface FormInput { +type FormInput = { title: string; -} +}; -interface RecordButtonProps { +type RecordButtonProps = { socket: Socket | null; roomId: string; -} +}; function RecordButton({ socket, roomId }: RecordButtonProps) { const [isRecording, setIsRecording] = useState(false); @@ -28,14 +28,14 @@ function RecordButton({ socket, roomId }: RecordButtonProps) { const handleStartRecording = () => { if (!socket?.connected || !roomId) return; - socket.emit('startRecord', { roomId: roomId }, (response: { success: boolean }) => { + socket.emit('startRecord', { roomId }, (response: { success: boolean }) => { if (response.success) setIsRecording(true); }); }; const handleStopRecording = (data: FormInput) => { if (!socket?.connected || !roomId) return; - socket.emit('stopRecord', { roomId: roomId, title: data.title }, (response: { success: boolean }) => { + socket.emit('stopRecord', { roomId, title: data.title }, (response: { success: boolean }) => { if (response.success) { setIsEditing(false); setIsRecording(false); diff --git a/apps/client/src/pages/Broadcast/index.tsx b/apps/client/src/pages/Broadcast/index.tsx index ce8703cf..d6cc0c8f 100644 --- a/apps/client/src/pages/Broadcast/index.tsx +++ b/apps/client/src/pages/Broadcast/index.tsx @@ -1,10 +1,11 @@ -import BroadcastTitle from './BroadcastTitle'; import ChatContainer from '@components/ChatContainer'; import ErrorCharacter from '@components/ErrorCharacter'; import { useProducer } from '@hooks/useProducer'; import { useRoom } from '@hooks/useRoom'; import { useSocket } from '@hooks/useSocket'; import { useTransport } from '@hooks/useTransport'; +import { Button } from '@components/ui/button'; +import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'; import { MicrophoneOffIcon, MicrophoneOnIcon, @@ -13,8 +14,7 @@ import { ScreenShareIcon, ScreenShareIconOff, } from '@/components/Icons'; -import { Button } from '@components/ui/button'; -import { useEffect, useLayoutEffect, useRef, useState } from 'react'; +import BroadcastTitle from './BroadcastTitle'; import useScreenShare from '@/hooks/useScreenShare'; import BroadcastPlayer from './BroadcastPlayer'; import { Tracks } from '@/types/mediasoupTypes'; @@ -69,10 +69,47 @@ function Broadcast() { } else { document.querySelector('html')?.removeAttribute('data-theme'); } - }, []); + }, [theme]); + + const stopBroadcast = useCallback( + (e?: BeforeUnloadEvent) => { + if (e) { + e.preventDefault(); + e.returnValue = ''; + } + if (socket) { + socket.emit('stopBroadcast', { roomId }); + socket.disconnect(); + mediaStream?.getTracks().forEach(track => { + track.stop(); + }); + } + transport?.close(); + }, + [socket, mediaStream, roomId, transport], + ); + + const handleCheckout = () => { + stopBroadcast(); + window.close(); + }; + + const handleBroadcastTitle = (newTitle: string) => { + setTitle(newTitle); + }; + + const playPauseAudio = () => { + if (isAudioEnabled) { + producers.get('mediaAudio')?.pause(); + if (tracksRef.current.mediaAudio) tracksRef.current.mediaAudio.enabled = false; + } else { + producers.get('mediaAudio')?.resume(); + if (tracksRef.current.mediaAudio) tracksRef.current.mediaAudio.enabled = true; + } + }; useEffect(() => { - tracksRef.current['mediaAudio'] = mediaStream?.getAudioTracks()[0]; + tracksRef.current.mediaAudio = mediaStream?.getAudioTracks()[0]; axiosInstance.get('/v1/members/info').then(response => { if (response.data.success) { @@ -84,71 +121,37 @@ function Broadcast() { return () => { window.removeEventListener('beforeunload', stopBroadcast); }; - }, []); + }, [mediaStream, stopBroadcast]); useEffect(() => { - changeTrack(); - }, [isVideoEnabled, isScreenSharing]); - - const changeTrack = async () => { - const currentProducer = producers.get('video'); - if (!currentProducer) return; - - currentProducer.pause(); + const changeTrack = async () => { + const currentProducer = producers.get('video'); + if (!currentProducer) return; - let newTrack = null; + currentProducer.pause(); - if (isVideoEnabled && isScreenSharing) { - newTrack = tracksRef.current.video || null; - } else if (isVideoEnabled && !isScreenSharing) { - newTrack = mediaStream?.getVideoTracks()[0] || null; - } else if (!isVideoEnabled && isScreenSharing) { - newTrack = screenStream?.getVideoTracks()[0] || null; - } - - if (isVideoEnabled && mediaStream) mediaStream.getVideoTracks()[0].enabled = true; - if (isScreenSharing && screenStream) screenStream.getVideoTracks()[0].enabled = true; + let newTrack = null; - await currentProducer.replaceTrack({ track: newTrack }); + if (isVideoEnabled && isScreenSharing) { + newTrack = tracksRef.current.video || null; + } else if (isVideoEnabled && !isScreenSharing) { + newTrack = mediaStream?.getVideoTracks()[0] || null; + } else if (!isVideoEnabled && isScreenSharing) { + newTrack = screenStream?.getVideoTracks()[0] || null; + } - if (newTrack) { - currentProducer.resume(); - } - }; + if (isVideoEnabled && mediaStream) mediaStream.getVideoTracks()[0].enabled = true; + if (isScreenSharing && screenStream) screenStream.getVideoTracks()[0].enabled = true; - const stopBroadcast = (e?: BeforeUnloadEvent) => { - if (e) { - e.preventDefault(); - e.returnValue = ''; - } - if (socket) { - socket.emit('stopBroadcast', { roomId }); - socket.disconnect(); - mediaStream?.getTracks().forEach(track => { - track.stop(); - }); - } - transport?.close(); - }; + await currentProducer.replaceTrack({ track: newTrack }); - const handleCheckout = () => { - stopBroadcast(); - window.close(); - }; - - const handleBroadcastTitle = (newTitle: string) => { - setTitle(newTitle); - }; + if (newTrack) { + currentProducer.resume(); + } + }; - const playPauseAudio = () => { - if (isAudioEnabled) { - producers.get('mediaAudio')?.pause(); - if (tracksRef.current.mediaAudio) tracksRef.current.mediaAudio.enabled = false; - } else { - producers.get('mediaAudio')?.resume(); - if (tracksRef.current.mediaAudio) tracksRef.current.mediaAudio.enabled = true; - } - }; + changeTrack(); + }, [isVideoEnabled, isScreenSharing, mediaStream, screenStream, producers]); if (socketError || roomError || transportError || screenShareError) { mediaStream?.getTracks().forEach((track: MediaStreamTrack) => track.stop()); @@ -182,15 +185,22 @@ function Broadcast() {
-
- + -
- + )} diff --git a/apps/client/src/pages/Home/Bookmark.tsx b/apps/client/src/pages/Home/Bookmark.tsx index a077d5a6..3e86c7f2 100644 --- a/apps/client/src/pages/Home/Bookmark.tsx +++ b/apps/client/src/pages/Home/Bookmark.tsx @@ -8,11 +8,11 @@ import axiosInstance from '@services/axios'; import { useContext, useEffect, useState } from 'react'; import { CloseIcon } from '@components/Icons'; -interface BookmarkData { +type BookmarkData = { bookmarkId: number; name: string; url: string; -} +}; function Bookmark() { const { isLoggedIn } = useContext(AuthContext); @@ -71,7 +71,7 @@ function Bookmark() { toast({ variant: 'destructive', title: '북마크 조회 실패' }); } }); - }, []); + }, [toast]); return ( <> @@ -85,12 +85,13 @@ function Bookmark() { className="h-14 w-52 bg-surface-alt text-text-strong hover:bg-surface-alt-light relative flex items-center justify-between" > {data.name} -
handleDeleteBookmark(e, data.bookmarkId)} className="flex items-center p-1 hover:text-text-strong hover:cursor-pointer" > -
+ ))} @@ -108,22 +109,28 @@ function Bookmark() {
- - +
- - +
{(errors.name || errors.url) && (

diff --git a/apps/client/src/pages/Home/FieldFilter.tsx b/apps/client/src/pages/Home/FieldFilter.tsx index 0320cd19..277d8297 100644 --- a/apps/client/src/pages/Home/FieldFilter.tsx +++ b/apps/client/src/pages/Home/FieldFilter.tsx @@ -1,12 +1,12 @@ +import { useState } from 'react'; import { Button } from '@/components/ui/button'; import { Field } from '@/types/liveTypes'; -import { useState } from 'react'; const fields: Field[] = ['WEB', 'AND', 'IOS']; -interface FieldFilterProps { +type FieldFilterProps = { onClickFilterButton: (field: Field) => void; -} +}; function FieldFilter({ onClickFilterButton }: FieldFilterProps) { const [selected, setSelected] = useState(''); diff --git a/apps/client/src/pages/Home/LiveCard.tsx b/apps/client/src/pages/Home/LiveCard.tsx index 4c2ec430..ba32d2d0 100644 --- a/apps/client/src/pages/Home/LiveCard.tsx +++ b/apps/client/src/pages/Home/LiveCard.tsx @@ -1,14 +1,14 @@ import { useNavigate } from 'react-router-dom'; -interface LiveCardProps { +type LiveCardProps = { liveId: string; title: string; userId: string; profileUrl?: string; thumbnailUrl: string; -} +}; -const LiveCard = ({ liveId, title, userId, profileUrl, thumbnailUrl }: LiveCardProps) => { +function LiveCard({ liveId, title, userId, profileUrl, thumbnailUrl }: LiveCardProps) { const navigate = useNavigate(); const handleClick = () => { @@ -16,9 +16,10 @@ const LiveCard = ({ liveId, title, userId, profileUrl, thumbnailUrl }: LiveCardP }; return ( -

{/* 썸네일 */}
@@ -32,18 +33,18 @@ const LiveCard = ({ liveId, title, userId, profileUrl, thumbnailUrl }: LiveCardP
{/* 방송 정보 */} -
+
{profileUrl && {userId}}
-

{title}

-

{userId}

+

{title}

+

{userId}

-
+ ); -}; +} export default LiveCard; diff --git a/apps/client/src/pages/Home/LiveList.tsx b/apps/client/src/pages/Home/LiveList.tsx index eeb5425c..1f7a22ce 100644 --- a/apps/client/src/pages/Home/LiveList.tsx +++ b/apps/client/src/pages/Home/LiveList.tsx @@ -1,8 +1,8 @@ +import { useCallback, useEffect, useState } from 'react'; +import axiosInstance from '@services/axios'; import FieldFilter from './FieldFilter'; import LiveCard from './LiveCard'; import { LivePreviewInfo } from '@/types/homeTypes'; -import { useEffect, useState } from 'react'; -import axiosInstance from '@services/axios'; import Search from './Search'; import { Field } from '@/types/liveTypes'; import { useIntersect } from '@/hooks/useIntersect'; @@ -14,15 +14,8 @@ function LiveList() { const [hasNext, setHasNext] = useState(true); const [cursor, setCursor] = useState(null); const [field, setField] = useState(''); - const ref = useIntersect({ - onIntersect: (entry, observer) => { - observer.unobserve(entry.target); - if (hasNext && cursor) getLiveList(); - }, - options: { threshold: 0.3 }, - }); - const getLiveList = () => { + const getLiveList = useCallback(() => { axiosInstance.get('/v1/broadcasts', { params: { field, cursor, limit: LIMIT } }).then(response => { if (response.data.success) { const { broadcasts, nextCursor } = response.data.data; @@ -31,17 +24,27 @@ function LiveList() { if (!nextCursor) setHasNext(false); } }); - }; + }, [field, cursor]); - const hanldeFilterField = (field: Field) => { - axiosInstance.get('/v1/broadcasts', { params: { field, cursor: null, limit: LIMIT } }).then(response => { - if (response.data.success) { - const { broadcasts, nextCursor } = response.data.data; - setLiveList(broadcasts); - setCursor(nextCursor); - setHasNext(nextCursor ? true : false); - } - }); + const ref = useIntersect({ + onIntersect: (entry, observer) => { + observer.unobserve(entry.target); + if (hasNext && cursor) getLiveList(); + }, + options: { threshold: 0.3 }, + }); + + const hanldeFilterField = (selectedField: Field) => { + axiosInstance + .get('/v1/broadcasts', { params: { field: selectedField, cursor: null, limit: LIMIT } }) + .then(response => { + if (response.data.success) { + const { broadcasts, nextCursor } = response.data.data; + setLiveList(broadcasts); + setCursor(nextCursor); + setHasNext(!!nextCursor); + } + }); }; const handleSearch = (keyword: string) => { @@ -56,7 +59,7 @@ function LiveList() { useEffect(() => { getLiveList(); - }, []); + }, [getLiveList]); return (
@@ -85,7 +88,7 @@ function LiveList() {
방송 정보가 없습니다.
)}
-
+
); diff --git a/apps/client/src/pages/Home/Search.tsx b/apps/client/src/pages/Home/Search.tsx index 50495377..5b65e95f 100644 --- a/apps/client/src/pages/Home/Search.tsx +++ b/apps/client/src/pages/Home/Search.tsx @@ -1,14 +1,14 @@ +import { useForm } from 'react-hook-form'; import IconButton from '@/components/IconButton'; import { SearchIcon } from '@/components/Icons'; -import { useForm } from 'react-hook-form'; -interface SearchProps { +type SearchProps = { onSearch: (keyword: string) => void; -} +}; -interface FormInput { +type FormInput = { keyword: string; -} +}; function Search({ onSearch }: SearchProps) { const { register, handleSubmit } = useForm(); diff --git a/apps/client/src/pages/Live/LiveCamperInfo.tsx b/apps/client/src/pages/Live/LiveCamperInfo.tsx index 4c4714dc..32d2b7a5 100644 --- a/apps/client/src/pages/Live/LiveCamperInfo.tsx +++ b/apps/client/src/pages/Live/LiveCamperInfo.tsx @@ -1,85 +1,84 @@ import { Avatar, AvatarFallback, AvatarImage } from '@components/ui/avatar'; import { Badge } from '@components/ui/badge'; import IconButton from '@components/IconButton'; -import { MailIcon, GithubIcon, BlogIcon, LinkedInIcon } from '@/components/Icons'; import { useAPI } from '@hooks/useAPI'; -import { LiveInfo } from '@/types/liveTypes'; import LoadingCharacter from '@components/LoadingCharacter'; import ErrorCharacter from '@components/ErrorCharacter'; +import { LiveInfo } from '@/types/liveTypes'; +import { MailIcon, GithubIcon, BlogIcon, LinkedInIcon } from '@/components/Icons'; function LiveCamperInfo({ liveId }: { liveId: string }) { const { data, isLoading, error } = useAPI({ url: `v1/broadcasts/${liveId}/info` }); + if (error || !data) { + return ( +
+ +
+ ); + } + + if (isLoading) { + return ; + } + return ( - <> - {error || !data ? ( -
- -
- ) : isLoading ? ( - - ) : ( -
-
- {/* 제목 */} -

{data.title}

+
+
+ {/* 제목 */} +

{data.title}

-
- {/* 프로필 사진 */} - - - {data.camperId} - +
+ {/* 프로필 사진 */} + + + {data.camperId} + -
-
- {data.camperId} - - {data.field ? data.field : '???'} - -
- {data.viewers}명 시청 중 -
+
+
+ {data.camperId} + + {data.field ? data.field : '???'} +
+ {data.viewers}명 시청 중
+
+
- {/* 우측 상단 아이콘들 - 2x2 그리드 */} -
- window.open(`mailto:${data.contacts.email}`)} - > - {' '} - + {/* 우측 상단 아이콘들 - 2x2 그리드 */} +
+ window.open(`mailto:${data.contacts.email}`)} + > + {' '} + - window.open(data.contacts.blog, '_blank')} - > - - + window.open(data.contacts.blog, '_blank')} + > + + - window.open(data.contacts.github, '_blank')}> - - + window.open(data.contacts.github, '_blank')}> + + - window.open(data.contacts.linkedin, '_blank')} - > - - -
-
- )} - + window.open(data.contacts.linkedin, '_blank')}> + + +
+
); } diff --git a/apps/client/src/pages/Live/LivePlayer.tsx b/apps/client/src/pages/Live/LivePlayer.tsx index 02173905..8168136b 100644 --- a/apps/client/src/pages/Live/LivePlayer.tsx +++ b/apps/client/src/pages/Live/LivePlayer.tsx @@ -1,21 +1,21 @@ import { useEffect, useRef, useState } from 'react'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@components/ui/select'; -import { PlayIcon, PauseIcon, VolumeOffIcon, VolumeOnIcon, ExpandIcon } from '@/components/Icons'; import { Socket } from 'socket.io-client'; +import { PlayIcon, PauseIcon, VolumeOffIcon, VolumeOnIcon, ExpandIcon } from '@/components/Icons'; import ErrorCharacter from '@/components/ErrorCharacter'; -interface Errors { +type Errors = { socketError: Error | null; transportError: Error | null; consumerError: Error | null; -} +}; -interface LivePlayerProps { +type LivePlayerProps = { mediaStream: MediaStream | null; socket: Socket | null; transportId: string | undefined; errors: Errors; -} +}; type VideoQuality = 'auto' | '480p' | '720p' | '1080p'; @@ -58,58 +58,62 @@ function LivePlayer({ mediaStream, socket, transportId, errors }: LivePlayerProp } }; - const handleVideoQuality = (videoQuality: VideoQuality) => { + const handleVideoQuality = (selectedVideoQuality: VideoQuality) => { if (!socket) return; - socket.emit('setVideoQuality', { transportId, quality: videoQuality }); - setVideoQuality(videoQuality); + socket.emit('setVideoQuality', { transportId, quality: selectedVideoQuality }); + setVideoQuality(selectedVideoQuality); }; const handleExpand = async () => { await videoRef.current?.requestFullscreen?.(); }; + if (socketError || transportError || consumerError) { + return ( +
+ +
+ ); + } + return ( - <> - {socketError || transportError || consumerError ? ( -
- +
+
); } diff --git a/apps/client/src/pages/Live/index.tsx b/apps/client/src/pages/Live/index.tsx index 004e3915..0fb14a88 100644 --- a/apps/client/src/pages/Live/index.tsx +++ b/apps/client/src/pages/Live/index.tsx @@ -1,12 +1,12 @@ import ChatContainer from '@components/ChatContainer'; import ErrorCharacter from '@components/ErrorCharacter'; -import LiveCamperInfo from './LiveCamperInfo'; import { useConsumer } from '@hooks/useConsumer'; import { useSocket } from '@hooks/useSocket'; import { useTransport } from '@hooks/useTransport'; -import LivePlayer from './LivePlayer'; import { useEffect } from 'react'; import { useParams } from 'react-router-dom'; +import LivePlayer from './LivePlayer'; +import LiveCamperInfo from './LiveCamperInfo'; const socketUrl = import.meta.env.VITE_MEDIASERVER_URL; @@ -30,29 +30,31 @@ export default function Live() { isConnected, }); - const handleLeaveLive = () => { - if (socket && liveId && transportInfo) { - socket.emit('leaveBroadcast', { transportId: transportInfo.transportId, roomId: liveId }); - } + useEffect(() => { + if (!socket || !liveId || !transportInfo || !transport) return undefined; + + const handleLeaveLive = () => { + if (socket && liveId && transportInfo) { + socket.emit('leaveBroadcast', { transportId: transportInfo.transportId, roomId: liveId }); + } - socket?.disconnect(); - transport?.close(); - }; + socket?.disconnect(); + transport?.close(); + }; - const preventClose = (e: BeforeUnloadEvent) => { - e.preventDefault(); - handleLeaveLive(); - e.returnValue = ''; - }; + const preventClose = (e: BeforeUnloadEvent) => { + e.preventDefault(); + handleLeaveLive(); + e.returnValue = ''; + }; - useEffect(() => { window.addEventListener('beforeunload', preventClose); return () => { handleLeaveLive(); window.removeEventListener('beforeunload', preventClose); }; - }, []); + }, [socket, liveId, transportInfo, transport]); return (
diff --git a/apps/client/src/pages/Profile/Attendance.tsx b/apps/client/src/pages/Profile/Attendance.tsx index fcb6c17a..895066fb 100644 --- a/apps/client/src/pages/Profile/Attendance.tsx +++ b/apps/client/src/pages/Profile/Attendance.tsx @@ -1,17 +1,17 @@ +import { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; import ErrorCharacter from '@/components/ErrorCharacter'; import { PlayIcon } from '@/components/Icons'; import LoadingCharacter from '@/components/LoadingCharacter'; import axiosInstance from '@/services/axios'; -import { useEffect, useState } from 'react'; -import { useNavigate } from 'react-router-dom'; -interface AttendanceData { +type AttendanceData = { attendanceId: number; date: string; startTime: string; endTime: string; isAttendance: boolean; -} +}; function Attendance() { const [attendanceList, setAttendanceList] = useState([]); @@ -31,7 +31,7 @@ function Attendance() { setError(new Error(response.data.message)); } }) - .catch(error => setError(error instanceof Error ? error : new Error(error))) + .catch(err => setError(err instanceof Error ? err : new Error(err))) .finally(() => { setIsLoading(false); }); @@ -49,45 +49,52 @@ function Attendance() { navigate(`/record/${attendanceId}`); }; + if (showLoading && isLoading) { + return ( +
+ +
+ ); + } + + if (error) { + return ( +
+ +
+ ); + } + return (
- {['학습일', '시작 시간', '종료 시간', '출석 여부'].map((data: string, idx) => ( -
+ {['학습일', '시작 시간', '종료 시간', '출석 여부'].map((data: string) => ( +
{data}
))}
- {showLoading && isLoading ? ( -
- -
- ) : error ? ( -
- -
- ) : ( -
- {attendanceList?.map(data => ( -
-
{data.date}
-
{data.startTime}
-
{data.endTime}
-
-
- - {data.isAttendance ? '출석' : '결석'} - - -
+ +
+ {attendanceList?.map(data => ( +
+
{data.date}
+
{data.startTime}
+
{data.endTime}
+
+
+ + {data.isAttendance ? '출석' : '결석'} + +
- ))} -
- )} +
+ ))} +
); diff --git a/apps/client/src/pages/Profile/EditUserInfo.tsx b/apps/client/src/pages/Profile/EditUserInfo.tsx index 25158705..d1829389 100644 --- a/apps/client/src/pages/Profile/EditUserInfo.tsx +++ b/apps/client/src/pages/Profile/EditUserInfo.tsx @@ -1,18 +1,18 @@ +import { useForm } from 'react-hook-form'; +import { useState } from 'react'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { UserData } from '.'; -import { useForm } from 'react-hook-form'; import { Field } from '@/types/liveTypes'; import { Button } from '@/components/ui/button'; -import { useState } from 'react'; import axiosInstance from '@/services/axios'; import { useToast } from '@/hooks/useToast'; -interface EditUserInfoProps { +type EditUserInfoProps = { userData: UserData | undefined; toggleEditing: () => void; -} +}; -interface FormInput { +export interface FormInput { camperId: string | undefined; name: string | undefined; field: Field | undefined; @@ -70,74 +70,96 @@ function EditUserInfo({ userData, toggleEditing }: EditUserInfoProps) { }; return ( -
+
MY - {(errors.camperId || errors.name || !selectedField) && ( -

- {errors.camperId ? errors.camperId.message : errors.name ? errors.name.message : '분야를 선택해주세요'} -

- )} {/* ID */} -
- - +
+ +

+ {errors.camperId && errors.camperId.message} +

{/* 이름 */} -
- - +
+ +

+ {errors.name && errors.name.message} +

{/* TODO: 입력 검증 */} {/* email */} -
- - +
+
{/* github */} -
- - +
+
{/* blog */} -
- - +
+
{/* linkedIn */} -
- - +
+
{/* 분야 */} -
- -
+
+ + 분야 + +
+

+ {selectedField === '' && '분야를 입력해주세요'} +

-
+
diff --git a/apps/client/src/pages/Profile/UserInfo.tsx b/apps/client/src/pages/Profile/UserInfo.tsx index c1409401..28151973 100644 --- a/apps/client/src/pages/Profile/UserInfo.tsx +++ b/apps/client/src/pages/Profile/UserInfo.tsx @@ -1,16 +1,16 @@ +import { useEffect, useState } from 'react'; import ErrorCharacter from '@/components/ErrorCharacter'; import { BlogIcon, EditIcon, GithubIcon, LinkedInIcon, MailIcon } from '@/components/Icons'; import LoadingCharacter from '@/components/LoadingCharacter'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; -import { useEffect, useState } from 'react'; import { UserData } from '.'; -interface UserInfoProps { +type UserInfoProps = { userData: UserData | undefined; isLoading: boolean; error: Error | null; toggleEditing: () => void; -} +}; function UserInfo({ userData, isLoading, error, toggleEditing }: UserInfoProps) { const [showLoading, setShowLoading] = useState(false); @@ -23,62 +23,66 @@ function UserInfo({ userData, isLoading, error, toggleEditing }: UserInfoProps) return () => clearTimeout(timer); }); + if (showLoading && isLoading) { + return ( +
+ +
+ ); + } + + if (error) { + return ( +
+ +
+ ); + } + return (
- {showLoading && isLoading ? ( -
- -
- ) : error ? ( -
- -
- ) : ( - <> -
- - - MY - -
{userData?.name}
-
-
-
-
- - {userData?.camperId ? userData.camperId : '???'} - -
- {userData?.field ? userData.field : '???'} -
- -
-
- - email - {userData?.contacts.email} -
-
- - Github - {userData?.contacts.github} -
-
- - Blog - {userData?.contacts.blog} -
-
- - LinkedIn - {userData?.contacts.linkedIn} -
+
+ + + MY + +
{userData?.name}
+
+
+
+
+ + {userData?.camperId ? userData.camperId : '???'} + +
+ {userData?.field ? userData.field : '???'}
+ +
+
+ + email + {userData?.contacts.email} +
+
+ + Github + {userData?.contacts.github}
- - )} +
+ + Blog + {userData?.contacts.blog} +
+
+ + LinkedIn + {userData?.contacts.linkedIn} +
+
+
); } diff --git a/apps/client/src/pages/Profile/index.tsx b/apps/client/src/pages/Profile/index.tsx index 40655a46..1649a1de 100644 --- a/apps/client/src/pages/Profile/index.tsx +++ b/apps/client/src/pages/Profile/index.tsx @@ -7,21 +7,21 @@ import { Field } from '@/types/liveTypes'; import ErrorCharacter from '@/components/ErrorCharacter'; import LoadingCharacter from '@/components/LoadingCharacter'; -export interface UserData { +export type Contacts = { + email: string; + github: string; + blog: string; + linkedIn: string; +}; + +export type UserData = { id: number; camperId: string; name: string; field: Field; contacts: Contacts; profileImage: string; -} - -export interface Contacts { - email: string; - github: string; - blog: string; - linkedIn: string; -} +}; export default function Profile() { const [userData, setUserData] = useState(null); @@ -40,7 +40,7 @@ export default function Profile() { setError(new Error(response.data.message)); } }) - .catch(error => setError(error instanceof Error ? error : new Error(error))) + .catch(err => setError(err instanceof Error ? err : new Error(err))) .finally(() => setIsLoading(false)); }, [isEditing]); @@ -49,7 +49,7 @@ export default function Profile() { if (!userData.camperId || !userData.name || !userData.field) { if (!isEditing) setIsEditing(true); } - }, [userData]); + }, [userData, isEditing]); useEffect(() => { const timeoutId = setTimeout(() => { @@ -63,24 +63,30 @@ export default function Profile() { setIsEditing(prev => !prev); }; + if (showLoading && isLoading) { + return ( +
+ +
+ ); + } + + if (error || !userData) { + return ( +
+ +
+ ); + } + + if (isEditing) { + return ; + } + return ( -
- {showLoading && isLoading ? ( -
- -
- ) : error || !userData ? ( -
- -
- ) : isEditing ? ( - - ) : ( - <> - - - - )} -
+ <> + + + ); } diff --git a/apps/client/src/pages/Record/RecordInfo.tsx b/apps/client/src/pages/Record/RecordInfo.tsx index 54840272..ea12f3dd 100644 --- a/apps/client/src/pages/Record/RecordInfo.tsx +++ b/apps/client/src/pages/Record/RecordInfo.tsx @@ -1,11 +1,12 @@ -interface RecordInfoProps { +type RecordInfoProps = { title: string; -} +}; function RecordInfo(props: RecordInfoProps) { + const { title } = props; return (
-

{props.title}

+

{title}

); } diff --git a/apps/client/src/pages/Record/RecordList.tsx b/apps/client/src/pages/Record/RecordList.tsx index 54302c2c..4d660be4 100644 --- a/apps/client/src/pages/Record/RecordList.tsx +++ b/apps/client/src/pages/Record/RecordList.tsx @@ -1,25 +1,27 @@ -import { PlayIcon } from '@/components/Icons'; -import { RecordData } from '.'; import { useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; +import { PlayIcon } from '@/components/Icons'; +import { RecordData } from '.'; import axiosInstance from '@/services/axios'; import ErrorCharacter from '@/components/ErrorCharacter'; -interface RecordListProps { +type RecordListProps = { onClickList: (data: RecordData) => void; -} +}; function RecordList(props: RecordListProps) { const [recordList, setRecordList] = useState([]); const { attendanceId } = useParams<{ attendanceId: string }>(); const [error, setError] = useState(''); + const { onClickList } = props; + useEffect(() => { axiosInstance.get(`/v1/records/${attendanceId}`).then(response => { if (response.data.success) setRecordList(response.data.data.records); else setError(response.data.message); }); - }, []); + }, [attendanceId]); return (
@@ -30,12 +32,13 @@ function RecordList(props: RecordListProps) { ) : (
- {recordList.map((record: RecordData, idx: number) => ( -
( +
+ ))}
diff --git a/apps/client/src/pages/Record/RecordPlayer.tsx b/apps/client/src/pages/Record/RecordPlayer.tsx index f5f066a5..b3bf3e36 100644 --- a/apps/client/src/pages/Record/RecordPlayer.tsx +++ b/apps/client/src/pages/Record/RecordPlayer.tsx @@ -1,26 +1,28 @@ -import LoadingCharacter from '@/components/LoadingCharacter'; import { useEffect, useState } from 'react'; import ReactPlayer from 'react-player'; +import LoadingCharacter from '@/components/LoadingCharacter'; -interface RecordPlayerProps { +type RecordPlayerProps = { video: string; -} +}; function RecordPlayer(props: RecordPlayerProps) { const [isSelectedVideo, setIsSelectedVideo] = useState(false); + const { video } = props; useEffect(() => { - if (props.video) { + if (video) { setIsSelectedVideo(true); } - }, [props.video]); + }, [video]); return (
{isSelectedVideo ? (
({ recordId: 0, title: '', video: '', date: '' }); return (
- <> -
- - -
-
- -
- +
+ + +
+
+ +
); } diff --git a/apps/client/src/services/axios.ts b/apps/client/src/services/axios.ts index ea1e8792..d57bdbcf 100644 --- a/apps/client/src/services/axios.ts +++ b/apps/client/src/services/axios.ts @@ -17,9 +17,7 @@ axiosInstance.interceptors.request.use( } return config; }, - error => { - return Promise.reject(error); - }, + error => Promise.reject(error), ); export default axiosInstance; diff --git a/apps/client/src/types/homeTypes.ts b/apps/client/src/types/homeTypes.ts index d8f0a309..4c8875da 100644 --- a/apps/client/src/types/homeTypes.ts +++ b/apps/client/src/types/homeTypes.ts @@ -1,15 +1,15 @@ import { Field } from './liveTypes'; -export interface LivePreviewInfo { +export type LivePreviewInfo = { broadcastId: string; broadcastTitle: string; camperId: string; profileImage: string; thumbnail: string; field: Field; -} +}; -export interface LivePreviewListInfo { +export type LivePreviewListInfo = { broadcasts: LivePreviewInfo[]; nextCursor: string | null; -} +}; diff --git a/apps/client/src/types/liveTypes.ts b/apps/client/src/types/liveTypes.ts index 4ae44e92..3f448368 100644 --- a/apps/client/src/types/liveTypes.ts +++ b/apps/client/src/types/liveTypes.ts @@ -1,17 +1,17 @@ -export interface ContactInfo { +export type ContactInfo = { github: string; linkedin: string; email: string; blog: string; -} +}; -export interface LiveInfo { +export type Field = 'WEB' | 'AND' | 'IOS' | ''; + +export type LiveInfo = { title: string; camperId: string; viewers: number; field: Field; profileImage: string; contacts: ContactInfo; -} - -export type Field = 'WEB' | 'AND' | 'IOS' | ''; +}; diff --git a/apps/client/src/types/mediasoupTypes.ts b/apps/client/src/types/mediasoupTypes.ts index 6c6cc9a3..ca946b96 100644 --- a/apps/client/src/types/mediasoupTypes.ts +++ b/apps/client/src/types/mediasoupTypes.ts @@ -1,20 +1,20 @@ import { DtlsParameters, IceCandidate, IceParameters } from 'mediasoup-client/lib/types'; -export interface TransportInfo { +export type TransportInfo = { transportId: string; isProducer: boolean; iceParameters: IceParameters; iceCandidates: IceCandidate[]; dtlsParameters: DtlsParameters; -} +}; -export interface ConnectTransportResponse { +export type ConnectTransportResponse = { connected: boolean; isProducer: boolean; -} +}; -export interface Tracks { +export type Tracks = { video: MediaStreamTrack | undefined; mediaAudio: MediaStreamTrack | undefined; screenAudio: MediaStreamTrack | undefined; -} +}; diff --git a/apps/client/src/utils/utils.ts b/apps/client/src/utils/utils.ts index 9d3dc04c..b8c7a53a 100644 --- a/apps/client/src/utils/utils.ts +++ b/apps/client/src/utils/utils.ts @@ -23,9 +23,7 @@ export const getPayloadFromJWT = () => { window .atob(base64) .split('') - .map(c => { - return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); - }) + .map(c => `%${ (`00${ c.charCodeAt(0).toString(16)}`).slice(-2)}`) .join(''), ), ); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 98038132..cf6ea675 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -348,6 +348,12 @@ importers: '@types/react-dom': specifier: ^18.3.1 version: 18.3.1 + '@typescript-eslint/eslint-plugin': + specifier: ^7.18.0 + version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/parser': + specifier: ^7.18.0 + version: 7.18.0(eslint@8.57.1)(typescript@5.6.3) '@vitejs/plugin-react-swc': specifier: ^3.5.0 version: 3.7.1(vite@5.4.10(@types/node@20.17.6)(terser@5.36.0)) @@ -357,9 +363,21 @@ importers: eslint: specifier: '*' version: 8.57.1 + eslint-config-airbnb: + specifier: ^19.0.4 + version: 19.0.4(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.1))(eslint-plugin-react-hooks@5.0.0(eslint@8.57.1))(eslint-plugin-react@7.37.2(eslint@8.57.1))(eslint@8.57.1) + eslint-config-airbnb-typescript: + specifier: ^18.0.0 + version: 18.0.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3))(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1) eslint-config-prettier: specifier: '*' version: 8.10.0(eslint@8.57.1) + eslint-plugin-import: + specifier: ^2.31.0 + version: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1) + eslint-plugin-jsx-a11y: + specifier: ^6.10.2 + version: 6.10.2(eslint@8.57.1) eslint-plugin-prettier: specifier: '*' version: 4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@2.8.8) @@ -381,6 +399,9 @@ importers: tailwindcss: specifier: ^3.4.14 version: 3.4.14(ts-node@10.9.2(@swc/core@1.8.0)(@types/node@20.17.6)(typescript@5.6.3)) + typescript: + specifier: '*' + version: 5.6.3 vite: specifier: ^5.4.10 version: 5.4.10(@types/node@20.17.6)(terser@5.36.0) @@ -2361,6 +2382,9 @@ packages: cpu: [x64] os: [win32] + '@rtsao/scc@1.1.0': + resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + '@scarf/scarf@1.4.0': resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==} @@ -2754,6 +2778,9 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/json5@0.0.29': + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/jsonwebtoken@9.0.5': resolution: {integrity: sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==} @@ -2857,6 +2884,17 @@ packages: typescript: optional: true + '@typescript-eslint/eslint-plugin@7.18.0': + resolution: {integrity: sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + '@typescript-eslint/parser': ^7.0.0 + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + '@typescript-eslint/parser@6.21.0': resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} engines: {node: ^16.0.0 || >=18.0.0} @@ -2867,10 +2905,24 @@ packages: typescript: optional: true + '@typescript-eslint/parser@7.18.0': + resolution: {integrity: sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + '@typescript-eslint/scope-manager@6.21.0': resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/scope-manager@7.18.0': + resolution: {integrity: sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==} + engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/type-utils@6.21.0': resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==} engines: {node: ^16.0.0 || >=18.0.0} @@ -2881,10 +2933,24 @@ packages: typescript: optional: true + '@typescript-eslint/type-utils@7.18.0': + resolution: {integrity: sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + '@typescript-eslint/types@6.21.0': resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/types@7.18.0': + resolution: {integrity: sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==} + engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/typescript-estree@6.21.0': resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} engines: {node: ^16.0.0 || >=18.0.0} @@ -2894,16 +2960,35 @@ packages: typescript: optional: true + '@typescript-eslint/typescript-estree@7.18.0': + resolution: {integrity: sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + '@typescript-eslint/utils@6.21.0': resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 + '@typescript-eslint/utils@7.18.0': + resolution: {integrity: sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + '@typescript-eslint/visitor-keys@6.21.0': resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/visitor-keys@7.18.0': + resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==} + engines: {node: ^18.18.0 || >=20.0.0} + '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} @@ -3085,6 +3170,10 @@ packages: resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==} engines: {node: '>=10'} + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + array-buffer-byte-length@1.0.1: resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} engines: {node: '>= 0.4'} @@ -3107,6 +3196,10 @@ packages: resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} engines: {node: '>= 0.4'} + array.prototype.findlastindex@1.2.5: + resolution: {integrity: sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==} + engines: {node: '>= 0.4'} + array.prototype.flat@1.3.2: resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} engines: {node: '>= 0.4'} @@ -3126,6 +3219,9 @@ packages: asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + ast-types-flow@0.0.8: + resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} + async@0.2.10: resolution: {integrity: sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ==} @@ -3158,9 +3254,17 @@ packages: resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==} engines: {node: '>= 6.0.0'} + axe-core@4.10.2: + resolution: {integrity: sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==} + engines: {node: '>=4'} + axios@1.7.7: resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==} + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + babel-jest@29.7.0: resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -3490,6 +3594,9 @@ packages: resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} engines: {'0': node >= 0.8} + confusing-browser-globals@1.0.11: + resolution: {integrity: sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==} + consola@2.15.3: resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==} @@ -3590,6 +3697,9 @@ packages: resolution: {integrity: sha512-Wa3cgG4+IC9zqezwozLbVBIiJ7Cjg1J2hPxvXcp1F3SFjzQzbdiGhOtI1thVfRBnBCX3kvCW63iaP9v+4yYa8w==} hasBin: true + damerau-levenshtein@1.0.8: + resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + data-uri-to-buffer@4.0.1: resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} engines: {node: '>= 12'} @@ -3617,6 +3727,14 @@ packages: supports-color: optional: true + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@4.3.7: resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} engines: {node: '>=6.0'} @@ -3851,12 +3969,76 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + eslint-config-airbnb-base@15.0.0: + resolution: {integrity: sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==} + engines: {node: ^10.12.0 || >=12.0.0} + peerDependencies: + eslint: ^7.32.0 || ^8.2.0 + eslint-plugin-import: ^2.25.2 + + eslint-config-airbnb-typescript@18.0.0: + resolution: {integrity: sha512-oc+Lxzgzsu8FQyFVa4QFaVKiitTYiiW3frB9KYW5OWdPrqFc7FzxgB20hP4cHMlr+MBzGcLl3jnCOVOydL9mIg==} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^7.0.0 + '@typescript-eslint/parser': ^7.0.0 + eslint: ^8.56.0 + + eslint-config-airbnb@19.0.4: + resolution: {integrity: sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==} + engines: {node: ^10.12.0 || ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^7.32.0 || ^8.2.0 + eslint-plugin-import: ^2.25.3 + eslint-plugin-jsx-a11y: ^6.5.1 + eslint-plugin-react: ^7.28.0 + eslint-plugin-react-hooks: ^4.3.0 + eslint-config-prettier@8.10.0: resolution: {integrity: sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==} hasBin: true peerDependencies: eslint: '>=7.0.0' + eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + + eslint-module-utils@2.12.0: + resolution: {integrity: sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + + eslint-plugin-import@2.31.0: + resolution: {integrity: sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + + eslint-plugin-jsx-a11y@6.10.2: + resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==} + engines: {node: '>=4.0'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 + eslint-plugin-prettier@4.2.1: resolution: {integrity: sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==} engines: {node: '>=12.0.0'} @@ -4783,6 +4965,10 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -4821,6 +5007,13 @@ packages: kuler@2.0.0: resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} + language-subtag-registry@0.3.23: + resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} + + language-tags@1.0.9: + resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} + engines: {node: '>=0.10'} + leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} @@ -5228,6 +5421,10 @@ packages: resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} engines: {node: '>= 0.4'} + object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} + object.values@1.2.0: resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} engines: {node: '>= 0.4'} @@ -5953,6 +6150,10 @@ packages: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} + string.prototype.includes@2.0.1: + resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} + engines: {node: '>= 0.4'} + string.prototype.matchall@4.0.11: resolution: {integrity: sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==} engines: {node: '>= 0.4'} @@ -6210,6 +6411,9 @@ packages: resolution: {integrity: sha512-xWFISjviPydmtmgeUAuXp4N1fky+VCtfhOkDUFIv5ea7p4wuTomI4QTrXvFBX2S4jZsmyTSrStQl+E+4w+RzxA==} engines: {node: '>=10.13.0'} + tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + tsconfig-paths@4.2.0: resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} engines: {node: '>=6'} @@ -8065,7 +8269,7 @@ snapshots: '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7(supports-color@9.4.0) espree: 9.6.1 globals: 13.24.0 ignore: 5.3.2 @@ -8133,7 +8337,7 @@ snapshots: '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7(supports-color@9.4.0) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -9043,6 +9247,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.24.4': optional: true + '@rtsao/scc@1.1.0': {} + '@scarf/scarf@1.4.0': {} '@sinclair/typebox@0.27.8': {} @@ -9562,6 +9768,8 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/json5@0.0.29': {} + '@types/jsonwebtoken@9.0.5': dependencies: '@types/node': 20.17.6 @@ -9700,6 +9908,24 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/scope-manager': 7.18.0 + '@typescript-eslint/type-utils': 7.18.0(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 7.18.0 + eslint: 8.57.1 + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 1.4.0(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.3.3)': dependencies: '@typescript-eslint/scope-manager': 6.21.0 @@ -9713,11 +9939,29 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3)': + dependencies: + '@typescript-eslint/scope-manager': 7.18.0 + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 7.18.0 + debug: 4.3.7(supports-color@9.4.0) + eslint: 8.57.1 + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/scope-manager@6.21.0': dependencies: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/visitor-keys': 6.21.0 + '@typescript-eslint/scope-manager@7.18.0': + dependencies: + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/visitor-keys': 7.18.0 + '@typescript-eslint/type-utils@6.21.0(eslint@8.57.1)(typescript@5.3.3)': dependencies: '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.3) @@ -9730,8 +9974,22 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/type-utils@7.18.0(eslint@8.57.1)(typescript@5.6.3)': + dependencies: + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.3) + '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.6.3) + debug: 4.3.7(supports-color@9.4.0) + eslint: 8.57.1 + ts-api-utils: 1.4.0(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/types@6.21.0': {} + '@typescript-eslint/types@7.18.0': {} + '@typescript-eslint/typescript-estree@6.21.0(typescript@5.3.3)': dependencies: '@typescript-eslint/types': 6.21.0 @@ -9747,6 +10005,21 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/typescript-estree@7.18.0(typescript@5.6.3)': + dependencies: + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/visitor-keys': 7.18.0 + debug: 4.3.7(supports-color@9.4.0) + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.6.3 + ts-api-utils: 1.4.0(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/utils@6.21.0(eslint@8.57.1)(typescript@5.3.3)': dependencies: '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) @@ -9761,11 +10034,27 @@ snapshots: - supports-color - typescript + '@typescript-eslint/utils@7.18.0(eslint@8.57.1)(typescript@5.6.3)': + dependencies: + '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) + '@typescript-eslint/scope-manager': 7.18.0 + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.3) + eslint: 8.57.1 + transitivePeerDependencies: + - supports-color + - typescript + '@typescript-eslint/visitor-keys@6.21.0': dependencies: '@typescript-eslint/types': 6.21.0 eslint-visitor-keys: 3.4.3 + '@typescript-eslint/visitor-keys@7.18.0': + dependencies: + '@typescript-eslint/types': 7.18.0 + eslint-visitor-keys: 3.4.3 + '@ungap/structured-clone@1.2.0': {} '@vitejs/plugin-react-swc@3.7.1(vite@5.4.10(@types/node@20.17.6)(terser@5.36.0))': @@ -9958,6 +10247,8 @@ snapshots: dependencies: tslib: 2.8.1 + aria-query@5.3.2: {} + array-buffer-byte-length@1.0.1: dependencies: call-bind: 1.0.7 @@ -9987,6 +10278,15 @@ snapshots: es-object-atoms: 1.0.0 es-shim-unscopables: 1.0.2 + array.prototype.findlastindex@1.2.5: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + es-shim-unscopables: 1.0.2 + array.prototype.flat@1.3.2: dependencies: call-bind: 1.0.7 @@ -10022,6 +10322,8 @@ snapshots: asap@2.0.6: {} + ast-types-flow@0.0.8: {} + async@0.2.10: {} async@3.2.6: {} @@ -10052,6 +10354,8 @@ snapshots: aws-ssl-profiles@1.1.2: {} + axe-core@4.10.2: {} + axios@1.7.7: dependencies: follow-redirects: 1.15.9 @@ -10060,6 +10364,8 @@ snapshots: transitivePeerDependencies: - debug + axobject-query@4.1.0: {} + babel-jest@29.7.0(@babel/core@7.26.0): dependencies: '@babel/core': 7.26.0 @@ -10456,6 +10762,8 @@ snapshots: readable-stream: 2.3.8 typedarray: 0.0.6 + confusing-browser-globals@1.0.11: {} + consola@2.15.3: {} content-disposition@0.5.4: @@ -10583,6 +10891,8 @@ snapshots: temp: 0.9.4 word-wrap: 1.2.5 + damerau-levenshtein@1.0.8: {} + data-uri-to-buffer@4.0.1: {} data-view-buffer@1.0.1: @@ -10609,6 +10919,10 @@ snapshots: dependencies: ms: 2.0.0 + debug@3.2.7: + dependencies: + ms: 2.1.3 + debug@4.3.7(supports-color@5.5.0): dependencies: ms: 2.1.3 @@ -10723,7 +11037,7 @@ snapshots: engine.io-client@6.6.2(bufferutil@4.0.8)(utf-8-validate@5.0.10): dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7(supports-color@9.4.0) engine.io-parser: 5.2.3 ws: 8.17.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) xmlhttprequest-ssl: 2.1.2 @@ -10896,10 +11210,105 @@ snapshots: escape-string-regexp@4.0.0: {} + eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1): + dependencies: + confusing-browser-globals: 1.0.11 + eslint: 8.57.1 + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1) + object.assign: 4.1.5 + object.entries: 1.1.8 + semver: 6.3.1 + + eslint-config-airbnb-typescript@18.0.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3))(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1): + dependencies: + '@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.3) + eslint: 8.57.1 + eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1) + transitivePeerDependencies: + - eslint-plugin-import + + eslint-config-airbnb@19.0.4(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.1))(eslint-plugin-react-hooks@5.0.0(eslint@8.57.1))(eslint-plugin-react@7.37.2(eslint@8.57.1))(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1) + eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) + eslint-plugin-react: 7.37.2(eslint@8.57.1) + eslint-plugin-react-hooks: 5.0.0(eslint@8.57.1) + object.assign: 4.1.5 + object.entries: 1.1.8 + eslint-config-prettier@8.10.0(eslint@8.57.1): dependencies: eslint: 8.57.1 + eslint-import-resolver-node@0.3.9: + dependencies: + debug: 3.2.7 + is-core-module: 2.15.1 + resolve: 1.22.8 + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint@8.57.1): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.3) + eslint: 8.57.1 + eslint-import-resolver-node: 0.3.9 + transitivePeerDependencies: + - supports-color + + eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.8 + array.prototype.findlastindex: 1.2.5 + array.prototype.flat: 1.3.2 + array.prototype.flatmap: 1.3.2 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 8.57.1 + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint@8.57.1) + hasown: 2.0.2 + is-core-module: 2.15.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.0 + semver: 6.3.1 + string.prototype.trimend: 1.0.8 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.3) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.1): + dependencies: + aria-query: 5.3.2 + array-includes: 3.1.8 + array.prototype.flatmap: 1.3.2 + ast-types-flow: 0.0.8 + axe-core: 4.10.2 + axobject-query: 4.1.0 + damerau-levenshtein: 1.0.8 + emoji-regex: 9.2.2 + eslint: 8.57.1 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + language-tags: 1.0.9 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + safe-regex-test: 1.0.3 + string.prototype.includes: 2.0.1 + eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@2.8.8): dependencies: eslint: 8.57.1 @@ -10963,7 +11372,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7(supports-color@9.4.0) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -12210,6 +12619,10 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} + json5@1.0.2: + dependencies: + minimist: 1.2.8 + json5@2.2.3: {} jsonc-parser@3.2.1: {} @@ -12261,6 +12674,12 @@ snapshots: kuler@2.0.0: {} + language-subtag-registry@0.3.23: {} + + language-tags@1.0.9: + dependencies: + language-subtag-registry: 0.3.23 + leven@3.1.0: {} levn@0.4.1: @@ -12643,6 +13062,12 @@ snapshots: es-abstract: 1.23.3 es-object-atoms: 1.0.0 + object.groupby@1.0.3: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + object.values@1.2.0: dependencies: call-bind: 1.0.7 @@ -13326,7 +13751,7 @@ snapshots: socket.io-client@4.8.1(bufferutil@4.0.8)(utf-8-validate@5.0.10): dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7(supports-color@9.4.0) engine.io-client: 6.6.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) socket.io-parser: 4.2.4 transitivePeerDependencies: @@ -13337,7 +13762,7 @@ snapshots: socket.io-parser@4.2.4: dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7(supports-color@9.4.0) transitivePeerDependencies: - supports-color @@ -13431,6 +13856,12 @@ snapshots: get-east-asian-width: 1.3.0 strip-ansi: 7.1.0 + string.prototype.includes@2.0.1: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + string.prototype.matchall@4.0.11: dependencies: call-bind: 1.0.7 @@ -13670,6 +14101,10 @@ snapshots: dependencies: typescript: 5.3.3 + ts-api-utils@1.4.0(typescript@5.6.3): + dependencies: + typescript: 5.6.3 + ts-interface-checker@0.1.13: {} ts-jest@29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@swc/core@1.8.0)(@types/node@20.17.6)(typescript@5.3.3)))(typescript@5.3.3): @@ -13776,6 +14211,13 @@ snapshots: enhanced-resolve: 5.17.1 tsconfig-paths: 4.2.0 + tsconfig-paths@3.15.0: + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.8 + strip-bom: 3.0.0 + tsconfig-paths@4.2.0: dependencies: json5: 2.2.3