From 336bffd796a9e54ce6a1f172945329efe0bf5198 Mon Sep 17 00:00:00 2001 From: Aseer KT Date: Thu, 27 Jun 2024 13:54:24 +0530 Subject: [PATCH] feat: react query integration --- .github/workflows/client-ci.yml | 39 + .github/workflows/server-ci.yml | 36 + .husky/commit-msg | 4 + .husky/pre-commit | 4 + README.md | 2 +- client/.eslintrc.cjs | 1 + client/package.json | 3 + client/src/App.tsx | 5 +- client/src/components/Skeleton.tsx | 5 +- client/src/components/Toast.tsx | 2 +- client/src/contexts/QueryCacheContext.tsx | 13 - client/src/features/auth/auth.interface.ts | 17 + client/src/features/auth/auth.service.ts | 14 + .../features/auth/components/LoginForm.tsx | 48 +- .../features/auth/components/SignUpForm.tsx | 45 +- client/src/features/chat/chat.interface.ts | 4 + .../features/chat/components/ChatHeader.tsx | 8 +- .../src/features/chat/components/ChatUser.tsx | 4 +- .../chat/components/TypingIndicators.tsx | 17 +- .../src/features/chat/hooks/useTypingUsers.ts | 19 + .../components/MemberItem.tsx | 2 +- .../features/member/components/MemberList.tsx | 126 +++ .../components/MembersSidebar.tsx | 0 .../{members => member}/components/index.ts | 0 .../src/features/member/member.interface.ts | 16 + client/src/features/member/member.service.ts | 9 + .../members/components/MemberList.tsx | 85 -- .../message/components/MessageItem.tsx | 2 +- .../message/components/MessageList.tsx | 77 +- .../src/features/message/message.interface.ts | 26 + .../src/features/message/message.service.ts | 9 + .../features/room/components/CreateRoom.tsx | 52 +- .../src/features/room/components/JoinRoom.tsx | 74 +- .../features/room/components/JoinRoomItem.tsx | 28 + .../features/room/components/JoinRoomList.tsx | 65 ++ .../features/room/components/RoomHeader.tsx | 21 +- .../src/features/room/components/RoomList.tsx | 43 - .../{RoomItem.tsx => UserRoomItem.tsx} | 6 +- .../features/room/components/UserRoomList.tsx | 58 ++ client/src/features/room/components/index.ts | 4 +- client/src/features/room/room.interface.ts | 17 + client/src/features/room/room.service.ts | 32 + client/src/hooks/useDebounce.ts | 0 client/src/hooks/useForm.ts | 4 +- .../{useInfiniteScroll.tsx => useInView.tsx} | 2 +- client/src/hooks/useInfiniteQuery.ts | 105 --- client/src/hooks/useMutation.ts | 72 -- client/src/hooks/useQuery.ts | 48 - client/src/hooks/useQueryCache.ts | 25 - client/src/interfaces/common.interface.ts | 10 + client/src/interfaces/member.inteface.ts | 9 - client/src/interfaces/message.interface.ts | 11 - client/src/interfaces/room.interface.ts | 7 - client/src/interfaces/socket.interface.ts | 4 +- client/src/main.tsx | 7 +- client/src/pages/ChatRoom.tsx | 14 +- client/src/providers/QueryCacheProvider.tsx | 44 - client/src/utils/api.ts | 51 ++ client/src/utils/headers.ts | 8 - client/tsconfig.json | 6 +- client/vite.config.ts | 4 + commitlint.config.js | 1 + package.json | 7 +- pnpm-lock.yaml | 836 ++++++++++++++++++ server/src/controllers/members.ts | 2 +- server/src/utils/db.ts | 5 +- 66 files changed, 1629 insertions(+), 695 deletions(-) create mode 100644 .github/workflows/client-ci.yml create mode 100644 .github/workflows/server-ci.yml create mode 100644 .husky/commit-msg create mode 100755 .husky/pre-commit delete mode 100644 client/src/contexts/QueryCacheContext.tsx create mode 100644 client/src/features/auth/auth.interface.ts create mode 100644 client/src/features/auth/auth.service.ts create mode 100644 client/src/features/chat/chat.interface.ts create mode 100644 client/src/features/chat/hooks/useTypingUsers.ts rename client/src/features/{members => member}/components/MemberItem.tsx (89%) create mode 100644 client/src/features/member/components/MemberList.tsx rename client/src/features/{members => member}/components/MembersSidebar.tsx (100%) rename client/src/features/{members => member}/components/index.ts (100%) create mode 100644 client/src/features/member/member.interface.ts create mode 100644 client/src/features/member/member.service.ts delete mode 100644 client/src/features/members/components/MemberList.tsx create mode 100644 client/src/features/message/message.interface.ts create mode 100644 client/src/features/message/message.service.ts create mode 100644 client/src/features/room/components/JoinRoomItem.tsx create mode 100644 client/src/features/room/components/JoinRoomList.tsx delete mode 100644 client/src/features/room/components/RoomList.tsx rename client/src/features/room/components/{RoomItem.tsx => UserRoomItem.tsx} (71%) create mode 100644 client/src/features/room/components/UserRoomList.tsx create mode 100644 client/src/features/room/room.interface.ts create mode 100644 client/src/features/room/room.service.ts delete mode 100644 client/src/hooks/useDebounce.ts rename client/src/hooks/{useInfiniteScroll.tsx => useInView.tsx} (94%) delete mode 100644 client/src/hooks/useInfiniteQuery.ts delete mode 100644 client/src/hooks/useMutation.ts delete mode 100644 client/src/hooks/useQuery.ts delete mode 100644 client/src/hooks/useQueryCache.ts create mode 100644 client/src/interfaces/common.interface.ts delete mode 100644 client/src/interfaces/member.inteface.ts delete mode 100644 client/src/interfaces/message.interface.ts delete mode 100644 client/src/interfaces/room.interface.ts delete mode 100644 client/src/providers/QueryCacheProvider.tsx create mode 100644 client/src/utils/api.ts delete mode 100644 client/src/utils/headers.ts create mode 100644 commitlint.config.js diff --git a/.github/workflows/client-ci.yml b/.github/workflows/client-ci.yml new file mode 100644 index 0000000..21ccf59 --- /dev/null +++ b/.github/workflows/client-ci.yml @@ -0,0 +1,39 @@ +name: mChat Server CI + +on: + push: + branches: + - main + paths: + - .github/workflows/client-ci.yml + - client/* + + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 + - name: Use Node.js 20 + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'pnpm' + + - name: Install deps + run: pnpm i --filter client + + - name: Lint + run: pnpm --filter client lint + + - name: Build + run: pnpm --filter client build + + + \ No newline at end of file diff --git a/.github/workflows/server-ci.yml b/.github/workflows/server-ci.yml new file mode 100644 index 0000000..957fb60 --- /dev/null +++ b/.github/workflows/server-ci.yml @@ -0,0 +1,36 @@ +name: mChat Server CI + +on: + push: + branches: + - main + paths: + - .github/workflows/server-ci.yml + - server/* + + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 + - name: Use Node.js 20 + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'pnpm' + + - name: Install dependencies + run: pnpm i --filter server + + - name: Build + run: pnpm --filter server build + + + \ No newline at end of file diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100644 index 0000000..9ef2ef0 --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +pnpm run commitlint ${1} diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..9c96ce9 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +pnpm build diff --git a/README.md b/README.md index 080d98c..c5dc27c 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ - Spin up entire stack (redis, mongo, client, server) ```bash -docker compose -f docker-compose.prod.yml up +docker compose -f docker-compose.prod.yml up --build ``` - Go to [http://localhost:3000](http://localhost:3000) diff --git a/client/.eslintrc.cjs b/client/.eslintrc.cjs index d6c9537..4172035 100644 --- a/client/.eslintrc.cjs +++ b/client/.eslintrc.cjs @@ -5,6 +5,7 @@ module.exports = { 'eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:react-hooks/recommended', + 'plugin:@tanstack/eslint-plugin-query/recommended', ], ignorePatterns: ['dist', '.eslintrc.cjs'], parser: '@typescript-eslint/parser', diff --git a/client/package.json b/client/package.json index b5301ee..c69f9b6 100644 --- a/client/package.json +++ b/client/package.json @@ -10,8 +10,10 @@ "preview": "vite preview" }, "dependencies": { + "@tanstack/react-query": "^5.48.0", "clsx": "^2.1.1", "cva": "npm:class-variance-authority@^0.7.0", + "immer": "^10.1.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-error-boundary": "^4.0.13", @@ -20,6 +22,7 @@ "tailwind-merge": "^2.3.0" }, "devDependencies": { + "@tanstack/eslint-plugin-query": "^5.47.0", "@types/node": "^20.14.5", "@types/react": "^18.2.66", "@types/react-dom": "^18.2.22", diff --git a/client/src/App.tsx b/client/src/App.tsx index cbd0819..143b0bd 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,7 +1,6 @@ import { Outlet } from 'react-router-dom' import { Toaster } from './components/Toaster' import { useAuthRedirect } from './hooks/useAuthRedirect' -import { QueryCacheProvider } from './providers/QueryCacheProvider' import { UserProvider } from './providers/UserProvider' const AuthRedirect = () => { @@ -13,9 +12,7 @@ function App() { return ( - - - + ) diff --git a/client/src/components/Skeleton.tsx b/client/src/components/Skeleton.tsx index 8f6b474..f74c5d3 100644 --- a/client/src/components/Skeleton.tsx +++ b/client/src/components/Skeleton.tsx @@ -4,7 +4,10 @@ export const Skeleton = ({ className }: { className?: string }) => { return (
) } diff --git a/client/src/components/Toast.tsx b/client/src/components/Toast.tsx index bfc42bd..3274263 100644 --- a/client/src/components/Toast.tsx +++ b/client/src/components/Toast.tsx @@ -3,7 +3,7 @@ import { useEffect } from 'react' import { createPortal } from 'react-dom' const toastVariants = cva( - 'fixed flex text-white justify-between gap-4 md:max-w-[250px] shadow-lg border rounded p-3 bottom-6 z-10 transition-all', + 'fixed flex text-white justify-between gap-4 md:min-w-[250px] shadow-lg border rounded p-3 bottom-6 z-10 transition-all', { variants: { severity: { diff --git a/client/src/contexts/QueryCacheContext.tsx b/client/src/contexts/QueryCacheContext.tsx deleted file mode 100644 index 1e74ac4..0000000 --- a/client/src/contexts/QueryCacheContext.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { createContext } from 'react' - -export type QueryCache = Record - -export type QueryCacheDispatch = React.Dispatch<{ - type: 'REGISTER_QUERY_CACHE' | 'INVALIDATE_QUERY_CACHE' - payload: string -}> - -export const QueryCacheContext = createContext({}) -export const QueryCacheDispatchContext = createContext( - {} as QueryCacheDispatch, -) diff --git a/client/src/features/auth/auth.interface.ts b/client/src/features/auth/auth.interface.ts new file mode 100644 index 0000000..9b401de --- /dev/null +++ b/client/src/features/auth/auth.interface.ts @@ -0,0 +1,17 @@ +export interface IUser { + _id: string + username: string + password: string + createdAt: string + updatedAt: string +} + +export interface IUserResponse { + user: IUser + token: string +} + +export interface IAuthMutationVariables { + username: string + password: string +} diff --git a/client/src/features/auth/auth.service.ts b/client/src/features/auth/auth.service.ts new file mode 100644 index 0000000..4eed09d --- /dev/null +++ b/client/src/features/auth/auth.service.ts @@ -0,0 +1,14 @@ +import { fetcher } from '@/utils/api' +import { IAuthMutationVariables } from './auth.interface' + +export const login = async (payload: IAuthMutationVariables) => + fetcher('users/login', { + method: 'POST', + body: JSON.stringify(payload), + }) + +export const signup = async (payload: IAuthMutationVariables) => + fetcher(`users`, { + method: 'POST', + body: JSON.stringify(payload), + }) diff --git a/client/src/features/auth/components/LoginForm.tsx b/client/src/features/auth/components/LoginForm.tsx index 0ec8cfe..21a8812 100644 --- a/client/src/features/auth/components/LoginForm.tsx +++ b/client/src/features/auth/components/LoginForm.tsx @@ -1,23 +1,14 @@ +import { useMutation } from '@tanstack/react-query' import { NavLink } from 'react-router-dom' import { Button } from '../../../components/Button' import { Input } from '../../../components/Input' import { useAuthSetter } from '../../../hooks/useAuth' import useForm from '../../../hooks/useForm' -import { useMutation } from '../../../hooks/useMutation' import { useToast } from '../../../hooks/useToast' -import { IUser } from '../../../interfaces/user.interface' import { setToken } from '../../../utils/token' import { isRequired } from '../../../utils/validators' - -interface LoginResponse { - user: IUser - token: string -} - -interface LoginVariables { - username: string - password: string -} +import { IAuthMutationVariables, IUserResponse } from '../auth.interface' +import { login } from '../auth.service' const validators = { username: [isRequired('Username is required')], @@ -31,24 +22,27 @@ export const LoginForm = () => { initialValues: { username: '', password: '' }, }) - const { mutate: login, loading } = useMutation( - '/api/users/login', - { method: 'POST' }, - ) - - const onSubmit = handleSubmit(async values => { - try { - const result = await login(values) - if (result?.user && result.token) { - setToken(result.token) - setAuth(result.user) + const { mutate: loginUser, isPending } = useMutation< + IUserResponse, + Error, + IAuthMutationVariables + >({ + mutationFn: login, + onSuccess(data) { + if (data.user && data.token) { + setToken(data.token) + setAuth(data.user) toast({ title: 'Login success', severity: 'success' }) } - } catch (error) { - toast({ title: (error as Error).message, severity: 'error' }) - } + }, + onError(error) { + console.log(error.message) + toast({ title: error.message, severity: 'error' }) + }, }) + const onSubmit = handleSubmit(loginUser) + return (
{ {...register('password', validators.password)} error={errors.password} /> - diff --git a/client/src/features/auth/components/SignUpForm.tsx b/client/src/features/auth/components/SignUpForm.tsx index e72aeb7..26a9602 100644 --- a/client/src/features/auth/components/SignUpForm.tsx +++ b/client/src/features/auth/components/SignUpForm.tsx @@ -1,23 +1,14 @@ +import { useMutation } from '@tanstack/react-query' import { NavLink } from 'react-router-dom' import { Button } from '../../../components/Button' import { Input } from '../../../components/Input' import { useAuthSetter } from '../../../hooks/useAuth' import useForm from '../../../hooks/useForm' -import { useMutation } from '../../../hooks/useMutation' import { useToast } from '../../../hooks/useToast' -import { IUser } from '../../../interfaces/user.interface' import { setToken } from '../../../utils/token' import { isRequired } from '../../../utils/validators' - -interface SignUpResponse { - user: IUser - token: string -} - -interface SignUpVariables { - username: string - password: string -} +import { IAuthMutationVariables, IUserResponse } from '../auth.interface' +import { signup } from '../auth.service' const validators = { username: [isRequired('Username is required')], @@ -31,24 +22,26 @@ export const SignUpForm = () => { initialValues: { username: '', password: '' }, }) - const { mutate: signup, loading } = useMutation< - SignUpResponse, - SignUpVariables - >('/api/users', { method: 'POST' }) - - const onSubmit = handleSubmit(async values => { - try { - const result = await signup(values) - if (result?.user && result.token) { - setToken(result.token) - setAuth(result.user) + const { mutate: signupUser, isPending } = useMutation< + IUserResponse, + Error, + IAuthMutationVariables + >({ + mutationFn: signup, + onSuccess: data => { + if (data.user && data.token) { + setToken(data.token) + setAuth(data.user) toast({ title: 'Login success', severity: 'success' }) } - } catch (error) { + }, + onError(error) { toast({ title: (error as Error).message, severity: 'error' }) - } + }, }) + const onSubmit = handleSubmit(signupUser) + return ( { {...register('password', validators.password)} error={errors.password} /> - diff --git a/client/src/features/chat/chat.interface.ts b/client/src/features/chat/chat.interface.ts new file mode 100644 index 0000000..e82217a --- /dev/null +++ b/client/src/features/chat/chat.interface.ts @@ -0,0 +1,4 @@ +export interface ITypingUser { + _id: string + username: string +} diff --git a/client/src/features/chat/components/ChatHeader.tsx b/client/src/features/chat/components/ChatHeader.tsx index fa700a5..3d15274 100644 --- a/client/src/features/chat/components/ChatHeader.tsx +++ b/client/src/features/chat/components/ChatHeader.tsx @@ -1,7 +1,7 @@ -import logoutSvg from '../../../assets/logout-2-svgrepo-com.svg' -import { Logo } from '../../../components/Logo' -import { useAuthSetter } from '../../../hooks/useAuth' -import { removeToken } from '../../../utils/token' +import logoutSvg from '@/assets/logout-2-svgrepo-com.svg' +import { Logo } from '@/components/Logo' +import { useAuthSetter } from '@/hooks/useAuth' +import { removeToken } from '@/utils/token' export const ChatHeader = () => { const setAuth = useAuthSetter() diff --git a/client/src/features/chat/components/ChatUser.tsx b/client/src/features/chat/components/ChatUser.tsx index e25e244..5cc50ec 100644 --- a/client/src/features/chat/components/ChatUser.tsx +++ b/client/src/features/chat/components/ChatUser.tsx @@ -1,5 +1,5 @@ -import { useAuthState } from '../../../hooks/useAuth' -import { cn } from '../../../utils/style' +import { useAuthState } from '@/hooks/useAuth' +import { cn } from '@/utils/style' export const ChatUser = ({ isConnected }: { isConnected: boolean }) => { const auth = useAuthState() diff --git a/client/src/features/chat/components/TypingIndicators.tsx b/client/src/features/chat/components/TypingIndicators.tsx index 8343ae4..b7fe578 100644 --- a/client/src/features/chat/components/TypingIndicators.tsx +++ b/client/src/features/chat/components/TypingIndicators.tsx @@ -1,20 +1,7 @@ -import { useEffect, useState } from 'react' -import { getSocketIO } from '../../../utils/socket' +import { useTypingUsers } from '../hooks/useTypingUsers' export const TypingIndicator = () => { - const [users, setUsers] = useState>( - [], - ) - - useEffect(() => { - const socket = getSocketIO() - socket.on('typingUsers', setUsers) - - return () => { - socket.off('typingUsers', setUsers) - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) + const { users } = useTypingUsers() let content diff --git a/client/src/features/chat/hooks/useTypingUsers.ts b/client/src/features/chat/hooks/useTypingUsers.ts new file mode 100644 index 0000000..dffec41 --- /dev/null +++ b/client/src/features/chat/hooks/useTypingUsers.ts @@ -0,0 +1,19 @@ +import { getSocketIO } from '@/utils/socket' +import { useEffect, useState } from 'react' +import { ITypingUser } from '../chat.interface' + +export const useTypingUsers = () => { + const [users, setUsers] = useState>([]) + + useEffect(() => { + const socket = getSocketIO() + socket.on('typingUsers', setUsers) + + return () => { + socket.off('typingUsers', setUsers) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + return { users } +} diff --git a/client/src/features/members/components/MemberItem.tsx b/client/src/features/member/components/MemberItem.tsx similarity index 89% rename from client/src/features/members/components/MemberItem.tsx rename to client/src/features/member/components/MemberItem.tsx index 5a0e563..512bbca 100644 --- a/client/src/features/members/components/MemberItem.tsx +++ b/client/src/features/member/components/MemberItem.tsx @@ -1,5 +1,5 @@ -import { IMember } from '../../../interfaces/member.inteface' import { cn } from '../../../utils/style' +import { IMember } from '../member.interface' interface MemberItemProps { member: IMember diff --git a/client/src/features/member/components/MemberList.tsx b/client/src/features/member/components/MemberList.tsx new file mode 100644 index 0000000..5ec8d84 --- /dev/null +++ b/client/src/features/member/components/MemberList.tsx @@ -0,0 +1,126 @@ +import { Skeleton } from '@/components/Skeleton' +import { useInView } from '@/hooks/useInView' +import { IPaginatedResult } from '@/interfaces/common.interface' +import { getSocketIO } from '@/utils/socket' +import { + InfiniteData, + Updater, + useInfiniteQuery, + useQueryClient, +} from '@tanstack/react-query' +import { produce } from 'immer' +import { Fragment, useEffect, useRef } from 'react' +import { IMember } from '../member.interface' +import { fetchRoomMembers } from '../member.service' +import { MemberItem } from './MemberItem' + +type MemberInfiniteData = InfiniteData, string> + +type MemberUpdater = Updater< + MemberInfiniteData | undefined, + MemberInfiniteData | undefined +> + +export const MemberList = ({ roomId }: { roomId: string }) => { + const listRef = useRef(null) + const queryClient = useQueryClient() + + const { data, hasNextPage, fetchNextPage, isLoading, error } = + useInfiniteQuery({ + queryKey: ['members', roomId], + queryFn: ({ queryKey, pageParam }) => + fetchRoomMembers({ roomId: queryKey[1], limit: 15, cursor: pageParam }), + initialPageParam: '', + getNextPageParam: lastPage => + lastPage.cursor ? lastPage.cursor : undefined, + }) + + const watchElement = useInView(listRef, fetchNextPage, hasNextPage) + + const updateMemberData = (updater: MemberUpdater) => { + queryClient.setQueriesData({ queryKey: ['members', roomId] }, updater) + } + + useEffect(() => { + const socket = getSocketIO() + + function setUserOnlineStatus(userId: string, online: boolean) { + updateMemberData(data => { + if (!data) { + return data + } + const updatedData = produce(data, draft => { + let member: IMember | undefined + draft.pages.forEach(page => { + page.data.forEach(m => { + if (m.user._id === userId) member = m + }) + }) + console.log(member) + if (member) { + member.online = online + } + }) + return updatedData + }) + } + + function handleNewMember(member: IMember) { + updateMemberData(data => { + if (!data) { + return data + } + const updatedData = produce(data, draft => { + draft.pages[0].data.unshift({ ...member, online: true }) + }) + return updatedData + }) + } + + function handleOnlineUser(userId: string) { + setUserOnlineStatus(userId, true) + } + + function handleOfflineUser(userId: string) { + setUserOnlineStatus(userId, false) + } + + socket.on('newMember', handleNewMember) + socket.on('userOnline', handleOnlineUser) + socket.on('userOffline', handleOfflineUser) + return () => { + socket.off('newMember', handleNewMember) + socket.off('userOnline', handleOnlineUser) + socket.off('userOffline', handleOfflineUser) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + let content + + if (error) { + return
{error.message}
+ } else if (isLoading) { + content = new Array(5).map((_, idx) => ( + + )) + } else if (data?.pages?.length) { + content = data.pages.map((page, i) => ( + + {page.data.map(member => ( + + ))} + + )) + } + + return ( +
    + {content} + {watchElement} +
+ ) +} diff --git a/client/src/features/members/components/MembersSidebar.tsx b/client/src/features/member/components/MembersSidebar.tsx similarity index 100% rename from client/src/features/members/components/MembersSidebar.tsx rename to client/src/features/member/components/MembersSidebar.tsx diff --git a/client/src/features/members/components/index.ts b/client/src/features/member/components/index.ts similarity index 100% rename from client/src/features/members/components/index.ts rename to client/src/features/member/components/index.ts diff --git a/client/src/features/member/member.interface.ts b/client/src/features/member/member.interface.ts new file mode 100644 index 0000000..372dbb4 --- /dev/null +++ b/client/src/features/member/member.interface.ts @@ -0,0 +1,16 @@ +import { TPaginatedParams } from '@/interfaces/common.interface' + +export interface IMember { + _id: string + roomId: string + user: { + _id: string + username: string + } + role: 'member' | 'admin' | 'owner' + online?: boolean +} + +export interface IGetRoomMembersArgs extends TPaginatedParams { + roomId: string +} diff --git a/client/src/features/member/member.service.ts b/client/src/features/member/member.service.ts new file mode 100644 index 0000000..08ce125 --- /dev/null +++ b/client/src/features/member/member.service.ts @@ -0,0 +1,9 @@ +import { IPaginatedResult } from '@/interfaces/common.interface' +import { fetcher, stringifyQueryParams } from '@/utils/api' +import { IGetRoomMembersArgs, IMember } from './member.interface' + +export const fetchRoomMembers = async ({ + roomId, + ...params +}: IGetRoomMembersArgs): Promise> => + fetcher(`rooms/${roomId}/members?${stringifyQueryParams(params)}`) diff --git a/client/src/features/members/components/MemberList.tsx b/client/src/features/members/components/MemberList.tsx deleted file mode 100644 index 9314bbc..0000000 --- a/client/src/features/members/components/MemberList.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { useEffect, useRef } from 'react' -import { Skeleton } from '../../../components/Skeleton' -import { useInfiniteQuery } from '../../../hooks/useInfiniteQuery' -import { useInfiniteScroll } from '../../../hooks/useInfiniteScroll' -import { IMember } from '../../../interfaces/member.inteface' -import { getSocketIO } from '../../../utils/socket' -import { MemberItem } from './MemberItem' - -export const MemberList = ({ roomId }: { roomId: string }) => { - const listRef = useRef(null) - const { - data: members, - setData: setMembers, - hasMore, - fetchMore, - loading, - error, - } = useInfiniteQuery(`/api/rooms/${roomId}/members`) - - const watchElement = useInfiniteScroll(listRef, fetchMore, hasMore) - - useEffect(() => { - const socket = getSocketIO() - - function setUserOnlineStatus(userId: string, online: boolean) { - const memberIndex = members!.findIndex(m => m.user._id === userId) - - if (memberIndex !== -1) { - setMembers(members => - members!.map((member, index) => - index === memberIndex ? { ...member, online } : member, - ), - ) - } - } - - function handleNewMember(member: IMember) { - setMembers(members => [...(members || []), { ...member, online: true }]) - } - - function handleOnlineUser(userId: string) { - setUserOnlineStatus(userId, true) - } - - function handleOfflineUser(userId: string) { - setUserOnlineStatus(userId, false) - } - - socket.on('newMember', handleNewMember) - if (members && members.length > 1) { - socket.on('userOnline', handleOnlineUser) - socket.on('userOffline', handleOfflineUser) - } - return () => { - socket.off('newMember', handleNewMember) - socket.off('userOnline', handleOnlineUser) - socket.off('userOffline', handleOfflineUser) - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [members]) - - let content - - if (error) { - return
{error.message}
- } else if (loading) { - content = new Array(5).map((_, idx) => ( - - )) - } else if (members?.length) { - content = members.map(member => ( - - )) - } - - return ( -
    - {content} - {watchElement} -
- ) -} diff --git a/client/src/features/message/components/MessageItem.tsx b/client/src/features/message/components/MessageItem.tsx index 3a8cfaf..2b857de 100644 --- a/client/src/features/message/components/MessageItem.tsx +++ b/client/src/features/message/components/MessageItem.tsx @@ -1,6 +1,6 @@ -import { IMessage } from '../../../interfaces/message.interface' import { formateChatDate } from '../../../utils/date' import { cn } from '../../../utils/style' +import { IMessage } from '../message.interface' interface MessageItemProps { message: IMessage diff --git a/client/src/features/message/components/MessageList.tsx b/client/src/features/message/components/MessageList.tsx index b223abb..1eddfe8 100644 --- a/client/src/features/message/components/MessageList.tsx +++ b/client/src/features/message/components/MessageList.tsx @@ -1,10 +1,15 @@ -import { useEffect, useRef } from 'react' -import { Skeleton } from '../../../components/Skeleton' -import { useAuthState } from '../../../hooks/useAuth' -import { useInfiniteQuery } from '../../../hooks/useInfiniteQuery' -import { useInfiniteScroll } from '../../../hooks/useInfiniteScroll' -import { IMessage } from '../../../interfaces/message.interface' -import { getSocketIO } from '../../../utils/socket' +import { Skeleton } from '@/components/Skeleton' +import { + IMessage, + TMessageInfiniteData, +} from '@/features/message/message.interface' +import { useAuthState } from '@/hooks/useAuth' +import { useInView } from '@/hooks/useInView' +import { getSocketIO } from '@/utils/socket' +import { useInfiniteQuery, useQueryClient } from '@tanstack/react-query' +import { produce } from 'immer' +import { Fragment, useEffect, useRef } from 'react' +import { fetchRoomMessages } from '../message.service' import { MessageItem } from './MessageItem' interface MessageListProps { @@ -14,13 +19,17 @@ interface MessageListProps { export const MessageList = ({ roomId }: MessageListProps) => { const auth = useAuthState() - const { - data: messages, - hasMore, - fetchMore, - setData: setMessagesData, - loading, - } = useInfiniteQuery(`/api/rooms/${roomId}/messages`) + const queryClient = useQueryClient() + + const { data, hasNextPage, fetchNextPage, isLoading, error, isSuccess } = + useInfiniteQuery({ + queryKey: ['messages', roomId], + queryFn: ({ pageParam }) => + fetchRoomMessages({ roomId, limit: 15, cursor: pageParam }), + initialPageParam: '', + getNextPageParam: lastPage => + lastPage.cursor ? lastPage.cursor : undefined, + }) const listRef = useRef(null) @@ -31,7 +40,19 @@ export const MessageList = ({ roomId }: MessageListProps) => { function scrollToBottom() { listRef.current?.scrollTo(0, listRef.current?.scrollHeight) } - setMessagesData(prevMessages => [message, ...(prevMessages ?? [])]) + queryClient.setQueriesData( + { queryKey: ['messages', roomId] }, + data => { + if (!data) { + return data + } + + const updatedData = produce(data, draft => { + draft?.pages[0].data.unshift(message) + }) + return updatedData + }, + ) setTimeout(scrollToBottom, 100) } @@ -42,23 +63,29 @@ export const MessageList = ({ roomId }: MessageListProps) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []) - const scrollElement = useInfiniteScroll(listRef, fetchMore, hasMore) + const scrollElement = useInView(listRef, fetchNextPage, hasNextPage) let content - if (loading) { + if (error) { + content =

{error.message}

+ } else if (isLoading) { content = new Array(5).map((_, index) => ( )) - } else if (messages?.length) { - content = messages.map(message => ( - + } else if (data?.pages.length) { + content = data.pages.map((page, i) => ( + + {page.data.map(message => ( + + ))} + )) - } else if (Array.isArray(messages)) { + } else if (isSuccess) { content =

Be the first one to message

} diff --git a/client/src/features/message/message.interface.ts b/client/src/features/message/message.interface.ts new file mode 100644 index 0000000..defd1e5 --- /dev/null +++ b/client/src/features/message/message.interface.ts @@ -0,0 +1,26 @@ +import { + IPaginatedResult, + TPaginatedParams, +} from '@/interfaces/common.interface' +import { InfiniteData } from '@tanstack/react-query' + +export interface IMessage { + _id: string + roomId: string + sender: { + _id: string + username: string + } + text: string + createdAt: string + updatedAt: string +} + +export interface IGetRoomMessagesArgs extends TPaginatedParams { + roomId: string +} + +export type TMessageInfiniteData = InfiniteData< + IPaginatedResult, + string +> diff --git a/client/src/features/message/message.service.ts b/client/src/features/message/message.service.ts new file mode 100644 index 0000000..8e916f3 --- /dev/null +++ b/client/src/features/message/message.service.ts @@ -0,0 +1,9 @@ +import { IPaginatedResult } from '@/interfaces/common.interface' +import { fetcher, stringifyQueryParams } from '@/utils/api' +import { IGetRoomMessagesArgs, IMessage } from './message.interface' + +export const fetchRoomMessages = async ({ + roomId, + ...params +}: IGetRoomMessagesArgs): Promise> => + fetcher(`rooms/${roomId}/messages?${stringifyQueryParams(params)}`) diff --git a/client/src/features/room/components/CreateRoom.tsx b/client/src/features/room/components/CreateRoom.tsx index ce8f379..e247094 100644 --- a/client/src/features/room/components/CreateRoom.tsx +++ b/client/src/features/room/components/CreateRoom.tsx @@ -1,26 +1,34 @@ +import { Button } from '@/components/Button' +import { Dialog } from '@/components/Dialog' +import { Input } from '@/components/Input' +import { useAutoFocus } from '@/hooks/useAutoFocus' +import { useDisclosure } from '@/hooks/useDisclosure' +import { useToast } from '@/hooks/useToast' +import { useMutation, useQueryClient } from '@tanstack/react-query' import { useRef, useState } from 'react' import { useNavigate } from 'react-router-dom' -import { Button } from '../../../components/Button' -import { Dialog } from '../../../components/Dialog' -import { Input } from '../../../components/Input' -import { useAuthState } from '../../../hooks/useAuth' -import { useAutoFocus } from '../../../hooks/useAutoFocus' -import { useDisclosure } from '../../../hooks/useDisclosure' -import { useMutation } from '../../../hooks/useMutation' -import { useInvalidateQueryCache } from '../../../hooks/useQueryCache' -import { useToast } from '../../../hooks/useToast' -import { IRoom } from '../../../interfaces/room.interface' +import { createNewRoom } from '../room.service' const CreateRoomForm = ({ onComplete }: { onComplete: () => void }) => { const [name, setName] = useState('') const { toast } = useToast() const navigate = useNavigate() - const auth = useAuthState() const inputRef = useRef(null) - const invalidateQueryCache = useInvalidateQueryCache() - const { mutate: createRoom, loading } = useMutation( - '/api/rooms', - ) + const queryClient = useQueryClient() + const { mutate: createRoom, isPending } = useMutation({ + mutationFn: createNewRoom, + onSuccess: result => { + if (result._id) { + toast({ title: `Room "${name}" created`, severity: 'success' }) + onComplete() + queryClient.invalidateQueries({ queryKey: ['userRooms'] }) + navigate(`/chat/${result._id}`) + } + }, + onError: error => { + toast({ title: (error as Error).message, severity: 'error' }) + }, + }) useAutoFocus(inputRef, []) @@ -29,17 +37,7 @@ const CreateRoomForm = ({ onComplete }: { onComplete: () => void }) => { if (!name.trim()) { return toast({ title: 'Room name is required', severity: 'error' }) } - try { - const result = await createRoom({ name }) - if (result._id) { - toast({ title: `Room "${name}" created`, severity: 'success' }) - onComplete() - invalidateQueryCache(`/api/users/${auth!._id}/rooms`) - navigate(`/chat/${result._id}`) - } - } catch (error) { - toast({ title: (error as Error).message, severity: 'error' }) - } + createRoom({ name }) } return ( @@ -53,7 +51,7 @@ const CreateRoomForm = ({ onComplete }: { onComplete: () => void }) => { autoFocus onChange={e => setName(e.target.value)} /> - + ) } diff --git a/client/src/features/room/components/JoinRoom.tsx b/client/src/features/room/components/JoinRoom.tsx index c063cc9..0e4e09c 100644 --- a/client/src/features/room/components/JoinRoom.tsx +++ b/client/src/features/room/components/JoinRoom.tsx @@ -1,16 +1,12 @@ -import { useRef, useState } from 'react' +import { useQueryClient } from '@tanstack/react-query' +import { useState } from 'react' import { useNavigate } from 'react-router-dom' import { Button } from '../../../components/Button' import { Dialog } from '../../../components/Dialog' -import { Skeleton } from '../../../components/Skeleton' -import { useAuthState } from '../../../hooks/useAuth' import { useDisclosure } from '../../../hooks/useDisclosure' -import { useInfiniteQuery } from '../../../hooks/useInfiniteQuery' -import { useInfiniteScroll } from '../../../hooks/useInfiniteScroll' -import { useInvalidateQueryCache } from '../../../hooks/useQueryCache' import { useToast } from '../../../hooks/useToast' -import { IRoom } from '../../../interfaces/room.interface' import { getSocketIO } from '../../../utils/socket' +import { JoinRoomList } from './JoinRoomList' export const JoinRoom = () => { const { isOpen, toggle } = useDisclosure() @@ -30,18 +26,7 @@ export const JoinRoom = () => { const JoinRoomsForm = ({ onClose }: { onClose: () => void }) => { const { toast } = useToast() const navigate = useNavigate() - const auth = useAuthState() - const invalidateQueryCache = useInvalidateQueryCache() - const { - data: rooms, - loading, - fetchMore, - hasMore, - error, - } = useInfiniteQuery('/api/rooms') - const listRef = useRef(null) - - const watchElement = useInfiniteScroll(listRef, fetchMore, hasMore) + const queryClient = useQueryClient() const [checkedRooms, setCheckedRooms] = useState>({}) @@ -58,8 +43,8 @@ const JoinRoomsForm = ({ onClose }: { onClose: () => void }) => { if (res?.success) { toast({ title: 'Rooms joined successfully', severity: 'success' }) navigate(`/chat/${roomIds[0]}`) - invalidateQueryCache(`/api/users/${auth!._id}/rooms`) - invalidateQueryCache(`/api/rooms`) + queryClient.invalidateQueries({ queryKey: ['userRooms'] }) + queryClient.invalidateQueries({ queryKey: ['roomsToJoin'] }) onClose() } else if (res.error) { throw res.error @@ -69,40 +54,6 @@ const JoinRoomsForm = ({ onClose }: { onClose: () => void }) => { } } - let content - - if (error) { - content =
Unable to fetch rooms
- } else if (loading) { - content = new Array(5).map((_, idx) => ( - - )) - } else if (rooms?.length) { - content = rooms.map(room => ( -
  • - -
  • - )) - } else if (Array.isArray(rooms)) { - content =

    No more rooms to join

    - } - return (
    void }) => {

    Select rooms to join

    -
      - {content} - {watchElement} -
    + Boolean(checkedRooms[roomId])} + toggleRoomCheck={(roomId, checked) => + setCheckedRooms(rooms => ({ ...rooms, [roomId]: checked })) + } + /> ) diff --git a/client/src/features/room/components/JoinRoomItem.tsx b/client/src/features/room/components/JoinRoomItem.tsx new file mode 100644 index 0000000..085eec5 --- /dev/null +++ b/client/src/features/room/components/JoinRoomItem.tsx @@ -0,0 +1,28 @@ +import { IRoom } from '../room.interface' + +export const JoinRoomItem = ({ + room, + isChecked, + toggleRoomCheck, +}: { + room: IRoom + isChecked: boolean + toggleRoomCheck: (id: string, isChecked: boolean) => void +}) => ( +
  • + +
  • +) diff --git a/client/src/features/room/components/JoinRoomList.tsx b/client/src/features/room/components/JoinRoomList.tsx new file mode 100644 index 0000000..d06906e --- /dev/null +++ b/client/src/features/room/components/JoinRoomList.tsx @@ -0,0 +1,65 @@ +import { Skeleton } from '@/components/Skeleton' +import { useInView } from '@/hooks/useInView' +import { useInfiniteQuery } from '@tanstack/react-query' +import { Fragment, useRef } from 'react' +import { fetchRoomsToJoin } from '../room.service' +import { JoinRoomItem } from './JoinRoomItem' + +interface JoinRoomListProps { + isRoomChecked: (id: string) => boolean + toggleRoomCheck: (id: string, checked: boolean) => void +} + +export const JoinRoomList = ({ + isRoomChecked, + toggleRoomCheck, +}: JoinRoomListProps) => { + const { data, isLoading, error, isSuccess, fetchNextPage, hasNextPage } = + useInfiniteQuery({ + queryKey: ['roomsToJoin'], + queryFn: ({ pageParam }) => + fetchRoomsToJoin({ limit: 15, cursor: pageParam }), + initialPageParam: '', + getNextPageParam: lastPage => + lastPage.cursor ? lastPage.cursor : undefined, + }) + + const listRef = useRef(null) + + const watchElement = useInView(listRef, fetchNextPage, hasNextPage) + + let content + + if (error) { + content =
    Unable to fetch rooms
    + } else if (isLoading) { + content = new Array(5).map((_, idx) => ( + + )) + } else if (data?.pages.length) { + content = data.pages.map((page, i) => ( + + {page.data.map(room => ( + + ))} + + )) + } else if (isSuccess) { + content =

    No more rooms to join

    + } + + return ( +
      + {content} + {watchElement} +
    + ) +} diff --git a/client/src/features/room/components/RoomHeader.tsx b/client/src/features/room/components/RoomHeader.tsx index 29128a2..44f27c3 100644 --- a/client/src/features/room/components/RoomHeader.tsx +++ b/client/src/features/room/components/RoomHeader.tsx @@ -1,9 +1,9 @@ +import backArrow from '@/assets/back-svgrepo-com.svg' +import usersSvg from '@/assets/users-svgrepo-com.svg' +import { Skeleton } from '@/components/Skeleton' +import { useQuery } from '@tanstack/react-query' import { NavLink } from 'react-router-dom' -import backArrow from '../../../assets/back-svgrepo-com.svg' -import usersSvg from '../../../assets/users-svgrepo-com.svg' -import { Skeleton } from '../../../components/Skeleton' -import { useQuery } from '../../../hooks/useQuery' -import { IRoom } from '../../../interfaces/room.interface' +import { fetchRoom } from '../room.service' interface RoomHeaderProps { roomId: string @@ -11,11 +11,18 @@ interface RoomHeaderProps { } export const RoomHeader = ({ roomId, showMembers }: RoomHeaderProps) => { - const { data: room, loading, error } = useQuery(`/api/rooms/${roomId}`) + const { + data: room, + isLoading, + error, + } = useQuery({ + queryKey: ['currentRoom', roomId], + queryFn: ({ queryKey }) => fetchRoom(queryKey[1]), + }) let content - if (loading) { + if (isLoading) { content = } else if (room?._id) { content = ( diff --git a/client/src/features/room/components/RoomList.tsx b/client/src/features/room/components/RoomList.tsx deleted file mode 100644 index b322920..0000000 --- a/client/src/features/room/components/RoomList.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { useRef } from 'react' -import { Skeleton } from '../../../components/Skeleton' -import { useAuthState } from '../../../hooks/useAuth' -import { useInfiniteQuery } from '../../../hooks/useInfiniteQuery' -import { useInfiniteScroll } from '../../../hooks/useInfiniteScroll' -import { IRoom } from '../../../interfaces/room.interface' -import { RoomItem } from './RoomItem' - -export const RoomList = () => { - const auth = useAuthState() - const { - data: rooms, - loading, - hasMore, - fetchMore, - error, - } = useInfiniteQuery(auth?._id ? `/api/users/${auth._id}/rooms` : '') - - const listRef = useRef(null) - - const watchElement = useInfiniteScroll(listRef, fetchMore, hasMore) - - let content - - if (error) { - content =

    {error.message}

    - } else if (loading) { - content = new Array(5).map((_, idx) => ( - - )) - } else if (rooms?.length) { - content = ( -
      - {rooms?.map(room => )} - {watchElement} -
    - ) - } else if (Array.isArray(rooms)) { - content =

    Join or create room

    - } - - return -} diff --git a/client/src/features/room/components/RoomItem.tsx b/client/src/features/room/components/UserRoomItem.tsx similarity index 71% rename from client/src/features/room/components/RoomItem.tsx rename to client/src/features/room/components/UserRoomItem.tsx index 5eb451a..8e124d8 100644 --- a/client/src/features/room/components/RoomItem.tsx +++ b/client/src/features/room/components/UserRoomItem.tsx @@ -1,12 +1,12 @@ import { NavLink } from 'react-router-dom' -import { IRoom } from '../../../interfaces/room.interface' import { cn } from '../../../utils/style' +import { IRoom } from '../room.interface' -interface RoomItemProps { +interface UserRoomItemProps { room: IRoom } -export const RoomItem = ({ room }: RoomItemProps) => { +export const UserRoomItem = ({ room }: UserRoomItemProps) => { return ( { + const auth = useAuthState() + const { data, isLoading, isSuccess, hasNextPage, fetchNextPage, error } = + useInfiniteQuery({ + queryKey: ['userRooms', auth], + queryFn: async ({ pageParam }) => { + return fetchUserRooms({ + userId: auth!._id, + limit: 15, + cursor: pageParam, + }) + }, + initialPageParam: '', + getNextPageParam(lastPage) { + return lastPage.cursor ? lastPage.cursor : undefined + }, + enabled: Boolean(auth?._id), + }) + + const listRef = useRef(null) + + const watchElement = useInView(listRef, fetchNextPage, hasNextPage) + + let content + + if (error) { + content =

    {error.message}

    + } else if (isLoading) { + content = new Array(5).map((_, idx) => ( + + )) + } else if (data?.pages.length) { + content = ( +
      + {data.pages.map((room, i) => ( + + {room.data.map(room => ( + + ))} + + ))} + {watchElement} +
    + ) + } else if (isSuccess) { + content =

    Join or create room

    + } + + return +} diff --git a/client/src/features/room/components/index.ts b/client/src/features/room/components/index.ts index ed24825..238276e 100644 --- a/client/src/features/room/components/index.ts +++ b/client/src/features/room/components/index.ts @@ -1,5 +1,5 @@ export { CreateRoom } from './CreateRoom' export { JoinRoom } from './JoinRoom' export { RoomHeader } from './RoomHeader' -export { RoomItem } from './RoomItem' -export { RoomList } from './RoomList' +export { UserRoomItem as RoomItem } from './UserRoomItem' +export { RoomList } from './UserRoomList' diff --git a/client/src/features/room/room.interface.ts b/client/src/features/room/room.interface.ts new file mode 100644 index 0000000..4d26083 --- /dev/null +++ b/client/src/features/room/room.interface.ts @@ -0,0 +1,17 @@ +import { TPaginatedParams } from '../../interfaces/common.interface' + +export interface IRoom { + _id: string + name: string + createdBy: string + createdAt: string + updatedAt: string +} + +export type TGetUserRoomsQueryVariables = TPaginatedParams & { + userId: string +} + +export interface ICreateRoomArgs { + name: string +} diff --git a/client/src/features/room/room.service.ts b/client/src/features/room/room.service.ts new file mode 100644 index 0000000..e7011f3 --- /dev/null +++ b/client/src/features/room/room.service.ts @@ -0,0 +1,32 @@ +import { + IPaginatedResult, + TPaginatedParams, +} from '@/interfaces/common.interface' +import { fetcher, getAuthHeaders, stringifyQueryParams } from '@/utils/api' +import { + ICreateRoomArgs, + IRoom, + TGetUserRoomsQueryVariables, +} from './room.interface' + +export const fetchUserRooms = async ({ + userId, + ...params +}: TGetUserRoomsQueryVariables): Promise> => + fetcher(`users/${userId}/rooms?${stringifyQueryParams(params)}`) + +export const fetchRoomsToJoin = async ( + params: TPaginatedParams, +): Promise> => + fetcher(`rooms?${stringifyQueryParams(params)}`) + +export const createNewRoom = async (args: ICreateRoomArgs): Promise => + fetcher('rooms', { + method: 'POST', + body: JSON.stringify(args), + }) + +export const fetchRoom = async (roomId: string): Promise => + fetcher(`rooms/${roomId}`, { + headers: getAuthHeaders(), + }) diff --git a/client/src/hooks/useDebounce.ts b/client/src/hooks/useDebounce.ts deleted file mode 100644 index e69de29..0000000 diff --git a/client/src/hooks/useForm.ts b/client/src/hooks/useForm.ts index 80caf38..aaf6b1f 100644 --- a/client/src/hooks/useForm.ts +++ b/client/src/hooks/useForm.ts @@ -35,7 +35,9 @@ const useForm = ({ initialValues }: { initialValues: TValues }) => { [], ) - const handleSubmit = (onSubmit: (values: TValues) => Promise) => { + const handleSubmit = ( + onSubmit: (values: TValues) => void | Promise, + ) => { return (e: React.FormEvent) => { e.preventDefault() const isValid = Object.values(errors).every(error => !error) diff --git a/client/src/hooks/useInfiniteScroll.tsx b/client/src/hooks/useInView.tsx similarity index 94% rename from client/src/hooks/useInfiniteScroll.tsx rename to client/src/hooks/useInView.tsx index 1349ec4..7ae177e 100644 --- a/client/src/hooks/useInfiniteScroll.tsx +++ b/client/src/hooks/useInView.tsx @@ -1,6 +1,6 @@ import { useEffect, useMemo, useRef } from 'react' -export const useInfiniteScroll = ( +export const useInView = ( rootRef: React.RefObject, onLoadMore: () => void, observe?: boolean, diff --git a/client/src/hooks/useInfiniteQuery.ts b/client/src/hooks/useInfiniteQuery.ts deleted file mode 100644 index 1274719..0000000 --- a/client/src/hooks/useInfiniteQuery.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { useEffect, useRef, useState } from 'react' -import { config } from '../config' -import { getAuthHeaders } from '../utils/headers' -import { useQueryHasCache, useSetQueryCache } from './useQueryCache' - -const stringifyQueryParams = >( - params: TQueryParams, -) => { - const queryParams = new URLSearchParams() - Object.keys(params).forEach(key => { - const value = String(params[key]) - if (value) { - queryParams.append(key, value) - } - }) - return queryParams.toString() -} - -export const useInfiniteQuery = ( - path: string, - fetchOptions?: RequestInit, -) => { - const [data, setData] = useState() - const [error, setError] = useState() - const [loading, setLoading] = useState(false) - const [isFetchingMore, setIsFetchingMore] = useState(false) - const [hasMore, setHasMore] = useState(false) - const queryParams = useRef({ limit: 15, cursor: '' }) - - const isCached = useQueryHasCache(path) - const setQueryCache = useSetQueryCache() - - const fetchData = async () => { - const res = await fetch( - `${config.backendUrl}${path}?${stringifyQueryParams(queryParams.current)}`, - { - ...fetchOptions, - headers: { - ...fetchOptions?.headers, - ...getAuthHeaders(), - }, - }, - ) - - const jsonData = await res.json() - - if (!res.ok) { - throw jsonData - } - - if (jsonData.data?.length) { - queryParams.current.cursor = jsonData.data[jsonData.data.length - 1]._id - } - - return jsonData - } - - async function fetchInitialData() { - if (!path) { - return - } - queryParams.current.cursor = '' - - setLoading(true) - setHasMore(false) - try { - const result = await fetchData() - setData(result.data) - setHasMore(result.hasMore) - setQueryCache(path) - } catch (error) { - setError(error as TError) - } finally { - setLoading(false) - } - } - - useEffect(() => { - fetchInitialData() - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [path]) - - useEffect(() => { - if (!isCached && Array.isArray(data)) { - fetchInitialData() - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isCached]) - - const fetchMore = async () => { - if (!data?.length) return - setIsFetchingMore(true) - try { - const result = await fetchData() - setData(prev => [...(prev || []), ...result.data]) - setHasMore(result.hasMore) - } catch (error) { - setError(error as TError) - } finally { - setIsFetchingMore(false) - } - } - - return { data, setData, hasMore, fetchMore, loading, isFetchingMore, error } -} diff --git a/client/src/hooks/useMutation.ts b/client/src/hooks/useMutation.ts deleted file mode 100644 index 289efeb..0000000 --- a/client/src/hooks/useMutation.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { useCallback, useState } from 'react' -import { config } from '../config' -import { getAuthHeaders } from '../utils/headers' - -type UseMutationOptions = RequestInit & { - baseUrl?: string -} - -export const useMutation = < - TData = unknown, - TBody = Record, - TError = Error, ->( - /** - * path should start with '/' - */ - path: string, - options?: UseMutationOptions, -) => { - const [data, setData] = useState(null) - const [error, setError] = useState(null) - const [loading, setLoading] = useState(false) - - const baseUrl = options?.baseUrl || config.backendUrl - - const mutate = useCallback( - async (body: TBody) => { - setLoading(true) - setError(null) - setData(null) - - try { - const headers = getAuthHeaders() - const res = await fetch(`${baseUrl}${path}`, { - ...options, - method: options?.method || 'POST', - headers: { - 'Content-Type': 'application/json', - ...options?.headers, - ...headers, - }, - body: JSON.stringify(body), - }) - - let responseData: TData - const contentType = res.headers.get('content-type') - if (contentType && contentType.includes('application/json')) { - responseData = await res.json() - } else { - responseData = (await res.text()) as unknown as TData - } - - if (res.ok) { - setData(responseData) - return responseData - } else { - const errorResponse = responseData as unknown as TError - setError(errorResponse) - throw errorResponse - } - } catch (error) { - setError(error as TError) - throw error - } finally { - setLoading(false) - } - }, - [baseUrl, path, options], - ) - - return { mutate, data, loading, error } -} diff --git a/client/src/hooks/useQuery.ts b/client/src/hooks/useQuery.ts deleted file mode 100644 index d549b4f..0000000 --- a/client/src/hooks/useQuery.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { useEffect, useRef, useState } from 'react' -import { config } from '../config' -import { getAuthHeaders } from '../utils/headers' -import { useQueryHasCache, useSetQueryCache } from './useQueryCache' - -export const useQuery = ( - path: string, - fetchOptions?: RequestInit, -) => { - const [data, setData] = useState() - const [error, setError] = useState() - const [loading, setLoading] = useState(true) - - const pathRef = useRef(path) - const isCached = useQueryHasCache(path) - const setQueryCache = useSetQueryCache() - - useEffect(() => { - if (!path || (pathRef.current === path && isCached && data)) { - return - } - async function fetchData() { - setLoading(true) - try { - const res = await fetch(`${config.backendUrl}${path}`, { - ...fetchOptions, - headers: { ...fetchOptions?.headers, ...getAuthHeaders() }, - }) - const data = await res.json() - if (res.ok) { - pathRef.current = path - setData(data) - setQueryCache(path) - } else { - setError(data) - } - } catch (error) { - setError(error as TError) - } finally { - setLoading(false) - } - } - fetchData() - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isCached, path]) - - return { data, setData, loading, error } -} diff --git a/client/src/hooks/useQueryCache.ts b/client/src/hooks/useQueryCache.ts deleted file mode 100644 index c1b174e..0000000 --- a/client/src/hooks/useQueryCache.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { useContext } from 'react' -import { - QueryCacheContext, - QueryCacheDispatchContext, -} from '../contexts/QueryCacheContext' - -export const useQueryHasCache = (key: string) => { - return Boolean(useContext(QueryCacheContext)[key]) -} - -export const useSetQueryCache = () => { - const dispatch = useContext(QueryCacheDispatchContext) - - return function (key: string) { - dispatch({ type: 'REGISTER_QUERY_CACHE', payload: key }) - } -} - -export const useInvalidateQueryCache = () => { - const dispatch = useContext(QueryCacheDispatchContext) - - return function (key: string) { - dispatch({ type: 'INVALIDATE_QUERY_CACHE', payload: key }) - } -} diff --git a/client/src/interfaces/common.interface.ts b/client/src/interfaces/common.interface.ts new file mode 100644 index 0000000..d027cd7 --- /dev/null +++ b/client/src/interfaces/common.interface.ts @@ -0,0 +1,10 @@ +export interface IPaginatedResult { + data: TData[] + hasMore: boolean + cursor: string +} + +export type TPaginatedParams = { + limit: number + cursor: string +} diff --git a/client/src/interfaces/member.inteface.ts b/client/src/interfaces/member.inteface.ts deleted file mode 100644 index 1686bf8..0000000 --- a/client/src/interfaces/member.inteface.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface IMember { - roomId: string - user: { - _id: string - username: string - } - role: 'member' | 'admin' | 'owner' - online?: boolean -} diff --git a/client/src/interfaces/message.interface.ts b/client/src/interfaces/message.interface.ts deleted file mode 100644 index 5f76cc0..0000000 --- a/client/src/interfaces/message.interface.ts +++ /dev/null @@ -1,11 +0,0 @@ -export interface IMessage { - _id: string - roomId: string - sender: { - _id: string - username: string - } - text: string - createdAt: string - updatedAt: string -} diff --git a/client/src/interfaces/room.interface.ts b/client/src/interfaces/room.interface.ts deleted file mode 100644 index 7498806..0000000 --- a/client/src/interfaces/room.interface.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface IRoom { - _id: string - name: string - createdBy: string - createdAt: string - updatedAt: string -} diff --git a/client/src/interfaces/socket.interface.ts b/client/src/interfaces/socket.interface.ts index f0e7aae..fe5f02e 100644 --- a/client/src/interfaces/socket.interface.ts +++ b/client/src/interfaces/socket.interface.ts @@ -1,6 +1,6 @@ import { Socket } from 'socket.io-client' -import { IMember } from './member.inteface' -import { IMessage } from './message.interface' +import { IMember } from '../features/member/member.interface' +import { IMessage } from '../features/message/message.interface' export interface ServerToClientEvents { userOnline: (userId: string) => void diff --git a/client/src/main.tsx b/client/src/main.tsx index 92b8813..0fd2cfc 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -1,3 +1,4 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import ReactDOM from 'react-dom/client' import { ErrorBoundary } from 'react-error-boundary' import { RouterProvider } from 'react-router-dom' @@ -5,8 +6,12 @@ import { ErrorFallback } from './components/ErrorFallback.tsx' import './index.css' import { router } from './router.tsx' +const queryClient = new QueryClient() + ReactDOM.createRoot(document.getElementById('root')!).render( - + + + , ) diff --git a/client/src/pages/ChatRoom.tsx b/client/src/pages/ChatRoom.tsx index 15c504c..42ed062 100644 --- a/client/src/pages/ChatRoom.tsx +++ b/client/src/pages/ChatRoom.tsx @@ -1,12 +1,12 @@ +import { TypingIndicator } from '@/features/chat/components' +import { MembersSidebar } from '@/features/member/components' +import { MessageComposer, MessageList } from '@/features/message/components' +import { RoomHeader } from '@/features/room/components' +import { useDisclosure } from '@/hooks/useDisclosure' +import { getSocketIO } from '@/utils/socket' +import { cn } from '@/utils/style' import { useEffect } from 'react' import { useParams } from 'react-router-dom' -import { TypingIndicator } from '../features/chat/components' -import { MembersSidebar } from '../features/members/components' -import { MessageComposer, MessageList } from '../features/message/components' -import { RoomHeader } from '../features/room/components' -import { useDisclosure } from '../hooks/useDisclosure' -import { getSocketIO } from '../utils/socket' -import { cn } from '../utils/style' export const Component = () => { const params = useParams<{ roomId: string }>() diff --git a/client/src/providers/QueryCacheProvider.tsx b/client/src/providers/QueryCacheProvider.tsx deleted file mode 100644 index da91172..0000000 --- a/client/src/providers/QueryCacheProvider.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { useReducer } from 'react' -import { - QueryCache, - QueryCacheContext, - QueryCacheDispatchContext, -} from '../contexts/QueryCacheContext' - -const initialState: QueryCache = {} - -function cacheReducer( - state = initialState, - action: { - type: 'REGISTER_QUERY_CACHE' | 'INVALIDATE_QUERY_CACHE' - payload: string - }, -) { - switch (action.type) { - case 'REGISTER_QUERY_CACHE': - return { ...state, [action.payload]: true } - case 'INVALIDATE_QUERY_CACHE': - delete state[action.payload] - return { - ...state, - } - default: - return state - } -} - -export const QueryCacheProvider = ({ - children, -}: { - children: React.ReactNode -}) => { - const [cache, dispatch] = useReducer(cacheReducer, initialState) - - return ( - - - {children} - - - ) -} diff --git a/client/src/utils/api.ts b/client/src/utils/api.ts new file mode 100644 index 0000000..5f89f48 --- /dev/null +++ b/client/src/utils/api.ts @@ -0,0 +1,51 @@ +import { config } from '../config' +import { getToken } from './token' + +export const getAuthHeaders = () => { + const token = getToken() + return { + 'Content-Type': 'application/json', + Authorization: token ? `Bearer ${token}` : '', + } +} + +export const getUrl = (path: string) => { + return `${config.backendUrl}/api/${path}` +} + +export const stringifyQueryParams = < + TQueryParams extends Record, +>( + params: TQueryParams, +) => { + const queryParams = new URLSearchParams() + Object.keys(params).forEach(key => { + const value = String(params[key]) + if (value) { + queryParams.append(key, value) + } + }) + return queryParams.toString() +} + +export const fetcher = async (path: string, options?: RequestInit) => { + const res = await fetch(getUrl(path), { + ...options, + headers: { + ...getAuthHeaders(), + ...options?.headers, + }, + }) + + const isJsonType = res.headers + .get('Content-Type') + ?.includes('application/json') + + const data = isJsonType ? await res.json() : await res.text() + + if (res.ok) { + return data + } else { + throw new Error(data.message) + } +} diff --git a/client/src/utils/headers.ts b/client/src/utils/headers.ts deleted file mode 100644 index 26f97bb..0000000 --- a/client/src/utils/headers.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { getToken } from './token' - -export const getAuthHeaders = () => { - const token = getToken() - return { - Authorization: token ? `Bearer ${token}` : '', - } -} diff --git a/client/tsconfig.json b/client/tsconfig.json index a7fc6fb..439469d 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -18,7 +18,11 @@ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + + "paths": { + "@/*": ["./src/*"] + } }, "include": ["src"], "references": [{ "path": "./tsconfig.node.json" }] diff --git a/client/vite.config.ts b/client/vite.config.ts index 8792d3f..88d1b54 100644 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -1,4 +1,5 @@ import react from '@vitejs/plugin-react-swc' +import path from 'path' import { defineConfig } from 'vite' // https://vitejs.dev/config/ @@ -7,4 +8,7 @@ export default defineConfig({ server: { port: 3000, }, + resolve: { + alias: [{ find: '@', replacement: path.resolve(__dirname, 'src') }], + }, }) diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 0000000..3f5e287 --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1 @@ +export default { extends: ['@commitlint/config-conventional'] }; diff --git a/package.json b/package.json index bac4052..f65f62d 100644 --- a/package.json +++ b/package.json @@ -5,12 +5,17 @@ "private": true, "scripts": { "dev": "pnpm run -r dev", - "build": "pnpm run -r build" + "build": "pnpm run -r build", + "prepare": "husky install", + "commitlint": "commitlint --edit" }, "keywords": [], "author": "Aseer KT", "license": "ISC", "devDependencies": { + "@commitlint/cli": "^19.3.0", + "@commitlint/config-conventional": "^19.2.2", + "husky": "^8.0.0", "typescript": "^5.2.2" }, "workspaces": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c253b8f..87222ee 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,18 +8,33 @@ importers: .: devDependencies: + '@commitlint/cli': + specifier: ^19.3.0 + version: 19.3.0(@types/node@20.14.5)(typescript@5.4.5) + '@commitlint/config-conventional': + specifier: ^19.2.2 + version: 19.2.2 + husky: + specifier: ^8.0.0 + version: 8.0.3 typescript: specifier: ^5.2.2 version: 5.4.5 client: dependencies: + '@tanstack/react-query': + specifier: ^5.48.0 + version: 5.48.0(react@18.3.1) clsx: specifier: ^2.1.1 version: 2.1.1 cva: specifier: npm:class-variance-authority@^0.7.0 version: class-variance-authority@0.7.0 + immer: + specifier: ^10.1.1 + version: 10.1.1 react: specifier: ^18.2.0 version: 18.3.1 @@ -39,6 +54,9 @@ importers: specifier: ^2.3.0 version: 2.3.0 devDependencies: + '@tanstack/eslint-plugin-query': + specifier: ^5.47.0 + version: 5.47.0(eslint@8.57.0)(typescript@5.4.5) '@types/node': specifier: ^20.14.5 version: 20.14.5 @@ -152,10 +170,91 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + '@babel/code-frame@7.24.7': + resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.24.7': + resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} + engines: {node: '>=6.9.0'} + + '@babel/highlight@7.24.7': + resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} + engines: {node: '>=6.9.0'} + '@babel/runtime@7.24.7': resolution: {integrity: sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==} engines: {node: '>=6.9.0'} + '@commitlint/cli@19.3.0': + resolution: {integrity: sha512-LgYWOwuDR7BSTQ9OLZ12m7F/qhNY+NpAyPBgo4YNMkACE7lGuUnuQq1yi9hz1KA4+3VqpOYl8H1rY/LYK43v7g==} + engines: {node: '>=v18'} + hasBin: true + + '@commitlint/config-conventional@19.2.2': + resolution: {integrity: sha512-mLXjsxUVLYEGgzbxbxicGPggDuyWNkf25Ht23owXIH+zV2pv1eJuzLK3t1gDY5Gp6pxdE60jZnWUY5cvgL3ufw==} + engines: {node: '>=v18'} + + '@commitlint/config-validator@19.0.3': + resolution: {integrity: sha512-2D3r4PKjoo59zBc2auodrSCaUnCSALCx54yveOFwwP/i2kfEAQrygwOleFWswLqK0UL/F9r07MFi5ev2ohyM4Q==} + engines: {node: '>=v18'} + + '@commitlint/ensure@19.0.3': + resolution: {integrity: sha512-SZEpa/VvBLoT+EFZVb91YWbmaZ/9rPH3ESrINOl0HD2kMYsjvl0tF7nMHh0EpTcv4+gTtZBAe1y/SS6/OhfZzQ==} + engines: {node: '>=v18'} + + '@commitlint/execute-rule@19.0.0': + resolution: {integrity: sha512-mtsdpY1qyWgAO/iOK0L6gSGeR7GFcdW7tIjcNFxcWkfLDF5qVbPHKuGATFqRMsxcO8OUKNj0+3WOHB7EHm4Jdw==} + engines: {node: '>=v18'} + + '@commitlint/format@19.3.0': + resolution: {integrity: sha512-luguk5/aF68HiF4H23ACAfk8qS8AHxl4LLN5oxPc24H+2+JRPsNr1OS3Gaea0CrH7PKhArBMKBz5RX9sA5NtTg==} + engines: {node: '>=v18'} + + '@commitlint/is-ignored@19.2.2': + resolution: {integrity: sha512-eNX54oXMVxncORywF4ZPFtJoBm3Tvp111tg1xf4zWXGfhBPKpfKG6R+G3G4v5CPlRROXpAOpQ3HMhA9n1Tck1g==} + engines: {node: '>=v18'} + + '@commitlint/lint@19.2.2': + resolution: {integrity: sha512-xrzMmz4JqwGyKQKTpFzlN0dx0TAiT7Ran1fqEBgEmEj+PU98crOFtysJgY+QdeSagx6EDRigQIXJVnfrI0ratA==} + engines: {node: '>=v18'} + + '@commitlint/load@19.2.0': + resolution: {integrity: sha512-XvxxLJTKqZojCxaBQ7u92qQLFMMZc4+p9qrIq/9kJDy8DOrEa7P1yx7Tjdc2u2JxIalqT4KOGraVgCE7eCYJyQ==} + engines: {node: '>=v18'} + + '@commitlint/message@19.0.0': + resolution: {integrity: sha512-c9czf6lU+9oF9gVVa2lmKaOARJvt4soRsVmbR7Njwp9FpbBgste5i7l/2l5o8MmbwGh4yE1snfnsy2qyA2r/Fw==} + engines: {node: '>=v18'} + + '@commitlint/parse@19.0.3': + resolution: {integrity: sha512-Il+tNyOb8VDxN3P6XoBBwWJtKKGzHlitEuXA5BP6ir/3loWlsSqDr5aecl6hZcC/spjq4pHqNh0qPlfeWu38QA==} + engines: {node: '>=v18'} + + '@commitlint/read@19.2.1': + resolution: {integrity: sha512-qETc4+PL0EUv7Q36lJbPG+NJiBOGg7SSC7B5BsPWOmei+Dyif80ErfWQ0qXoW9oCh7GTpTNRoaVhiI8RbhuaNw==} + engines: {node: '>=v18'} + + '@commitlint/resolve-extends@19.1.0': + resolution: {integrity: sha512-z2riI+8G3CET5CPgXJPlzftH+RiWYLMYv4C9tSLdLXdr6pBNimSKukYP9MS27ejmscqCTVA4almdLh0ODD2KYg==} + engines: {node: '>=v18'} + + '@commitlint/rules@19.0.3': + resolution: {integrity: sha512-TspKb9VB6svklxNCKKwxhELn7qhtY1rFF8ls58DcFd0F97XoG07xugPjjbVnLqmMkRjZDbDIwBKt9bddOfLaPw==} + engines: {node: '>=v18'} + + '@commitlint/to-lines@19.0.0': + resolution: {integrity: sha512-vkxWo+VQU5wFhiP9Ub9Sre0FYe019JxFikrALVoD5UGa8/t3yOJEpEhxC5xKiENKKhUkTpEItMTRAjHw2SCpZw==} + engines: {node: '>=v18'} + + '@commitlint/top-level@19.0.0': + resolution: {integrity: sha512-KKjShd6u1aMGNkCkaX4aG1jOGdn7f8ZI8TR1VEuNqUOjWTOdcDSsmglinglJ18JTjuBX5I1PtjrhQCRcixRVFQ==} + engines: {node: '>=v18'} + + '@commitlint/types@19.0.3': + resolution: {integrity: sha512-tpyc+7i6bPG9mvaBbtKUeghfyZSDgWquIDfMgqYtTbmZ9Y9VzEm2je9EYcQ0aoz5o7NvGS+rcDec93yO08MHYA==} + engines: {node: '>=v18'} + '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} @@ -558,6 +657,19 @@ packages: '@swc/types@0.1.8': resolution: {integrity: sha512-RNFA3+7OJFNYY78x0FYwi1Ow+iF1eF5WvmfY1nXPOEH4R2p/D4Cr1vzje7dNAI2aLFqpv8Wyz4oKSWqIZArpQA==} + '@tanstack/eslint-plugin-query@5.47.0': + resolution: {integrity: sha512-Y56KKu0DoftFkCd3H0xckDbvm38kg9UrGF2CABqMYr7yo94XUZhBYE8R3Gq7N4JxRvPha+lUg37eypECY5u2kA==} + peerDependencies: + eslint: ^8 || ^9 + + '@tanstack/query-core@5.48.0': + resolution: {integrity: sha512-lZAfPPeVIqXCswE9SSbG33B6/91XOWt/Iq41bFeWb/mnHwQSIfFRbkS4bfs+WhIk9abRArF9Id2fp0Mgo+hq6Q==} + + '@tanstack/react-query@5.48.0': + resolution: {integrity: sha512-GDExbjYWzvDokyRqMSWXdrPiYpp95Aig0oeMIrxTaruOJJgWiWfUP//OAaowm2RrRkGVsavSZdko/XmIrrV2Nw==} + peerDependencies: + react: ^18.0.0 + '@tsconfig/node10@1.0.11': resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} @@ -576,6 +688,9 @@ packages: '@types/connect@3.4.38': resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + '@types/conventional-commits-parser@5.0.0': + resolution: {integrity: sha512-loB369iXNmAZglwWATL+WRe+CRMmmBPtpolYzIebFaX4YA3x+BEfLqhUAV9WanycKI3TG1IMr5bMJDajDKLlUQ==} + '@types/cookie@0.4.1': resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==} @@ -655,6 +770,10 @@ packages: resolution: {integrity: sha512-adbXNVEs6GmbzaCpymHQ0MB6E4TqoiVbC0iqG3uijR8ZYfpAXMGttouQzF4Oat3P2GxDVIrg7bMI/P65LiQZdg==} engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/scope-manager@8.0.0-alpha.30': + resolution: {integrity: sha512-FGW/iPWGyPFamAVZ60oCAthMqQrqafUGebF8UKuq/ha+e9SVG6YhJoRzurlQXOVf8dHfOhJ0ADMXyFnMc53clg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/type-utils@7.13.1': resolution: {integrity: sha512-aWDbLu1s9bmgPGXSzNCxELu+0+HQOapV/y+60gPXafR8e2g1Bifxzevaa+4L2ytCWm+CHqpELq4CSoN9ELiwCg==} engines: {node: ^18.18.0 || >=20.0.0} @@ -669,6 +788,10 @@ packages: resolution: {integrity: sha512-7K7HMcSQIAND6RBL4kDl24sG/xKM13cA85dc7JnmQXw2cBDngg7c19B++JzvJHRG3zG36n9j1i451GBzRuHchw==} engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/types@8.0.0-alpha.30': + resolution: {integrity: sha512-4WzLlw27SO9pK9UFj/Hu7WGo8WveT0SEiIpFVsV2WwtQmLps6kouwtVCB8GJPZKJyurhZhcqCoQVQFmpv441Vg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@7.13.1': resolution: {integrity: sha512-uxNr51CMV7npU1BxZzYjoVz9iyjckBduFBP0S5sLlh1tXYzHzgZ3BR9SVsNed+LmwKrmnqN3Kdl5t7eZ5TS1Yw==} engines: {node: ^18.18.0 || >=20.0.0} @@ -678,16 +801,35 @@ packages: typescript: optional: true + '@typescript-eslint/typescript-estree@8.0.0-alpha.30': + resolution: {integrity: sha512-WSXbc9ZcXI+7yC+6q95u77i8FXz6HOLsw3ST+vMUlFy1lFbXyFL/3e6HDKQCm2Clt0krnoCPiTGvIn+GkYPn4Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + '@typescript-eslint/utils@7.13.1': resolution: {integrity: sha512-h5MzFBD5a/Gh/fvNdp9pTfqJAbuQC4sCN2WzuXme71lqFJsZtLbjxfSk4r3p02WIArOF9N94pdsLiGutpDbrXQ==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: eslint: ^8.56.0 + '@typescript-eslint/utils@8.0.0-alpha.30': + resolution: {integrity: sha512-rfhqfLqFyXhHNDwMnHiVGxl/Z2q/3guQ1jLlGQ0hi9Rb7inmwz42crM+NnLPR+2vEnwyw1P/g7fnQgQ3qvFx4g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + '@typescript-eslint/visitor-keys@7.13.1': resolution: {integrity: sha512-k/Bfne7lrP7hcb7m9zSsgcBmo+8eicqqfNAJ7uUY+jkTFpKeH2FSkWpFRtimBxgkyvqfu9jTPRbYOvud6isdXA==} engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/visitor-keys@8.0.0-alpha.30': + resolution: {integrity: sha512-XZuNurZxBqmr6ZIRIwWFq7j5RZd6ZlkId/HZEWyfciK+CWoyOxSF9Pv2VXH9Rlu2ZG2PfbhLz2Veszl4Pfn7yA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} @@ -696,6 +838,10 @@ packages: peerDependencies: vite: ^4 || ^5 + JSONStream@1.3.5: + resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} + hasBin: true + accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -717,6 +863,9 @@ packages: ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ajv@8.16.0: + resolution: {integrity: sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -725,6 +874,10 @@ packages: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} engines: {node: '>=12'} + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -756,6 +909,9 @@ packages: array-flatten@1.1.1: resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + array-ify@1.0.0: + resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} + array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} @@ -823,10 +979,18 @@ packages: caniuse-lite@1.0.30001636: resolution: {integrity: sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==} + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + chalk@5.3.0: + resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -834,6 +998,10 @@ packages: class-variance-authority@0.7.0: resolution: {integrity: sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==} + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + clsx@2.0.0: resolution: {integrity: sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==} engines: {node: '>=6'} @@ -846,10 +1014,16 @@ packages: resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} engines: {node: '>=0.10.0'} + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} @@ -861,6 +1035,9 @@ packages: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} + compare-func@2.0.0: + resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -872,6 +1049,19 @@ packages: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} + conventional-changelog-angular@7.0.0: + resolution: {integrity: sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==} + engines: {node: '>=16'} + + conventional-changelog-conventionalcommits@7.0.2: + resolution: {integrity: sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==} + engines: {node: '>=16'} + + conventional-commits-parser@5.0.0: + resolution: {integrity: sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==} + engines: {node: '>=16'} + hasBin: true + cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} @@ -887,6 +1077,23 @@ packages: resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} engines: {node: '>= 0.10'} + cosmiconfig-typescript-loader@5.0.0: + resolution: {integrity: sha512-+8cK7jRAReYkMwMiG+bxhcNKiHJDM6bR9FD/nGBXOWdMLuYawjF5cGrtLilJ+LGd3ZjCXnJjR5DkfWPoIVlqJA==} + engines: {node: '>=v16'} + peerDependencies: + '@types/node': '*' + cosmiconfig: '>=8.2' + typescript: '>=4' + + cosmiconfig@9.0.0: + resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} @@ -902,6 +1109,10 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + dargs@8.1.0: + resolution: {integrity: sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==} + engines: {node: '>=12'} + debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -956,6 +1167,10 @@ packages: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} + dot-prop@5.3.0: + resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} + engines: {node: '>=8'} + dotenv@16.4.5: resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} engines: {node: '>=12'} @@ -993,6 +1208,13 @@ packages: resolution: {integrity: sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==} engines: {node: '>=10.2.0'} + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + es-define-property@1.0.0: resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} engines: {node: '>= 0.4'} @@ -1013,6 +1235,10 @@ packages: escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -1065,6 +1291,10 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} + execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + express@4.19.2: resolution: {integrity: sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==} engines: {node: '>= 0.10.0'} @@ -1101,6 +1331,10 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} + find-up@7.0.0: + resolution: {integrity: sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==} + engines: {node: '>=18'} + flat-cache@3.2.0: resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} engines: {node: ^10.12.0 || >=12.0.0} @@ -1134,13 +1368,26 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + get-intrinsic@1.2.4: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} engines: {node: '>= 0.4'} + get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + get-tsconfig@4.7.5: resolution: {integrity: sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==} + git-raw-commits@4.0.0: + resolution: {integrity: sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==} + engines: {node: '>=16'} + hasBin: true + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1158,6 +1405,10 @@ packages: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported + global-directory@4.0.1: + resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} + engines: {node: '>=18'} + globals@13.24.0: resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} engines: {node: '>=8'} @@ -1172,6 +1423,10 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -1195,6 +1450,15 @@ packages: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} + human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + + husky@8.0.3: + resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==} + engines: {node: '>=14'} + hasBin: true + iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -1203,10 +1467,16 @@ packages: resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} engines: {node: '>= 4'} + immer@10.1.1: + resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==} + import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} + import-meta-resolve@4.1.0: + resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==} + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -1218,6 +1488,10 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + ini@4.1.1: + resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + ioredis@5.4.1: resolution: {integrity: sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==} engines: {node: '>=12.22.0'} @@ -1226,6 +1500,9 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} @@ -1249,10 +1526,22 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-obj@2.0.0: + resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} + engines: {node: '>=8'} + is-path-inside@3.0.3: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} + is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + is-text-path@2.0.0: + resolution: {integrity: sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==} + engines: {node: '>=8'} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -1274,12 +1563,22 @@ packages: json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + jsonparse@1.3.1: + resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} + engines: {'0': node >= 0.2.0} + jsonwebtoken@9.0.2: resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} engines: {node: '>=12', npm: '>=6'} @@ -1316,6 +1615,13 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + locate-path@7.2.0: + resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + lodash.defaults@4.2.0: resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} @@ -1340,12 +1646,30 @@ packages: lodash.isstring@4.0.1: resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + lodash.kebabcase@4.1.1: + resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==} + lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.mergewith@4.6.2: + resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==} + lodash.once@4.1.1: resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + lodash.snakecase@4.1.1: + resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} + + lodash.startcase@4.4.0: + resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} + + lodash.uniq@4.5.0: + resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} + + lodash.upperfirst@4.3.1: + resolution: {integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==} + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -1364,9 +1688,16 @@ packages: memory-pager@1.5.0: resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==} + meow@12.1.1: + resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==} + engines: {node: '>=16.10'} + merge-descriptors@1.0.1: resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -1392,6 +1723,10 @@ packages: engines: {node: '>=4'} hasBin: true + mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -1399,6 +1734,9 @@ packages: resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} engines: {node: '>=16 || 14 >=14.17'} + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} @@ -1488,6 +1826,10 @@ packages: resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} engines: {node: '>=0.10.0'} + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -1506,6 +1848,10 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -1514,10 +1860,18 @@ packages: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} + p-limit@4.0.0: + resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + p-locate@6.0.0: + resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + package-json-from-dist@1.0.0: resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} @@ -1525,6 +1879,10 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -1533,6 +1891,10 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} + path-exists@5.0.0: + resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} @@ -1541,6 +1903,10 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} @@ -1740,10 +2106,22 @@ packages: regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} @@ -1842,6 +2220,10 @@ packages: sparse-bitfield@3.0.3: resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==} + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + standard-as-callback@2.1.0: resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} @@ -1865,6 +2247,10 @@ packages: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} + strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -1874,6 +2260,10 @@ packages: engines: {node: '>=16 || 14 >=14.17'} hasBin: true + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -1890,6 +2280,10 @@ packages: engines: {node: '>=14.0.0'} hasBin: true + text-extensions@2.4.0: + resolution: {integrity: sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==} + engines: {node: '>=8'} + text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -1900,6 +2294,9 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -1963,6 +2360,10 @@ packages: undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + unicorn-magic@0.1.0: + resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} + engines: {node: '>=18'} + unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} @@ -2062,11 +2463,23 @@ packages: resolution: {integrity: sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==} engines: {node: '>=0.4.0'} + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + yaml@2.4.5: resolution: {integrity: sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==} engines: {node: '>= 14'} hasBin: true + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + yn@3.1.1: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} @@ -2075,14 +2488,143 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + yocto-queue@1.0.0: + resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} + engines: {node: '>=12.20'} + snapshots: '@alloc/quick-lru@5.2.0': {} + '@babel/code-frame@7.24.7': + dependencies: + '@babel/highlight': 7.24.7 + picocolors: 1.0.1 + + '@babel/helper-validator-identifier@7.24.7': {} + + '@babel/highlight@7.24.7': + dependencies: + '@babel/helper-validator-identifier': 7.24.7 + chalk: 2.4.2 + js-tokens: 4.0.0 + picocolors: 1.0.1 + '@babel/runtime@7.24.7': dependencies: regenerator-runtime: 0.14.1 + '@commitlint/cli@19.3.0(@types/node@20.14.5)(typescript@5.4.5)': + dependencies: + '@commitlint/format': 19.3.0 + '@commitlint/lint': 19.2.2 + '@commitlint/load': 19.2.0(@types/node@20.14.5)(typescript@5.4.5) + '@commitlint/read': 19.2.1 + '@commitlint/types': 19.0.3 + execa: 8.0.1 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - typescript + + '@commitlint/config-conventional@19.2.2': + dependencies: + '@commitlint/types': 19.0.3 + conventional-changelog-conventionalcommits: 7.0.2 + + '@commitlint/config-validator@19.0.3': + dependencies: + '@commitlint/types': 19.0.3 + ajv: 8.16.0 + + '@commitlint/ensure@19.0.3': + dependencies: + '@commitlint/types': 19.0.3 + lodash.camelcase: 4.3.0 + lodash.kebabcase: 4.1.1 + lodash.snakecase: 4.1.1 + lodash.startcase: 4.4.0 + lodash.upperfirst: 4.3.1 + + '@commitlint/execute-rule@19.0.0': {} + + '@commitlint/format@19.3.0': + dependencies: + '@commitlint/types': 19.0.3 + chalk: 5.3.0 + + '@commitlint/is-ignored@19.2.2': + dependencies: + '@commitlint/types': 19.0.3 + semver: 7.6.2 + + '@commitlint/lint@19.2.2': + dependencies: + '@commitlint/is-ignored': 19.2.2 + '@commitlint/parse': 19.0.3 + '@commitlint/rules': 19.0.3 + '@commitlint/types': 19.0.3 + + '@commitlint/load@19.2.0(@types/node@20.14.5)(typescript@5.4.5)': + dependencies: + '@commitlint/config-validator': 19.0.3 + '@commitlint/execute-rule': 19.0.0 + '@commitlint/resolve-extends': 19.1.0 + '@commitlint/types': 19.0.3 + chalk: 5.3.0 + cosmiconfig: 9.0.0(typescript@5.4.5) + cosmiconfig-typescript-loader: 5.0.0(@types/node@20.14.5)(cosmiconfig@9.0.0(typescript@5.4.5))(typescript@5.4.5) + lodash.isplainobject: 4.0.6 + lodash.merge: 4.6.2 + lodash.uniq: 4.5.0 + transitivePeerDependencies: + - '@types/node' + - typescript + + '@commitlint/message@19.0.0': {} + + '@commitlint/parse@19.0.3': + dependencies: + '@commitlint/types': 19.0.3 + conventional-changelog-angular: 7.0.0 + conventional-commits-parser: 5.0.0 + + '@commitlint/read@19.2.1': + dependencies: + '@commitlint/top-level': 19.0.0 + '@commitlint/types': 19.0.3 + execa: 8.0.1 + git-raw-commits: 4.0.0 + minimist: 1.2.8 + + '@commitlint/resolve-extends@19.1.0': + dependencies: + '@commitlint/config-validator': 19.0.3 + '@commitlint/types': 19.0.3 + global-directory: 4.0.1 + import-meta-resolve: 4.1.0 + lodash.mergewith: 4.6.2 + resolve-from: 5.0.0 + + '@commitlint/rules@19.0.3': + dependencies: + '@commitlint/ensure': 19.0.3 + '@commitlint/message': 19.0.0 + '@commitlint/to-lines': 19.0.0 + '@commitlint/types': 19.0.3 + execa: 8.0.1 + + '@commitlint/to-lines@19.0.0': {} + + '@commitlint/top-level@19.0.0': + dependencies: + find-up: 7.0.0 + + '@commitlint/types@19.0.3': + dependencies: + '@types/conventional-commits-parser': 5.0.0 + chalk: 5.3.0 + '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 @@ -2368,6 +2910,21 @@ snapshots: dependencies: '@swc/counter': 0.1.3 + '@tanstack/eslint-plugin-query@5.47.0(eslint@8.57.0)(typescript@5.4.5)': + dependencies: + '@typescript-eslint/utils': 8.0.0-alpha.30(eslint@8.57.0)(typescript@5.4.5) + eslint: 8.57.0 + transitivePeerDependencies: + - supports-color + - typescript + + '@tanstack/query-core@5.48.0': {} + + '@tanstack/react-query@5.48.0(react@18.3.1)': + dependencies: + '@tanstack/query-core': 5.48.0 + react: 18.3.1 + '@tsconfig/node10@1.0.11': optional: true @@ -2389,6 +2946,10 @@ snapshots: dependencies: '@types/node': 20.14.5 + '@types/conventional-commits-parser@5.0.0': + dependencies: + '@types/node': 20.14.5 + '@types/cookie@0.4.1': {} '@types/cors@2.8.17': @@ -2491,6 +3052,11 @@ snapshots: '@typescript-eslint/types': 7.13.1 '@typescript-eslint/visitor-keys': 7.13.1 + '@typescript-eslint/scope-manager@8.0.0-alpha.30': + dependencies: + '@typescript-eslint/types': 8.0.0-alpha.30 + '@typescript-eslint/visitor-keys': 8.0.0-alpha.30 + '@typescript-eslint/type-utils@7.13.1(eslint@8.57.0)(typescript@5.4.5)': dependencies: '@typescript-eslint/typescript-estree': 7.13.1(typescript@5.4.5) @@ -2505,6 +3071,8 @@ snapshots: '@typescript-eslint/types@7.13.1': {} + '@typescript-eslint/types@8.0.0-alpha.30': {} + '@typescript-eslint/typescript-estree@7.13.1(typescript@5.4.5)': dependencies: '@typescript-eslint/types': 7.13.1 @@ -2520,6 +3088,21 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/typescript-estree@8.0.0-alpha.30(typescript@5.4.5)': + dependencies: + '@typescript-eslint/types': 8.0.0-alpha.30 + '@typescript-eslint/visitor-keys': 8.0.0-alpha.30 + debug: 4.3.5 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.4 + semver: 7.6.2 + ts-api-utils: 1.3.0(typescript@5.4.5) + optionalDependencies: + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/utils@7.13.1(eslint@8.57.0)(typescript@5.4.5)': dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) @@ -2531,11 +3114,27 @@ snapshots: - supports-color - typescript + '@typescript-eslint/utils@8.0.0-alpha.30(eslint@8.57.0)(typescript@5.4.5)': + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@typescript-eslint/scope-manager': 8.0.0-alpha.30 + '@typescript-eslint/types': 8.0.0-alpha.30 + '@typescript-eslint/typescript-estree': 8.0.0-alpha.30(typescript@5.4.5) + eslint: 8.57.0 + transitivePeerDependencies: + - supports-color + - typescript + '@typescript-eslint/visitor-keys@7.13.1': dependencies: '@typescript-eslint/types': 7.13.1 eslint-visitor-keys: 3.4.3 + '@typescript-eslint/visitor-keys@8.0.0-alpha.30': + dependencies: + '@typescript-eslint/types': 8.0.0-alpha.30 + eslint-visitor-keys: 3.4.3 + '@ungap/structured-clone@1.2.0': {} '@vitejs/plugin-react-swc@3.7.0(@swc/helpers@0.5.11)(vite@5.3.1(@types/node@20.14.5))': @@ -2545,6 +3144,11 @@ snapshots: transitivePeerDependencies: - '@swc/helpers' + JSONStream@1.3.5: + dependencies: + jsonparse: 1.3.1 + through: 2.3.8 + accepts@1.3.8: dependencies: mime-types: 2.1.35 @@ -2568,10 +3172,21 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ajv@8.16.0: + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + ansi-regex@5.0.1: {} ansi-regex@6.0.1: {} + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 @@ -2600,6 +3215,8 @@ snapshots: array-flatten@1.1.1: {} + array-ify@1.0.0: {} + array-union@2.1.0: {} autoprefixer@10.4.19(postcss@8.4.38): @@ -2675,11 +3292,19 @@ snapshots: caniuse-lite@1.0.30001636: {} + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 + chalk@5.3.0: {} + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -2696,22 +3321,39 @@ snapshots: dependencies: clsx: 2.0.0 + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + clsx@2.0.0: {} clsx@2.1.1: {} cluster-key-slot@1.1.2: {} + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + color-convert@2.0.1: dependencies: color-name: 1.1.4 + color-name@1.1.3: {} + color-name@1.1.4: {} colors@1.4.0: {} commander@4.1.1: {} + compare-func@2.0.0: + dependencies: + array-ify: 1.0.0 + dot-prop: 5.3.0 + concat-map@0.0.1: {} content-disposition@0.5.4: @@ -2720,6 +3362,21 @@ snapshots: content-type@1.0.5: {} + conventional-changelog-angular@7.0.0: + dependencies: + compare-func: 2.0.0 + + conventional-changelog-conventionalcommits@7.0.2: + dependencies: + compare-func: 2.0.0 + + conventional-commits-parser@5.0.0: + dependencies: + JSONStream: 1.3.5 + is-text-path: 2.0.0 + meow: 12.1.1 + split2: 4.2.0 + cookie-signature@1.0.6: {} cookie@0.4.2: {} @@ -2731,6 +3388,22 @@ snapshots: object-assign: 4.1.1 vary: 1.1.2 + cosmiconfig-typescript-loader@5.0.0(@types/node@20.14.5)(cosmiconfig@9.0.0(typescript@5.4.5))(typescript@5.4.5): + dependencies: + '@types/node': 20.14.5 + cosmiconfig: 9.0.0(typescript@5.4.5) + jiti: 1.21.6 + typescript: 5.4.5 + + cosmiconfig@9.0.0(typescript@5.4.5): + dependencies: + env-paths: 2.2.1 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + parse-json: 5.2.0 + optionalDependencies: + typescript: 5.4.5 + create-require@1.1.1: optional: true @@ -2744,6 +3417,8 @@ snapshots: csstype@3.1.3: {} + dargs@8.1.0: {} + debug@2.6.9: dependencies: ms: 2.0.0 @@ -2781,6 +3456,10 @@ snapshots: dependencies: esutils: 2.0.3 + dot-prop@5.3.0: + dependencies: + is-obj: 2.0.0 + dotenv@16.4.5: {} eastasianwidth@0.2.0: {} @@ -2830,6 +3509,12 @@ snapshots: - supports-color - utf-8-validate + env-paths@2.2.1: {} + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + es-define-property@1.0.0: dependencies: get-intrinsic: 1.2.4 @@ -2866,6 +3551,8 @@ snapshots: escape-html@1.0.3: {} + escape-string-regexp@1.0.5: {} + escape-string-regexp@4.0.0: {} eslint-plugin-react-hooks@4.6.2(eslint@8.57.0): @@ -2946,6 +3633,18 @@ snapshots: etag@1.8.1: {} + execa@8.0.1: + dependencies: + cross-spawn: 7.0.3 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + express@4.19.2: dependencies: accepts: 1.3.8 @@ -3025,6 +3724,12 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 + find-up@7.0.0: + dependencies: + locate-path: 7.2.0 + path-exists: 5.0.0 + unicorn-magic: 0.1.0 + flat-cache@3.2.0: dependencies: flatted: 3.3.1 @@ -3051,6 +3756,8 @@ snapshots: function-bind@1.1.2: {} + get-caller-file@2.0.5: {} + get-intrinsic@1.2.4: dependencies: es-errors: 1.3.0 @@ -3059,10 +3766,18 @@ snapshots: has-symbols: 1.0.3 hasown: 2.0.2 + get-stream@8.0.1: {} + get-tsconfig@4.7.5: dependencies: resolve-pkg-maps: 1.0.0 + git-raw-commits@4.0.0: + dependencies: + dargs: 8.1.0 + meow: 12.1.1 + split2: 4.2.0 + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -3089,6 +3804,10 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 + global-directory@4.0.1: + dependencies: + ini: 4.1.1 + globals@13.24.0: dependencies: type-fest: 0.20.2 @@ -3108,6 +3827,8 @@ snapshots: graphemer@1.4.0: {} + has-flag@3.0.0: {} + has-flag@4.0.0: {} has-property-descriptors@1.0.2: @@ -3130,17 +3851,25 @@ snapshots: statuses: 2.0.1 toidentifier: 1.0.1 + human-signals@5.0.0: {} + + husky@8.0.3: {} + iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 ignore@5.3.1: {} + immer@10.1.1: {} + import-fresh@3.3.0: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 + import-meta-resolve@4.1.0: {} + imurmurhash@0.1.4: {} inflight@1.0.6: @@ -3150,6 +3879,8 @@ snapshots: inherits@2.0.4: {} + ini@4.1.1: {} + ioredis@5.4.1: dependencies: '@ioredis/commands': 1.2.0 @@ -3166,6 +3897,8 @@ snapshots: ipaddr.js@1.9.1: {} + is-arrayish@0.2.1: {} + is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 @@ -3184,8 +3917,16 @@ snapshots: is-number@7.0.0: {} + is-obj@2.0.0: {} + is-path-inside@3.0.3: {} + is-stream@3.0.0: {} + + is-text-path@2.0.0: + dependencies: + text-extensions: 2.4.0 + isexe@2.0.0: {} jackspeak@3.4.0: @@ -3204,10 +3945,16 @@ snapshots: json-buffer@3.0.1: {} + json-parse-even-better-errors@2.3.1: {} + json-schema-traverse@0.4.1: {} + json-schema-traverse@1.0.0: {} + json-stable-stringify-without-jsonify@1.0.1: {} + jsonparse@1.3.1: {} + jsonwebtoken@9.0.2: dependencies: jws: 3.2.2 @@ -3253,6 +4000,12 @@ snapshots: dependencies: p-locate: 5.0.0 + locate-path@7.2.0: + dependencies: + p-locate: 6.0.0 + + lodash.camelcase@4.3.0: {} + lodash.defaults@4.2.0: {} lodash.includes@4.3.0: {} @@ -3269,10 +4022,22 @@ snapshots: lodash.isstring@4.0.1: {} + lodash.kebabcase@4.1.1: {} + lodash.merge@4.6.2: {} + lodash.mergewith@4.6.2: {} + lodash.once@4.1.1: {} + lodash.snakecase@4.1.1: {} + + lodash.startcase@4.4.0: {} + + lodash.uniq@4.5.0: {} + + lodash.upperfirst@4.3.1: {} + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 @@ -3286,8 +4051,12 @@ snapshots: memory-pager@1.5.0: {} + meow@12.1.1: {} + merge-descriptors@1.0.1: {} + merge-stream@2.0.0: {} + merge2@1.4.1: {} methods@1.1.2: {} @@ -3305,6 +4074,8 @@ snapshots: mime@1.6.0: {} + mimic-fn@4.0.0: {} + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 @@ -3313,6 +4084,8 @@ snapshots: dependencies: brace-expansion: 2.0.1 + minimist@1.2.8: {} + minipass@7.1.2: {} mongodb-connection-string-url@3.0.1: @@ -3381,6 +4154,10 @@ snapshots: normalize-range@0.1.2: {} + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + object-assign@4.1.1: {} object-hash@3.0.0: {} @@ -3395,6 +4172,10 @@ snapshots: dependencies: wrappy: 1.0.2 + onetime@6.0.0: + dependencies: + mimic-fn: 4.0.0 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -3408,24 +4189,43 @@ snapshots: dependencies: yocto-queue: 0.1.0 + p-limit@4.0.0: + dependencies: + yocto-queue: 1.0.0 + p-locate@5.0.0: dependencies: p-limit: 3.1.0 + p-locate@6.0.0: + dependencies: + p-limit: 4.0.0 + package-json-from-dist@1.0.0: {} parent-module@1.0.1: dependencies: callsites: 3.1.0 + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.24.7 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + parseurl@1.3.3: {} path-exists@4.0.0: {} + path-exists@5.0.0: {} + path-is-absolute@1.0.1: {} path-key@3.1.1: {} + path-key@4.0.0: {} + path-parse@1.0.7: {} path-scurry@1.11.1: @@ -3556,8 +4356,14 @@ snapshots: regenerator-runtime@0.14.1: {} + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + resolve-from@4.0.0: {} + resolve-from@5.0.0: {} + resolve-pkg-maps@1.0.0: {} resolve@1.22.8: @@ -3712,6 +4518,8 @@ snapshots: dependencies: memory-pager: 1.5.0 + split2@4.2.0: {} + standard-as-callback@2.1.0: {} statuses@2.0.1: {} @@ -3736,6 +4544,8 @@ snapshots: dependencies: ansi-regex: 6.0.1 + strip-final-newline@3.0.0: {} + strip-json-comments@3.1.1: {} sucrase@3.35.0: @@ -3748,6 +4558,10 @@ snapshots: pirates: 4.0.6 ts-interface-checker: 0.1.13 + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -3785,6 +4599,8 @@ snapshots: transitivePeerDependencies: - ts-node + text-extensions@2.4.0: {} + text-table@0.2.0: {} thenify-all@1.6.0: @@ -3795,6 +4611,8 @@ snapshots: dependencies: any-promise: 1.3.0 + through@2.3.8: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -3855,6 +4673,8 @@ snapshots: undici-types@5.26.5: {} + unicorn-magic@0.1.0: {} + unpipe@1.0.0: {} update-browserslist-db@1.0.16(browserslist@4.23.1): @@ -3916,9 +4736,25 @@ snapshots: xmlhttprequest-ssl@2.0.0: {} + y18n@5.0.8: {} + yaml@2.4.5: {} + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.1.2 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + yn@3.1.1: optional: true yocto-queue@0.1.0: {} + + yocto-queue@1.0.0: {} diff --git a/server/src/controllers/members.ts b/server/src/controllers/members.ts index cd5a122..2fea97e 100644 --- a/server/src/controllers/members.ts +++ b/server/src/controllers/members.ts @@ -49,7 +49,7 @@ export const getRoomMembers: RequestHandler = async (req, res, next) => { online: onlineMembers.has(member.user._id.toString()), })) - res.json({ data: membersWithOnlineStatus, hasMore: result.hasMore }) + res.json({ ...result, data: membersWithOnlineStatus }) } catch (error) { next(error) } diff --git a/server/src/utils/db.ts b/server/src/utils/db.ts index 738e126..eba77c9 100644 --- a/server/src/utils/db.ts +++ b/server/src/utils/db.ts @@ -45,8 +45,11 @@ export async function findByPaginate( limit, }) + const hasMore = results?.length === limit + return { data: results, - hasMore: results?.length === limit, + hasMore, + cursor: hasMore ? results[results.length - 1]._id : '', } }