]*>(.*?)<\/h[1-6]>/gi, '$1. ') // ํค๋ฉ์ ๊ฐ์กฐ
+ .replace(/]*>(.*?)<\/p>/gi, '$1 ') // ๋จ๋ฝ ์ ์ง
+ .replace(/<[^>]*>/g, '') // ๋๋จธ์ง ํ๊ทธ ์ ๊ฑฐ
+ .replace(/\s+/g, ' ') // ๊ณต๋ฐฑ ์ ๋ฆฌ
+ .trim();
+};
+
+const fallbackMetadata: Metadata = {
+ title: '404 | ๋ชจ์ฌ๋ผ-IT',
+ description: '์กด์ฌํ์ง ์๋ ๋ชจ์์
๋๋ค.',
+ openGraph: {
+ title: '404 | ๋ชจ์ฌ๋ผ-IT',
+ description: '์กด์ฌํ์ง ์๋ ๋ชจ์์
๋๋ค.',
+ images: [{ url: '/logos/logo-img.svg' }],
+ },
+};
+
+export async function generateMetadata({
+ params,
+}: GroupDetailPageProps): Promise {
+ const groupId = Number((await params).groupId);
+ const cookieHeaderValue = await getAuthCookieHeader();
+
+ let group;
+
+ try {
+ const response = await fetch(
+ `${process.env.NEXT_PUBLIC_API_BASE_URL}/v2/groups/${groupId}`,
+ {
+ headers: {
+ Cookie: cookieHeaderValue,
+ },
+ },
+ );
+
+ if (!response.ok) return fallbackMetadata;
+
+ const data: GroupDetailResponse = await response.json();
+
+ group = data?.items?.group;
+ } catch {
+ return fallbackMetadata;
+ }
+
+ if (!group) return fallbackMetadata;
+
+ const plainDescription = convertHtmlToPlainText(group.description);
+
+ return {
+ title: `${group.title} | ๋ชจ์ฌ๋ผ-IT`,
+ description: plainDescription,
+ openGraph: {
+ title: `${group.title} | ๋ชจ์ฌ๋ผ-IT`,
+ description: plainDescription,
+ images: [{ url: '/logos/logo-img.svg' }],
+ },
+ };
+}
+
+export default async function GroupDetailPage({
+ params,
+}: GroupDetailPageProps) {
+ const groupId = Number((await params).groupId);
+ const cookieHeaderValue = await getAuthCookieHeader();
+
+ let response: Response;
+
+ try {
+ response = await fetch(
+ `${process.env.NEXT_PUBLIC_API_BASE_URL}/v2/groups/${groupId}`,
+ {
+ headers: {
+ Cookie: cookieHeaderValue,
+ },
+ next: { tags: [`group-detail-${groupId}`] },
+ },
+ );
+ } catch (error) {
+ console.error('Fetch ์์ฒญ ์คํจ:', error);
+ return (
+
+ );
+ }
+
+ if (response.status === 404) {
+ return notFound();
+ }
+
+ if (!response.ok) {
+ console.error('์๋ต ์ํ ์ค๋ฅ:', response.status);
+ return ;
+ }
+
+ let responseBody: GroupDetailResponse;
+
+ try {
+ responseBody = await response.json();
+ } catch (err) {
+ console.error('JSON ํ์ฑ ์ค๋ฅ:', err);
+ return ;
+ }
+
+ if (!responseBody.items) {
+ return notFound();
+ }
+
+ if (!responseBody.status.success) {
+ console.error('API ์ฑ๊ณต ์ํ false:', responseBody.status);
+ return ;
+ }
+
+ const data = responseBody.items;
+
+ const { group, host, isApplicant, isJoined } = data;
+
+ const isRecruiting =
+ !isBeforeToday(group.deadline) &&
+ group.participants.length < group.maxParticipants;
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ {isRecruiting && (
+
+ )}
+ >
+ );
+}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index f7fa87eb..ab1ccaa6 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,20 +1,17 @@
-import type { Metadata } from "next";
-import { Geist, Geist_Mono } from "next/font/google";
-import "./globals.css";
-
-const geistSans = Geist({
- variable: "--font-geist-sans",
- subsets: ["latin"],
-});
-
-const geistMono = Geist_Mono({
- variable: "--font-geist-mono",
- subsets: ["latin"],
-});
+import { Header } from '@/components/organisms/header';
+import { AutoLoginManager } from '@/features/auth/components/AutoLoginManager';
+import { ReactQueryProvider } from '@/providers/ReactQueryProvider';
+import { SocketProvider } from '@/providers/WSProvider';
+import type { Metadata } from 'next';
+import { Toaster } from 'sonner';
+import './globals.css';
export const metadata: Metadata = {
- title: "Create Next App",
- description: "Generated by create next app",
+ title: '๋ชจ์ฌ๋ผ-IT',
+ description: '๊ฐ๋ฐ์๋ค์ ์คํฐ๋, ์ฌ์ดํธ ํ๋ก์ ํธ ๋ชจ์ง ํ๋ซํผ',
+ icons: {
+ icon: '/logos/logo-img.svg',
+ },
};
export default function RootLayout({
@@ -23,11 +20,17 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
-
-
- {children}
+
+
+
+ {/* ์์ผ ์ค์ ์ ์ ๋ก๊ทธ์ธ ํ๋จํด์ผํ๋ฏ๋ก ๋ก๊ทธ์ธ ์๋ก ์ฌ๋ฆผ */}
+
+
+
+ {children}
+
+
+
);
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 7bcd29e7..21a64709 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,3 +1,100 @@
-export default function Home() {
- return ;
+import { request } from '@/api/request';
+import { Groups } from '@/components/organisms/group';
+import RecommendGroup from '@/components/organisms/recommend-group';
+import { QueryErrorBoundary } from '@/components/query-error-boundary';
+import { Position, Skill } from '@/types/enums';
+import { getAuthCookieHeader } from '@/utils/cookie';
+import {
+ dehydrate,
+ HydrationBoundary,
+ QueryClient,
+} from '@tanstack/react-query';
+
+export default async function Home({
+ searchParams,
+}: {
+ searchParams: Promise>;
+}) {
+ const awaitedSearchParams = await searchParams; // searchParams๊ฐ Promise ๊ฐ์ฒด์ฌ์ await์ผ๋ก ๋ฒ๊ฒจ๋ด์ผ ํจ
+ const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ staleTime: 60 * 1000, // ๊ธฐ๋ณธ ์บ์ฑ ์๊ฐ(1๋ถ)
+ },
+ },
+ });
+
+ const queryParams = {
+ type: awaitedSearchParams.type ?? '',
+ skill: awaitedSearchParams.skill
+ ? awaitedSearchParams.skill.split(',')
+ ? awaitedSearchParams.skill
+ .split(',')
+ .map((v) => Skill[v as keyof typeof Skill])
+ .join(',')
+ : Skill[awaitedSearchParams.skill as keyof typeof Skill]
+ : '',
+ position: awaitedSearchParams.position
+ ? awaitedSearchParams.position.split(',')
+ ? awaitedSearchParams.position
+ .split(',')
+ .map((v) => Position[v as keyof typeof Position])
+ .join(',')
+ : Position[awaitedSearchParams.position as keyof typeof Position]
+ : '',
+ sort: awaitedSearchParams.sort ?? 'createdAt',
+ order: awaitedSearchParams.order ?? 'desc',
+ search: awaitedSearchParams.search ?? '',
+ };
+
+ // console.log('โ
Fetching data from server ', queryParams); // DEV: ๐ก ์๋ฒ ์ปดํฌ๋ํธ์์ prefetch ํ๋์ง ํ์ธ์ฉ
+ const cookieHeaderValue = await getAuthCookieHeader();
+
+ try {
+ await queryClient.fetchInfiniteQuery({
+ queryKey: ['items', '/v2/groups', { size: 10, ...queryParams }],
+ queryFn({ pageParam }) {
+ return request.get(
+ '/v2/groups',
+ {
+ ...queryParams,
+ size: 10,
+ cursor:
+ queryParams.order === 'desc' || !queryParams.order
+ ? 'null'
+ : pageParam,
+ },
+ { credentials: 'include' },
+ { Cookie: cookieHeaderValue },
+ );
+ },
+ initialPageParam: 0,
+ });
+ } catch (e) {
+ //ISSUE: ์๋ฌ ์ค์
+ console.log(e);
+ return Error
;
+ }
+
+ return (
+
+
+
+
+ ๐ฅ ์ธ๊ธฐ๊ธ
+
+
+
+ โ ๏ธ ๊ทธ๋ฃน์ ๋ถ๋ฌ์ค๋ ์ค ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค. ๋ค์ ์๋ํด์ฃผ์ธ์.
+
+ }
+ >
+
+
+
+
+
+ );
}
diff --git a/src/app/write/__tests__/writeform.test.ts b/src/app/write/__tests__/writeform.test.ts
new file mode 100644
index 00000000..6bc094f2
--- /dev/null
+++ b/src/app/write/__tests__/writeform.test.ts
@@ -0,0 +1,79 @@
+import { request } from '@/api/request';
+import { groupsHandlers } from '@/mocks/handler/groups';
+import { GroupType, WriteFormWithCreatedAt } from '@/types';
+import { http, HttpResponse } from 'msw';
+import { setupServer } from 'msw/node';
+
+const server = setupServer(...groupsHandlers);
+
+// ๋ชจ๋ ํ
์คํธ๊ฐ ์์ํ๊ธฐ ์ MSW ์๋ฒ๋ฅผ ์์ํฉ๋๋ค.
+beforeAll(() => server.listen());
+// ์ด์ ํ
์คํธ์ ๋ชจ์ ์๋ต์ด ๋ค์ ํ
์คํธ์ ์ํฅ์ ์ฃผ์ง ์๋๋ก ์ด์ ํ
์คํธ์์ ์ค์ ๋ ํธ๋ค๋ฌ๋ฅผ ์ด๊ธฐํํฉ๋๋ค.
+afterEach(() => server.resetHandlers());
+// ๋ชจ๋ ํ
์คํธ๊ฐ ์๋ฃ๋ ํ์ MSW ์๋ฒ๋ฅผ ์ข
๋ฃํฉ๋๋ค.
+afterAll(() => server.close());
+
+describe('write form ํ
์คํธ', () => {
+ const tempBody: WriteFormWithCreatedAt = {
+ title: '์คํฐ๋ ๋ง๋ค๊ธฐ',
+ maxParticipants: 10,
+ deadline: new Date(2024, 5, 26),
+ startDate: new Date(2024, 5, 27),
+ endDate: new Date(2024, 6, 26),
+ description:
+ '์ฌ๋ฐ๋ Next.js ์คํฐ๋
๊ฐ์ดํด๋ณด์์
',
+ autoAllow: false,
+ type: GroupType.STUDY,
+ skills: ['Typescript', 'Next.js'],
+ position: ['FE'],
+ createdAt: new Date(2024.05, 26),
+ };
+
+ test('group์ด ์ ์์ ์ผ๋ก ์์ฑ๋์์ ์ { success: true }๋ฅผ ๋ฐ๋๋ค', async () => {
+ const result = await request.post(
+ '/v2/groups',
+ { 'Content-Type': 'application/json' },
+ JSON.stringify(tempBody),
+ );
+
+ expect(result).toEqual({ success: true });
+ });
+
+ test('group ์์ฑ ์ ์๋ต์ ์์ง๋ง ์๋ฌ๊ฐ ๋ฐ์ํ ๊ฒฝ์ฐ { success: false, code: 400 }๋ฅผ ๋ฐ๋๋ค', async () => {
+ server.use(
+ http.post(`${process.env.NEXT_PUBLIC_API_BASE_URL}/group`, () => {
+ return HttpResponse.json(
+ { success: false, code: 400 },
+ { status: 200 },
+ );
+ }),
+ );
+
+ const result = await request.post(
+ '/v2/groups',
+ { 'Content-Type': 'application/json' },
+ JSON.stringify(tempBody),
+ );
+
+ expect(result).toEqual({ success: false, code: 400 });
+ });
+
+ test('group ์์ฑ ์ ์๋ฒ ์๋ฌ๊ฐ ๋ฐ์ํ ๊ฒฝ์ฐ Unexpected Error๋ฅผ ๋ฐ๋๋ค', async () => {
+ server.use(
+ http.post(`${process.env.NEXT_PUBLIC_API_BASE_URL}/group`, () => {
+ return HttpResponse.json(
+ { message: 'Bad Request' },
+ { status: 400 }, // 400 ์ํ์ฝ๋ -> response.ok === false
+ );
+ }),
+ );
+
+ const result = request.post(
+ '/v2/groups',
+ { 'Content-Type': 'application/json' },
+ JSON.stringify(tempBody),
+ );
+
+ await expect(result).rejects.toThrow('Unexpected Error');
+ });
+});
diff --git a/src/app/write/page.tsx b/src/app/write/page.tsx
new file mode 100644
index 00000000..74cef58d
--- /dev/null
+++ b/src/app/write/page.tsx
@@ -0,0 +1,40 @@
+'use client';
+
+import { WriteForm } from '@/components/organisms/write-form';
+import useIsClient from '@/hooks/useIsClient';
+import useAuthStore from '@/stores/useAuthStore';
+import { routes } from '@/utils/routes';
+import { useRouter } from 'next/navigation';
+import { useEffect } from 'react';
+
+export default function Page() {
+ const user = useAuthStore((state) => state.user);
+ const router = useRouter();
+ const isClient = useIsClient();
+
+ useEffect(() => {
+ const handleBeforeUnload = (e: BeforeUnloadEvent) => {
+ e.preventDefault();
+ e.returnValue = ''; // ๋ธ๋ผ์ฐ์ ๊ฒฝ๊ณ ์ฐฝ์ ๋์ฐ๊ธฐ ์ํ ํธ๋ฆฌ๊ฑฐ
+ };
+
+ window.addEventListener('beforeunload', handleBeforeUnload);
+ return () => {
+ window.removeEventListener('beforeunload', handleBeforeUnload);
+ };
+ }, []);
+
+ if (!isClient) return null;
+
+ if (!user) {
+ router.push(routes.login);
+
+ return;
+ }
+
+ return (
+
+
+
+ );
+}
diff --git a/src/components/atoms/avatar/index.tsx b/src/components/atoms/avatar/index.tsx
new file mode 100644
index 00000000..588119c1
--- /dev/null
+++ b/src/components/atoms/avatar/index.tsx
@@ -0,0 +1,34 @@
+import {
+ Avatar as ShadcnAvatar,
+ AvatarFallback,
+ AvatarImage,
+} from '@/components/ui/avatar';
+
+type AvatarProps = {
+ imageSrc: string;
+ fallback: string;
+ className?: string;
+ onClick?: () => void;
+};
+
+/**
+ * ์๋ฐํ ์ปดํฌ๋ํธ
+ * @param imageSrc ์ด๋ฏธ์ง ์ฃผ์(๋ฌธ์์ด)
+ * @param fallback ์๋ฐํ ํด๋ฐฑ ๋ฌธ์์ด(๋ฌธ์์ด)
+ * @param className ํด๋์ค๋ช
(๋ฌธ์์ด)
+ * @param onClick ํด๋ฆญ ์ด๋ฒคํธ(ํจ์)
+ * @returns ์๋ฐํ ์ปดํฌ๋ํธ
+ */
+export const Avatar = ({
+ imageSrc,
+ fallback,
+ className,
+ onClick,
+}: AvatarProps) => {
+ return (
+
+
+ {fallback}
+
+ );
+};
diff --git a/src/components/atoms/badge/half-round-badge.tsx b/src/components/atoms/badge/half-round-badge.tsx
new file mode 100644
index 00000000..eb1fc08e
--- /dev/null
+++ b/src/components/atoms/badge/half-round-badge.tsx
@@ -0,0 +1,13 @@
+type BadgeProps = {
+ text: string;
+ className?: string;
+};
+export const HalfRoundBadge = ({ text, className }: BadgeProps) => {
+ return (
+
+ {text}
+
+ );
+};
diff --git a/src/components/atoms/badge/index.tsx b/src/components/atoms/badge/index.tsx
new file mode 100644
index 00000000..a5fb8fa3
--- /dev/null
+++ b/src/components/atoms/badge/index.tsx
@@ -0,0 +1,11 @@
+type BadgeProps = {
+ text: string;
+ className?: string;
+};
+export const Badge = ({ text, className }: BadgeProps) => {
+ return (
+
+ {text}
+
+ );
+};
diff --git a/src/components/atoms/circle-icon/circle-info.tsx b/src/components/atoms/circle-icon/circle-info.tsx
new file mode 100644
index 00000000..039055bd
--- /dev/null
+++ b/src/components/atoms/circle-icon/circle-info.tsx
@@ -0,0 +1,11 @@
+type CircleInfoProps = { className?: string };
+
+export const CircleInfo = ({ className }: CircleInfoProps) => {
+ return (
+
+ i
+
+ );
+};
diff --git a/src/components/atoms/circle-icon/circle-number.tsx b/src/components/atoms/circle-icon/circle-number.tsx
new file mode 100644
index 00000000..aad9d8d6
--- /dev/null
+++ b/src/components/atoms/circle-icon/circle-number.tsx
@@ -0,0 +1,11 @@
+type CircleNumberProps = { number: number; className?: string };
+
+export const CircleNumber = ({ number, className }: CircleNumberProps) => {
+ return (
+
+ {number}
+
+ );
+};
diff --git a/src/components/atoms/group/close-cover.tsx b/src/components/atoms/group/close-cover.tsx
new file mode 100644
index 00000000..459c36eb
--- /dev/null
+++ b/src/components/atoms/group/close-cover.tsx
@@ -0,0 +1,17 @@
+import Link from 'next/link';
+
+type CloseCoverProps = {
+ itemId: number;
+};
+
+export const CloseCover = ({ itemId }: CloseCoverProps) => {
+ return (
+
+
+
+ โ๏ธ ๋ชจ์ง์ด ์ข
๋ฃ๋์์ต๋๋ค.
+
+
+
+ );
+};
diff --git a/src/components/atoms/group/deadline.tsx b/src/components/atoms/group/deadline.tsx
new file mode 100644
index 00000000..22f235a7
--- /dev/null
+++ b/src/components/atoms/group/deadline.tsx
@@ -0,0 +1,17 @@
+type DeadlineProps = {
+ text: string;
+ className?: string;
+};
+
+export const Deadline = ({ text, className }: DeadlineProps) => {
+ return (
+
+
+ ๋ง๊ฐ์ผ
+
+ {text}
+
+ );
+};
diff --git a/src/components/atoms/group/group-positions.tsx b/src/components/atoms/group/group-positions.tsx
new file mode 100644
index 00000000..a18c6cd7
--- /dev/null
+++ b/src/components/atoms/group/group-positions.tsx
@@ -0,0 +1,22 @@
+import { Position } from '@/types/enums';
+import { PositionBadge } from '../../molecules/position-badge';
+
+type GroupPositionsProps = {
+ positions: Position[];
+ className?: string;
+};
+
+export const GroupPositions = ({
+ positions,
+ className,
+}: GroupPositionsProps) => {
+ return (
+
+ {positions.map((position, i) => (
+ -
+
+
+ ))}
+
+ );
+};
diff --git a/src/components/atoms/group/group-skills.tsx b/src/components/atoms/group/group-skills.tsx
new file mode 100644
index 00000000..e4941e37
--- /dev/null
+++ b/src/components/atoms/group/group-skills.tsx
@@ -0,0 +1,19 @@
+import { Skill } from '@/types/enums';
+import { SkillBadge } from '../../molecules/skill-badge';
+
+type GroupSkillsProps = {
+ skills: Skill[];
+ className?: string;
+};
+
+export const GroupSkills = ({ skills, className }: GroupSkillsProps) => {
+ return (
+
+ {skills?.map((skill, i) => (
+ -
+
+
+ ))}
+
+ );
+};
diff --git a/src/components/atoms/group/group-title.tsx b/src/components/atoms/group/group-title.tsx
new file mode 100644
index 00000000..125973a9
--- /dev/null
+++ b/src/components/atoms/group/group-title.tsx
@@ -0,0 +1,14 @@
+type GroupTitleProps = {
+ text: string;
+ className?: string;
+};
+
+export const GroupTitle = ({ text, className }: GroupTitleProps) => {
+ return (
+
+ {text}
+
+ );
+};
diff --git a/src/components/atoms/group/particiapant-progress.tsx b/src/components/atoms/group/particiapant-progress.tsx
new file mode 100644
index 00000000..b0eb36b1
--- /dev/null
+++ b/src/components/atoms/group/particiapant-progress.tsx
@@ -0,0 +1,41 @@
+'use client';
+
+import { Progress } from '@/components/ui/progress';
+import Image from 'next/image';
+import { useEffect, useState } from 'react';
+
+type GroupProgressProps = {
+ participantsCount: number;
+ maxParticipants: number;
+ className?: string;
+};
+
+export const GroupProgress = ({
+ participantsCount,
+ maxParticipants,
+ className,
+}: GroupProgressProps) => {
+ const [progress, setProgress] = useState(0);
+
+ useEffect(() => {
+ requestAnimationFrame(() => {
+ setProgress((participantsCount / maxParticipants) * 100);
+ });
+ }, [maxParticipants, participantsCount]);
+
+ return (
+
+
+
+ {`${participantsCount}/${maxParticipants}`}
+
+
+
+ );
+};
diff --git a/src/components/atoms/input-text/index.tsx b/src/components/atoms/input-text/index.tsx
new file mode 100644
index 00000000..fab263ef
--- /dev/null
+++ b/src/components/atoms/input-text/index.tsx
@@ -0,0 +1,23 @@
+import { Input } from '@/components/ui/input';
+import * as React from 'react';
+
+// ํค๋ณด๋๋ก ์ง์ ์
๋ ฅํ๋ type์ผ๋ก ์ ํ
+export type InputTextProps = React.ComponentProps<'input'> & {
+ type?: 'text' | 'password' | 'search' | 'email' | 'url' | 'tel' | 'number';
+};
+
+// ์ฌ์ฉ์๊ฐ ํค๋ณด๋๋ก ์ง์ ํ
์คํธ๋ฅผ ์
๋ ฅํ ์ ์๋ Input์
๋๋ค
+// type์ ์
๋ ฅํ์ง ์์ ๊ฒฝ์ฐ ๊ธฐ๋ณธ ํ์
์ text์
๋๋ค
+export const InputText = ({
+ className,
+ type = 'text',
+ ...props
+}: InputTextProps) => {
+ return (
+
+ );
+};
diff --git a/src/components/atoms/login-require-button/index.tsx b/src/components/atoms/login-require-button/index.tsx
new file mode 100644
index 00000000..27e52829
--- /dev/null
+++ b/src/components/atoms/login-require-button/index.tsx
@@ -0,0 +1,69 @@
+'use client';
+
+import { Button } from '@/components/ui/button';
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogTitle,
+} from '@/components/ui/dialog';
+import useAuthStore from '@/stores/useAuthStore';
+import { useRouter } from 'next/navigation';
+import { useState } from 'react';
+
+type LoginRequireButtonProps = {
+ children: React.ReactNode;
+ onClick: () => void;
+ disabled?: boolean;
+ className?: string;
+};
+
+export const LoginRequireButton = ({
+ children,
+ onClick,
+ disabled = false,
+ className = '',
+}: LoginRequireButtonProps) => {
+ const [isOpen, setIsOpen] = useState(false);
+ const user = useAuthStore((state) => state.user);
+ const router = useRouter();
+
+ const LoginRequireButtonClickHandler = () => {
+ if (!user) {
+ setIsOpen(true); // ๋ก๊ทธ์ธ ๋ชจ๋ฌ ์ด๊ธฐ
+ } else {
+ onClick(); // ๋ก๊ทธ์ธ ์ํ๋ฉด ์๋ ๋์ ์ํ
+ }
+ };
+
+ return (
+ <>
+
+
+ >
+ );
+};
diff --git a/src/components/atoms/logout-button/index.tsx b/src/components/atoms/logout-button/index.tsx
new file mode 100644
index 00000000..2b1fc9c7
--- /dev/null
+++ b/src/components/atoms/logout-button/index.tsx
@@ -0,0 +1,52 @@
+'use client';
+
+import useAuthStore from '@/stores/useAuthStore';
+import { request } from '@/api/request';
+import { Button } from '@/components/ui/button';
+import { useRouter } from 'next/navigation';
+import { useMutation } from '@tanstack/react-query';
+
+const LogoutButton = () => {
+ const { user, clearUser } = useAuthStore();
+ const router = useRouter();
+
+ const { mutate, isPending } = useMutation({
+ mutationFn: async () => {
+ await request.post(
+ '/v1/user/logout',
+ {
+ 'Content-Type': 'application/json',
+ },
+ '{}',
+ {
+ credentials: 'include',
+ },
+ );
+ },
+ onSuccess: () => {
+ clearUser();
+ router.push('/login');
+ },
+ onError: (error) => {
+ // ๋ก๊ทธ์์ ์คํจ์ฒ๋ฆฌ
+ console.error('๋ก๊ทธ์์ ์คํจ:', error);
+ },
+ });
+
+ if (!user) {
+ return null; // ๋ก๊ทธ์ธํ์ง ์์ ๊ฒฝ์ฐ ๋ฒํผ์ ๋ ๋๋งํ์ง ์์
+ }
+
+ const onClick = async () => {
+ if (isPending) return;
+ mutate();
+ };
+
+ return (
+
+ );
+};
+
+export default LogoutButton;
diff --git a/src/components/atoms/notification-item/index.tsx b/src/components/atoms/notification-item/index.tsx
new file mode 100644
index 00000000..829123b0
--- /dev/null
+++ b/src/components/atoms/notification-item/index.tsx
@@ -0,0 +1,65 @@
+import useNotificationStore from '@/stores/useNotificationStore';
+import { Notification } from '@/types/index';
+import { formatRelativeTime } from '@/utils/dateUtils';
+import { useRouter } from 'next/navigation';
+import React from 'react';
+
+const NotificationItemComponent = ({
+ notification,
+ onClose,
+}: {
+ notification: Notification;
+ onClose?: () => void;
+}) => {
+ const { setReadNotification } = useNotificationStore();
+ const router = useRouter();
+
+ const alarmClickHandler = () => {
+ // ์ฝ์ ์ฒ๋ฆฌ
+ if (!notification.isRead) {
+ setReadNotification(notification.id);
+ }
+
+ // URL์ด ์๋ ๊ฒฝ์ฐ ํด๋น ํ์ด์ง๋ก ์ด๋
+ if (notification.url) {
+ router.push(notification.url);
+ // ํ์ค๋ฒ ๋ซ๊ธฐ
+ onClose?.();
+ }
+ };
+
+ return (
+
+
+
+ {notification.message}
+
+
+ {formatRelativeTime(notification.createdAt.toString())}
+
+
+ {!notification.isRead && (
+
+
+
+ )}
+
+ );
+};
+
+export const NotificationItem = React.memo(NotificationItemComponent);
diff --git a/src/components/atoms/recommend/recommend-deadline.tsx b/src/components/atoms/recommend/recommend-deadline.tsx
new file mode 100644
index 00000000..b4ba0321
--- /dev/null
+++ b/src/components/atoms/recommend/recommend-deadline.tsx
@@ -0,0 +1,17 @@
+type DeadlineProps = {
+ text: string;
+ className?: string;
+};
+
+export const RecommendDeadline = ({ text, className }: DeadlineProps) => {
+ return (
+
+
+ ๋ง๊ฐ์ผ
+
+ {text}
+
+ );
+};
diff --git a/src/components/atoms/recommend/recommend-group-title.tsx b/src/components/atoms/recommend/recommend-group-title.tsx
new file mode 100644
index 00000000..ebd33c83
--- /dev/null
+++ b/src/components/atoms/recommend/recommend-group-title.tsx
@@ -0,0 +1,17 @@
+type RecommendGroupTitleProps = {
+ text: string;
+ className?: string;
+};
+
+export const RecommendGroupTitle = ({
+ text,
+ className,
+}: RecommendGroupTitleProps) => {
+ return (
+
+ {text}
+
+ );
+};
diff --git a/src/components/atoms/share-button/index.tsx b/src/components/atoms/share-button/index.tsx
new file mode 100644
index 00000000..1728b5fc
--- /dev/null
+++ b/src/components/atoms/share-button/index.tsx
@@ -0,0 +1,23 @@
+'use client';
+
+import { Button } from '@/components/ui/button';
+import { toast } from 'sonner';
+
+export const ShareButton = () => {
+ const shareButtonClickHandler = async () => {
+ const currentUrl = window.location.href;
+
+ try {
+ await navigator.clipboard.writeText(`${currentUrl}`);
+ toast.success('๋งํฌ๊ฐ ๋ณต์ฌ๋์์ต๋๋ค.');
+ } catch {
+ toast.error('๋งํฌ ๋ณต์ฌ์ ์คํจํ์์ต๋๋ค.');
+ }
+ };
+
+ return (
+
+ );
+};
diff --git a/src/components/atoms/star/index.tsx b/src/components/atoms/star/index.tsx
new file mode 100644
index 00000000..a6a27e67
--- /dev/null
+++ b/src/components/atoms/star/index.tsx
@@ -0,0 +1,36 @@
+type StarProps = {
+ fillPercent: number;
+ width?: number;
+ height?: number;
+ className?: string;
+};
+
+/**
+ * ๋ณ์ ์ปดํฌ๋ํธ
+ * @param fillPercent ์ฑ์์ง ๋ณ์ ํผ์ผํธ
+ * @param width ๋ณ์ ๋๋น
+ * @param height ๋ณ์ ๋์ด
+ * @param className ๋ณ์ ํด๋์ค๋ช
+ * @returns ๋ณ์ ์ปดํฌ๋ํธ
+ */
+export const Star: React.FC = ({ fillPercent, width = 50, height = 50, className }) => (
+
+);
diff --git a/src/components/atoms/thumbnail/index.tsx b/src/components/atoms/thumbnail/index.tsx
new file mode 100644
index 00000000..cbce1bbd
--- /dev/null
+++ b/src/components/atoms/thumbnail/index.tsx
@@ -0,0 +1,27 @@
+import Image from 'next/image';
+
+type ThumbnailProps = {
+ imageSrc: string;
+ alt: string;
+ width: number;
+ height: number;
+ className?: string;
+};
+export const Thumbnail = ({
+ imageSrc,
+ alt,
+ width,
+ height,
+ className,
+}: ThumbnailProps) => {
+ return (
+ //TODO:contain์ผ๋ก ํ ์ง cover๋ก ํ ์ง ๊ฒฐ์ ํด์ผํจ -> ํ๋ก์ ํธ ๋ง๋ค ๋ ์ต์ ์ฌ์ด์ฆ ์ ๊ณตํ๋ฉด ์ข์
+
+ );
+};
diff --git a/src/components/atoms/title/index.tsx b/src/components/atoms/title/index.tsx
new file mode 100644
index 00000000..7bd94376
--- /dev/null
+++ b/src/components/atoms/title/index.tsx
@@ -0,0 +1,12 @@
+type TitleProps = {
+ title: string;
+};
+
+export const Title = ({ title }: TitleProps) => {
+ return (
+
+
{title}
+
+ );
+
+};
diff --git a/src/components/atoms/write-form/categoryName.tsx b/src/components/atoms/write-form/categoryName.tsx
new file mode 100644
index 00000000..6a7cacd9
--- /dev/null
+++ b/src/components/atoms/write-form/categoryName.tsx
@@ -0,0 +1,20 @@
+import { CircleNumber } from '../circle-icon/circle-number';
+
+type CategoryNameProps = {
+ number?: number;
+ text: string;
+ className?: string;
+};
+
+export const CategoryName = ({
+ number,
+ text,
+ className,
+}: CategoryNameProps) => {
+ return (
+
+ {number &&
}
+
{text}
+
+ );
+};
diff --git a/src/components/atoms/write-form/form-label.tsx b/src/components/atoms/write-form/form-label.tsx
new file mode 100644
index 00000000..487b52f3
--- /dev/null
+++ b/src/components/atoms/write-form/form-label.tsx
@@ -0,0 +1,44 @@
+import { FormLabel } from '@/components/ui/form';
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from '@/components/ui/tooltip';
+import { CircleInfo } from '../circle-icon/circle-info';
+
+type WriteFormLabelProps = {
+ htmlFor?: string;
+ text: string;
+ className?: string;
+ info?: string;
+ isTooltipOpen?: boolean;
+};
+
+export const WriteFormLabel = ({
+ htmlFor,
+ text,
+ className,
+ info,
+ isTooltipOpen,
+}: WriteFormLabelProps) => {
+ return (
+
+
+ {text}
+
+ {info && (
+
+
+
+
+
+ {info}
+
+
+ )}
+
+ );
+};
diff --git a/src/components/button/index.tsx b/src/components/button/index.tsx
deleted file mode 100644
index 11e6e17a..00000000
--- a/src/components/button/index.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-const Button = () => {
- return ;
-};
-
-export default Button;
diff --git a/src/components/error-boundary/error-handler.tsx b/src/components/error-boundary/error-handler.tsx
new file mode 100644
index 00000000..cfb91a98
--- /dev/null
+++ b/src/components/error-boundary/error-handler.tsx
@@ -0,0 +1,43 @@
+import { ErrorFallback } from '@/components/error-fallback';
+
+interface ErrorHandlerProps {
+ error: Error | null;
+ resetErrorBoundary: () => void;
+ defaultMessage?: string; // ๊ธฐ๋ณธ ์๋ฌ ๋ฉ์์ง๋ฅผ ์ปค์คํฐ๋ง์ด์ง ํ ์ ์์
+}
+/**
+ * ์๋ฌ ๋ฐ์ ์ ์ฒ๋ฆฌํ๋ ํจ์ - fallback ์ปดํฌ๋ํธ ํ์
+ * @param param0
+ * @returns
+ */
+export const handleError = ({
+ error,
+ resetErrorBoundary,
+ defaultMessage = '๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค'
+}: ErrorHandlerProps) => {
+ if (error instanceof Error) {
+ // ๋คํธ์ํฌ ์๋ฌ
+ if (error.message.includes('Network')) {
+ return (
+
+ ๋คํธ์ํฌ ์ฐ๊ฒฐ์ ํ์ธํด์ฃผ์ธ์
+
+ );
+ }
+
+ // ์ธ์ฆ ์๋ฌ
+ if (error.message.includes('401') || error.message.toLowerCase().includes('unauthorized') || error.message.includes('refresh ๋ง๋ฃ')) {
+ return (
+
+ ๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
+
+ );
+ }
+ }
+
+ return (
+
+ {defaultMessage}
+
+ );
+};
\ No newline at end of file
diff --git a/src/components/error-boundary/index.tsx b/src/components/error-boundary/index.tsx
new file mode 100644
index 00000000..17953b50
--- /dev/null
+++ b/src/components/error-boundary/index.tsx
@@ -0,0 +1,57 @@
+'use client';
+
+/**
+ * ์๋ฌ ๋ฐ์ด๋๋ฆฌ๋ ํด๋์ค๋ก ๊ตฌํํด์ผ๋จ
+ * ์ปดํฌ๋ํธ๋ก ๊ตฌํํ๋ฉด ์ปดํฌ๋ํธ๊ฐ ๋ฆฌ๋ ๋๋ง ๋ ๋ ์๋ฌ ๋ฐ์ด๋๋ฆฌ๊ฐ ์ด๊ธฐํ๋์ง ์์
+ */
+import { Component, ReactNode } from 'react';
+
+interface Props {
+ children: ReactNode;
+ fallback: (props: { error: Error | null; resetErrorBoundary: () => void }) => ReactNode;
+ onReset?: (error: Error | null) => void;
+}
+
+interface State {
+ hasError: boolean;
+ error: Error | null;
+}
+
+export class ErrorBoundary extends Component {
+ constructor(props: Props) {
+ super(props);
+ this.state = { hasError: false, error: null }; // ์ด๊ธฐ ์ํ
+ }
+
+ // ์์ ์ปดํฌ๋ํธ์์ ์๋ฌ ๋ฐ์ ์ ์ํ ์
๋ฐ์ดํธ
+ static getDerivedStateFromError(error: Error) {
+ return { hasError: true, error };
+ }
+
+ resetErrorBoundary = () => {
+ console.log('resetErrorBoundary');
+ const {error} = this.state;
+ // ์๋ฌ ์ํ ์ด๊ธฐํ
+ this.setState({ hasError: false, error: null });
+ this.props.onReset?.(error);
+ }
+
+ componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
+ // ์ฌ๊ธฐ์ Sentry ๋ฑ ์๋ฌ ๋ก๊น
์๋น์ค ์ฐ๋ ๊ฐ๋ฅ
+ // ์ฌ์ฉํ์ง ์์ง๋ง ์ฐธ๊ณ ์ฉ์ผ๋ก ์์ฑ
+ console.error('Error caught by ErrorBoundary:', error, errorInfo);
+ }
+
+ // ์๋ฌ ๋ฐ์ ์ fallback ์ปดํฌ๋ํธ ํ์
+ render() {
+ if (this.state.hasError) {
+ return this.props.fallback({
+ error: this.state.error,
+ resetErrorBoundary: this.resetErrorBoundary
+ });
+ }
+
+ // ์๋ฌ ์๋ ๊ฒฝ์ฐ ์์ ์ปดํฌ๋ํธ ํ์
+ return this.props.children;
+ }
+}
\ No newline at end of file
diff --git a/src/components/error-fallback/index.tsx b/src/components/error-fallback/index.tsx
new file mode 100644
index 00000000..a78df166
--- /dev/null
+++ b/src/components/error-fallback/index.tsx
@@ -0,0 +1,65 @@
+'use client';
+
+import useAuthStore from '@/stores/useAuthStore';
+import { useRouter } from 'next/navigation';
+import { ReactNode } from 'react';
+import { Button } from '../ui/button';
+
+interface ErrorFallbackProps {
+ error: Error | null;
+ resetErrorBoundary: () => void;
+ children: ReactNode;
+}
+
+/**
+ * ์๋ฌ ๋ฐ์ ์ fallback๋๋ ์ปดํฌ๋ํธ
+ * @param error ์๋ฌ ๊ฐ์ฒด
+ * @param resetErrorBoundary ์๋ฌ ์ฌ์๋ ํจ์
+ * @param children ํ์ํ ๋ฉ์์ง
+ * @returns
+ */
+export const ErrorFallback = ({
+ error,
+ resetErrorBoundary,
+ children,
+}: ErrorFallbackProps) => {
+ const { clearUser } = useAuthStore();
+
+ const router = useRouter();
+
+ const handleClick = () => {
+ if (
+ error?.message.includes('401') ||
+ error?.message.toLowerCase().includes('unauthorized') ||
+ error?.message.toLowerCase().includes('refresh ๋ง๋ฃ')
+ ) {
+ clearUser();
+ console.log('401 ์๋ฌ ๋ฐ์');
+ router.push('/login'); // 401 ์๋ฌ๋ฉด ๋ก๊ทธ์ธ ํ์ด์ง๋ก ์ด๋
+ } else if (error?.message.includes('Network')) {
+ resetErrorBoundary(); // ๋คํธ์ํฌ ์๋ฌ๋ฉด ์ฌ์๋
+ } else {
+ resetErrorBoundary(); // ๊ธฐํ ์๋ฌ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์ฌ์๋
+ }
+ };
+
+ return (
+
+ {/* ์๋ฌ ๋ฉ์์ง */}
+
{children}
+ {/* ์๋ฌ ๊ฐ์ฒด๊ฐ ์กด์ฌํ๋ ๊ฒฝ์ฐ ์๋ฌ ์์ธ ๋ฉ์์ง */}
+ {/* {error &&
{error.message}} */}
+ {/* resetErrorBoundary์ด ์กด์ฌํ๋ ๊ฒฝ์ฐ ์ฌ์๋ ๋ฒํผ */}
+
+
+ );
+};
diff --git a/src/components/molecules/bookmark-card-contents/index.tsx b/src/components/molecules/bookmark-card-contents/index.tsx
new file mode 100644
index 00000000..d981fcbe
--- /dev/null
+++ b/src/components/molecules/bookmark-card-contents/index.tsx
@@ -0,0 +1,62 @@
+import { Badge } from '@/components/atoms/badge';
+import { Title } from '@/components/atoms/title';
+import { ContentInfo } from '@/components/organisms/bookmark-card';
+import { Progress } from '@/components/ui/progress';
+import { BookmarkButtonContainer } from '@/features/bookmark/components/bookmark-button-container';
+import { getPosition } from '@/types/enums';
+import { formatYearMonthDayWithDot } from '@/utils/dateUtils';
+
+type BookmarkCardContentsProps = {
+ className?: string;
+ info: ContentInfo;
+};
+
+export const BookmarkCardContents = ({
+ className,
+ info,
+}: BookmarkCardContentsProps) => {
+ const isCompleted = info.participants.length === info.maxParticipants;
+ return (
+ info && (
+
+
+
+
+
+
+ {info.position.map((position) => (
+
+ ))}
+
+
+
+
+
+
+ )
+ );
+};
diff --git a/src/components/molecules/card-image/index.tsx b/src/components/molecules/card-image/index.tsx
new file mode 100644
index 00000000..d42a6342
--- /dev/null
+++ b/src/components/molecules/card-image/index.tsx
@@ -0,0 +1,33 @@
+import { Badge } from '../../atoms/badge';
+import { Thumbnail } from '../../atoms/thumbnail';
+
+type CardImageProps = {
+ imageSrc: string;
+ alt: string;
+ width: number;
+ height: number;
+ className?: string;
+};
+export const CardImage = ({
+ imageSrc,
+ alt,
+ width,
+ height,
+ className,
+}: CardImageProps) => {
+ return (
+
+
+
+
+ );
+};
diff --git a/src/components/molecules/group-create-button/index.tsx b/src/components/molecules/group-create-button/index.tsx
new file mode 100644
index 00000000..337e52a6
--- /dev/null
+++ b/src/components/molecules/group-create-button/index.tsx
@@ -0,0 +1,39 @@
+'use client';
+
+import { LoginRequireButton } from '@/components/atoms/login-require-button';
+import { Button } from '@/components/ui/button';
+import useAuthStore from '@/stores/useAuthStore';
+import { routes } from '@/utils/routes';
+import { PlusIcon } from 'lucide-react';
+import { useRouter } from 'next/navigation';
+
+export const WriteGroupButton = () => {
+ const user = useAuthStore((state) => state.user);
+ const router = useRouter();
+
+ const writeButtonClickHandler = () => {
+ if (!user) {
+ router.push(routes.login);
+ return;
+ }
+
+ router.push(routes.write);
+ };
+
+ return (
+
+ );
+};
diff --git a/src/components/molecules/group-filter/group-filter.tsx b/src/components/molecules/group-filter/group-filter.tsx
new file mode 100644
index 00000000..86a8c79b
--- /dev/null
+++ b/src/components/molecules/group-filter/group-filter.tsx
@@ -0,0 +1,91 @@
+'use client';
+
+import Link from 'next/link';
+import { usePathname, useRouter, useSearchParams } from 'next/navigation';
+import { SearchInput } from '@/components/molecules/search-input/search-input';
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '@/components/ui/select';
+import { SelectGroup } from '@radix-ui/react-select';
+
+/**
+ * ๋ชจ์ ๋ชฉ๋ก ํํฐ ์ปดํฌ๋ํธ
+ *
+ * ์ฌ์ฉ์์ ์ฐธ๊ฐ/๋ชจ์ง ์ค์ธ ๋ชจ์ ๋ชฉ๋ก์ ํํฐ๋งํ๋ค.
+ *
+ * @returns ๋ชจ์ ๋ชฉ๋ก ํํฐ ์ปดํฌ๋ํธ
+ */
+export const GroupFilter = () => {
+ const router = useRouter();
+
+ const pathname = usePathname();
+
+ const searchParams = useSearchParams();
+
+ const type = searchParams.get('type') ?? 'study';
+
+ const order = searchParams.get('order') ?? 'latest';
+
+ const handleOrderSelectChange = (value: string) => {
+ const queryParams = new URLSearchParams(searchParams);
+ if (queryParams.has('order') && queryParams.get('order') === value) {
+ return;
+ }
+ queryParams.set('order', value);
+ router.push(`${pathname}?${queryParams.toString()}`);
+ };
+
+ return (
+
+
+
+ {[
+ {
+ label: '์คํฐ๋',
+ value: 'study',
+ },
+ {
+ label: 'ํ๋ก์ ํธ',
+ value: 'project',
+ },
+ ].map((item) => (
+ -
+
+ {item.label}
+
+
+ ))}
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/components/molecules/group/filter.tsx b/src/components/molecules/group/filter.tsx
new file mode 100644
index 00000000..7c1e34ec
--- /dev/null
+++ b/src/components/molecules/group/filter.tsx
@@ -0,0 +1,158 @@
+'use client';
+
+import { Button } from '@/components/ui/button';
+import { Popover, PopoverTrigger } from '@/components/ui/popover';
+import {
+ DEFAULT_POSITION_NAMES,
+ DEFAULT_SKILL_NAMES,
+ PositionName,
+ SkillName,
+} from '@/types';
+import { PopoverContent } from '@radix-ui/react-popover';
+import { useSearchParams } from 'next/navigation';
+
+type FilterProps = {
+ updateQueryParams: (queries: Record) => void;
+};
+
+export const Filter = ({ updateQueryParams }: FilterProps) => {
+ const searchParams = useSearchParams();
+ const selectedSkills = searchParams.get('skill')?.split(',') ?? [];
+ const selectedPositions = searchParams.get('position')?.split(',') ?? [];
+ const isSelectedSkills = selectedSkills?.length > 0;
+ const isSelectedPosition = selectedPositions?.length > 0;
+
+ const skillSelectHandler = (skill: SkillName) => {
+ if (skill === '') {
+ // ์ ์ฒด ์ ํํ ๊ฒฝ์ฐ ์ญ์
+ updateQueryParams({ skill: '' });
+ return;
+ }
+
+ if (selectedSkills.length === 0) {
+ // ์์ง ์ ํํ ๊ธฐ์ ์ด ํ๋๋ ์๋ค๋ฉด ํ์ฌ ์ ํํ ๊ธฐ์ ๋ง ์ถ๊ฐ
+ updateQueryParams({ skill: skill });
+ return;
+ }
+ if (!selectedSkills.includes(skill)) {
+ // ๊ธฐ์กด์ ์ ํ๋ ๊ธฐ์ ์ ํ์ฌ ์ ํํ ๊ธฐ์ ์ด ์๋ค๋ฉด ์ถ๊ฐ
+ updateQueryParams({ skill: [...selectedSkills, skill].join(',') });
+ return;
+ }
+
+ // ์์ ํด๋นํ์ง ์๋๋ค๋ฉด ์ด๋ฏธ ์ ํ๋์ด์๋ ๊ธฐ์ ์ ๋ ์ ํํ ๊ฒ์ด๋ฏ๋ก ์ญ์ ๋ผ์ผ ํจ
+ const nextSkill = selectedSkills.filter(
+ (selectedSkill) => selectedSkill !== skill,
+ );
+
+ updateQueryParams({
+ skill: nextSkill.join(','),
+ });
+ };
+
+ // skillSlectHandler์ ๋์ผํ ๋ก์ง
+ const positionSelectHandler = (position: PositionName) => {
+ if (position === '') {
+ updateQueryParams({ position: '' });
+ return;
+ }
+
+ if (selectedPositions.length === 0) {
+ updateQueryParams({ position: position });
+ return;
+ }
+ if (!selectedPositions.includes(position)) {
+ updateQueryParams({
+ position: [...selectedPositions, position].join(','),
+ });
+ return;
+ }
+
+ const nextSelectedPositions = selectedPositions.filter(
+ (selectedPosition) => selectedPosition !== position,
+ );
+
+ updateQueryParams({
+ position: nextSelectedPositions.join(','),
+ });
+ };
+
+ return (
+
+
+
+
+
+
+
+ {DEFAULT_SKILL_NAMES.map((skill) => (
+
+ ))}
+
+
+
+
+
+
+
+
+ {DEFAULT_POSITION_NAMES.map((position) => (
+
+ ))}
+
+
+
+ );
+};
diff --git a/src/components/molecules/group/group-card.tsx b/src/components/molecules/group/group-card.tsx
new file mode 100644
index 00000000..722192d4
--- /dev/null
+++ b/src/components/molecules/group/group-card.tsx
@@ -0,0 +1,60 @@
+'use client';
+
+import { Badge } from '@/components/atoms/badge';
+import { CloseCover } from '@/components/atoms/group/close-cover';
+import { Deadline } from '@/components/atoms/group/deadline';
+import { GroupPositions } from '@/components/atoms/group/group-positions';
+import { GroupTitle } from '@/components/atoms/group/group-title';
+import { GroupProgress } from '@/components/atoms/group/particiapant-progress';
+import { BookmarkButtonContainer } from '@/features/bookmark/components/bookmark-button-container';
+import { Group, GroupTypeName } from '@/types';
+import { formatYearMonthDayWithDot, isBeforeToday } from '@/utils/dateUtils';
+import { routes } from '@/utils/routes';
+import Link from 'next/link';
+import { GroupSkills } from '../../atoms/group/group-skills';
+
+type GroupCardProps = {
+ item: Group;
+};
+
+// TODO : ์น์
๋ณ๋ก component ๋๋๊ธฐ
+export const GroupCard = ({ item }: GroupCardProps) => {
+ const isClosed =
+ isBeforeToday(item.deadline) ||
+ item.participants.length >= item.maxParticipants;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {isClosed &&
}
+
+ );
+};
diff --git a/src/components/molecules/group/group-list.tsx b/src/components/molecules/group/group-list.tsx
new file mode 100644
index 00000000..c753898d
--- /dev/null
+++ b/src/components/molecules/group/group-list.tsx
@@ -0,0 +1,113 @@
+'use client';
+
+import { GroupCard } from '@/components/molecules/group/group-card';
+import { Empty } from '@/components/organisms/empty';
+import { useFetchInView } from '@/hooks/useFetchInView';
+import { useFetchItems } from '@/hooks/useFetchItems';
+import { Group } from '@/types';
+import { Position, Skill } from '@/types/enums';
+import flattenPages from '@/utils/flattenPages';
+import { useEffect, useMemo, useState } from 'react';
+
+enum EMPTY_INFO_MESSAGE {
+ EMPTY_INITIAL = '์์ฑ๋ ๊ทธ๋ฃน์ด ์์ต๋๋ค',
+ SEARCH = '๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค',
+ FILTER = '์กฐ๊ฑด์ ํด๋นํ๋ ๊ทธ๋ฃน์ด ์์ต๋๋ค.',
+}
+
+type GroupListProps = {
+ serverQueryParams: Record;
+};
+
+export const GroupList = ({ serverQueryParams }: GroupListProps) => {
+ const [isEmptyItems, setIsEmptyItems] = useState(true);
+ const [emptyInfoMessage, setEmptyInfoMessage] =
+ useState(null);
+
+ const queryParams = useMemo(() => {
+ return {
+ type: serverQueryParams.type ?? '',
+ skill: serverQueryParams.skill
+ ? serverQueryParams.skill
+ .split(',')
+ .map((v) => Skill[v as keyof typeof Skill])
+ .join(',')
+ : '',
+ position: serverQueryParams.position
+ ? serverQueryParams.position
+ .split(',')
+ .map((v) => Position[v as keyof typeof Position])
+ .join(',')
+ : '',
+ sort: serverQueryParams.sort ?? 'createdAt',
+ order: serverQueryParams.order ?? 'desc',
+ search: serverQueryParams.search ?? '',
+ };
+ }, [
+ serverQueryParams.type,
+ serverQueryParams.skill,
+ serverQueryParams.position,
+ serverQueryParams.sort,
+ serverQueryParams.order,
+ serverQueryParams.search,
+ ]);
+
+ const { data, fetchNextPage, hasNextPage, isLoading, isFetchingNextPage } =
+ useFetchItems({
+ url: '/v2/groups',
+ queryParams: {
+ ...queryParams,
+ size: 10,
+ },
+ });
+
+ const { ref } = useFetchInView({
+ fetchNextPage,
+ isLoading,
+ isFetchingNextPage,
+ options: {
+ rootMargin: '300px',
+ },
+ });
+
+ const items = flattenPages(data.pages);
+
+ // ๋น data ์ฒ๋ฆฌ ๋ก์ง
+ useEffect(() => {
+ if (items.length === 0) {
+ setIsEmptyItems(true);
+ if (queryParams.search) {
+ setEmptyInfoMessage(EMPTY_INFO_MESSAGE.SEARCH);
+ return;
+ } else if (
+ queryParams.type ||
+ queryParams.skill ||
+ queryParams.position
+ ) {
+ setEmptyInfoMessage(EMPTY_INFO_MESSAGE.FILTER);
+ return;
+ } else {
+ setEmptyInfoMessage(EMPTY_INFO_MESSAGE.EMPTY_INITIAL);
+ return;
+ }
+ } else {
+ setIsEmptyItems(false);
+ setEmptyInfoMessage(null);
+ }
+ }, [queryParams, items.length]);
+
+ return (
+ <>
+ {isEmptyItems && emptyInfoMessage !== null ? (
+
+ ) : (
+
+ {items.map((group) => (
+
+ ))}
+ {hasNextPage && }
+
+ )}
+ >
+ );
+};
diff --git a/src/components/molecules/group/sort-order.tsx b/src/components/molecules/group/sort-order.tsx
new file mode 100644
index 00000000..1161b86f
--- /dev/null
+++ b/src/components/molecules/group/sort-order.tsx
@@ -0,0 +1,103 @@
+'use client';
+
+import { Button } from '@/components/ui/button';
+import { Popover } from '@/components/ui/popover';
+import { GroupSort, Order } from '@/types';
+import {
+ PopoverClose,
+ PopoverContent,
+ PopoverTrigger,
+} from '@radix-ui/react-popover';
+import { useSearchParams } from 'next/navigation';
+
+type OrderProps = {
+ updateQueryParams: (queries: Record) => void;
+};
+
+export const SortOrder = ({ updateQueryParams }: OrderProps) => {
+ const searchParams = useSearchParams();
+ const selectedSort = searchParams.get('sort');
+ const selectedOrder = searchParams.get('order');
+
+ const orderOptions: {
+ name: string;
+ value: { sort: GroupSort; order: Order };
+ }[] = [
+ { name: '์์ฑ์ผ์ โผ', value: { sort: 'createdAt', order: 'desc' } },
+ { name: '์์ฑ์ผ์ โฒ', value: { sort: 'createdAt', order: 'asc' } },
+ { name: '๋ง๊ฐ์ผ์ โผ', value: { sort: 'deadline', order: 'desc' } },
+ { name: '๋ง๊ฐ์ผ์ โฒ', value: { sort: 'deadline', order: 'asc' } },
+ ];
+
+ const getSelectedOrderOptionName = () => {
+ const found = orderOptions.find(
+ (option) =>
+ option.value.sort === selectedSort &&
+ option.value.order === selectedOrder,
+ );
+
+ if (!selectedSort && !selectedOrder) {
+ // ์ด๊ธฐ๊ฐ์ sort, order๊ฐ ๋ชจ๋ ์์ผ๋ฏ๋ก ๊ธฐ๋ณธ๊ฐ ๋ฐํ
+ return '์์ฑ์ผ์ โผ';
+ }
+
+ return found?.name;
+ };
+
+ const orderSelectHandler = (option: { sort: GroupSort; order: Order }) => {
+ if (selectedSort === option.sort && selectedOrder === option.order) {
+ // ์ด๋ฏธ ์ ํ๋ ์ ๋ ฌ ์ต์
๊ณผ ํ์ฌ ์ ํํ ์ ๋ ฌ ์ต์
์ด ๊ฐ์ ๊ฒฝ์ฐ ์๋ฌด ๋์ํ์ง ์์
+ return;
+ }
+
+ // ์ด๋ฏธ ์ ํ๋ sort๋ order๊ฐ ํ์ฌ ์ ํํ sort๋ order์ ๊ฐ์ ๊ฒฝ์ฐ updateQueryParams์์ ์ ์ธํ์ฌ ํ ๊ธ๋์ง ์๊ฒ ํ๋ค
+ if (selectedSort === option.sort) {
+ if (option.order === 'desc') {
+ updateQueryParams({ order: option.order, cursor: 'null' });
+ return;
+ }
+ updateQueryParams({ order: option.order });
+ return;
+ }
+ if (selectedOrder === option.order) {
+ if (option.order === 'desc') {
+ updateQueryParams({ sort: option.sort, cursor: 'null' });
+ return;
+ }
+ updateQueryParams({ sort: option.sort });
+ return;
+ }
+
+ if (option.order === 'desc') {
+ updateQueryParams({
+ sort: option.sort,
+ order: option.order,
+ cursor: 'null',
+ });
+ return;
+ }
+ // ์ด๋ฏธ ์ ํ๋ ์ ๋ ฌ ์ต์
๊ณผ ์์ ๋ค๋ฅธ ๊ฒฝ์ฐ updateQueryParams์์ ๋ชจ๋ ์
๋ฐ์ดํธ ํ๋ค
+ updateQueryParams({ sort: option.sort, order: option.order });
+ };
+
+ return (
+
+
+
+
+
+ {orderOptions.map((option) => (
+ orderSelectHandler(option.value)}
+ className="p-1 cursor-pointer"
+ >
+ {option.name}
+
+ ))}
+
+
+ );
+};
diff --git a/src/components/molecules/input-checkbox-field/index.tsx b/src/components/molecules/input-checkbox-field/index.tsx
new file mode 100644
index 00000000..097227d2
--- /dev/null
+++ b/src/components/molecules/input-checkbox-field/index.tsx
@@ -0,0 +1,82 @@
+// components/molecules/FormCheckboxGroupField.tsx
+
+import {
+ ControllerRenderProps,
+ FieldPath,
+ FieldValues,
+ UseFormReturn,
+} from 'react-hook-form';
+import {
+ FormControl,
+ FormDescription,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from '@/components/ui/form';
+import { Checkbox } from '@/components/ui/checkbox';
+
+interface FormCheckboxGroupFieldProps {
+ form: UseFormReturn;
+ name: FieldPath;
+ label: string;
+ description?: string;
+ options: string[];
+}
+
+export const FormCheckboxGroupField = ({
+ form,
+ name,
+ label,
+ description,
+ options,
+}: FormCheckboxGroupFieldProps) => {
+ return (
+
+
+ {label}
+ {description && {description}}
+
+
+ {options.map((option) => (
+ >;
+ }) => {
+ return (
+
+
+ {
+ return checked
+ ? field.onChange([...field.value, option])
+ : field.onChange(
+ field.value?.filter((v: string) => v !== option),
+ );
+ }}
+ />
+
+
+ {option}
+
+
+ );
+ }}
+ />
+ ))}
+
+
+
+ );
+};
diff --git a/src/components/molecules/input-radiogroup-field/index.tsx b/src/components/molecules/input-radiogroup-field/index.tsx
new file mode 100644
index 00000000..26677306
--- /dev/null
+++ b/src/components/molecules/input-radiogroup-field/index.tsx
@@ -0,0 +1,70 @@
+import {
+ ControllerRenderProps,
+ FieldPath,
+ FieldValues,
+ UseFormReturn,
+} from 'react-hook-form';
+import {
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from '@/components/ui/form';
+import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
+
+interface FormRadioGroupFieldProps {
+ form: UseFormReturn;
+ name: FieldPath;
+ label: string;
+ options: string[];
+}
+
+export const FormRadioGroupField = ({
+ form,
+ name,
+ label,
+ options,
+}: FormRadioGroupFieldProps) => {
+ return (
+ >;
+ }) => (
+
+ {label}
+
+
+ {options.map((option) => (
+
+
+
+
+
+ {option}
+
+
+ ))}
+
+
+
+
+ )}
+ />
+ );
+};
diff --git a/src/components/molecules/input-select-field/index.tsx b/src/components/molecules/input-select-field/index.tsx
new file mode 100644
index 00000000..7b78fc53
--- /dev/null
+++ b/src/components/molecules/input-select-field/index.tsx
@@ -0,0 +1,63 @@
+import {
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from '@/components/ui/form';
+import { FieldPath, FieldValues, UseFormReturn } from 'react-hook-form';
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '@/components/ui/select';
+
+type InputSelectFieldProps = {
+ form: UseFormReturn;
+ name: FieldPath;
+ label: string;
+ placeholder: string;
+ options: {
+ value: string;
+ label: string;
+ }[];
+ selectTriggerClassName?: string;
+};
+
+export const InputSelectField = ({
+ form,
+ name,
+ label,
+ placeholder,
+ options,
+ selectTriggerClassName,
+}: InputSelectFieldProps) => {
+ return (
+ (
+
+ {label}
+
+
+
+ )}
+ />
+ );
+};
diff --git a/src/components/molecules/input-text-field/index.tsx b/src/components/molecules/input-text-field/index.tsx
new file mode 100644
index 00000000..24b84f67
--- /dev/null
+++ b/src/components/molecules/input-text-field/index.tsx
@@ -0,0 +1,41 @@
+import {
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from '@/components/ui/form';
+import { InputText } from '@/components/atoms/input-text';
+import { FieldPath, FieldValues, UseFormReturn } from 'react-hook-form';
+
+type InputTextFieldProps = {
+ form: UseFormReturn; // ์ ๋ค๋ฆญ์ผ๋ก ํ์ฅ ๊ฐ๋ฅ
+ name: FieldPath;
+ label: string;
+ placeholder?: string;
+ type?: 'text' | 'password' | 'search' | 'email' | 'url' | 'tel' | 'number';
+};
+
+export const InputTextField = ({
+ label,
+ type = 'text',
+ name,
+ form,
+ placeholder,
+}: InputTextFieldProps) => {
+ return (
+ (
+
+ {label}
+
+
+
+
+
+ )}
+ />
+ );
+};
diff --git a/src/components/molecules/notification-badge/index.tsx b/src/components/molecules/notification-badge/index.tsx
new file mode 100644
index 00000000..d5034d54
--- /dev/null
+++ b/src/components/molecules/notification-badge/index.tsx
@@ -0,0 +1,11 @@
+import { Badge } from '@/components/ui/badge';
+
+/**
+ * ์ ์ฝ์ ์๋์ด ์๋ ๊ฒฝ์ฐ ํ์ฑํ -> ์ ์ฝ์ ์๋๊ฐ์ ์
๋ฐ์ดํธ ํ์ํ ๊ฒฝ์ฐ ์ถ๊ฐ ํ์
+ * @returns
+ */
+export const NotificationBadge = () => {
+ return (
+
+ )
+};
diff --git a/src/components/molecules/notification-list/index.tsx b/src/components/molecules/notification-list/index.tsx
new file mode 100644
index 00000000..2c0bb10d
--- /dev/null
+++ b/src/components/molecules/notification-list/index.tsx
@@ -0,0 +1,165 @@
+import { request } from '@/api/request';
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from '@/components/ui/popover';
+import { useFetchInView } from '@/hooks/useFetchInView';
+import { useFetchItems } from '@/hooks/useFetchItems';
+import useAuthStore from '@/stores/useAuthStore';
+import useNotificationStore from '@/stores/useNotificationStore';
+import { Notification as NotificationType } from '@/types/index';
+import { useQuery } from '@tanstack/react-query';
+import Image from 'next/image';
+import { useEffect, useState } from 'react';
+import { z } from 'zod';
+import { NotificationItem } from '../../atoms/notification-item';
+
+const NotificationPageSchema = z.object({
+ status: z.object({
+ success: z.boolean(),
+ }),
+ notifications: z.object({
+ hasNext: z.boolean(),
+ cursor: z.number().nullable(),
+ items: z.array(z.any()),
+ }),
+});
+
+export const NotificationList = () => {
+ const [isOpen, setIsOpen] = useState(false);
+ const { setNotifications, setUnreadCount } = useNotificationStore();
+ const unreadCount = useNotificationStore((state) => state.unreadCount);
+ const notifications = useNotificationStore((state) => state.notifications);
+ const user = useAuthStore((state) => state.user);
+
+ // ์ ์ฒด ์๋ฆผ ๋ชฉ๋ก ์กฐํ
+ // TODO: ๋ฐฑ์๋ ๋ฐ์ดํฐ ํ์
๊ณ ์ณ์ผ๋จ - ํ์ ๋๋๊ณ ๊ณ ์น ์์
+ const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
+ useFetchItems({
+ url: '/v1/notification',
+ getNextPageParam: (lastPage) => {
+ const page = NotificationPageSchema.parse(lastPage);
+ return page.notifications.hasNext ? page.notifications.cursor : null;
+ },
+ });
+
+ // ์ ์ฝ์ ์๋ฆผ ๋ชฉ๋ก ์กฐํ
+ const { data: unreadData, isLoading: isUnreadLoading } = useQuery<{
+ unreadCount: number;
+ }>({
+ queryKey: ['unread-count'],
+ queryFn: async () => {
+ return await request.get(
+ '/v1/notification/unread-count',
+ {},
+ {
+ credentials: 'include',
+ },
+ );
+ },
+ });
+
+ const { ref } = useFetchInView({
+ fetchNextPage,
+ isLoading: isUnreadLoading,
+ isFetchingNextPage,
+ });
+
+ const normalizeNotification = (
+ item: Record,
+ ): NotificationType => {
+ const { content, created_at, createdAt, isRead, read, message, ...rest } =
+ item as Partial;
+ return {
+ ...rest,
+ message: content || message,
+ isRead: !!(isRead || read),
+ createdAt: createdAt || created_at,
+ } as NotificationType;
+ };
+
+ useEffect(() => {
+ if (!data) return;
+
+ const allPages = data.pages.map((page) => {
+ const result = NotificationPageSchema.safeParse(page);
+ if (result.success) {
+ return result.data.notifications.items ?? [];
+ }
+ return [];
+ });
+
+ const newItems = allPages.flat().map(normalizeNotification);
+ setNotifications(newItems);
+ // eslint-disable-next-line
+ }, [data]);
+
+ // ์ ์ฝ์ ์๋ ์
๋ฐ์ดํธ
+ useEffect(() => {
+ if (!unreadData) return;
+ setUnreadCount(Number(unreadData.unreadCount) || 0);
+ }, [unreadData, setUnreadCount]);
+
+ const openhandler = (open: boolean) => {
+ setIsOpen(open);
+ };
+
+ const renderNotificationItems = () =>
+ notifications.map(
+ (notification: NotificationType, idx: number) =>
+ notification && (
+
+ setIsOpen(false)}
+ />
+
+ ),
+ );
+
+ if (!user) return null;
+
+ return (
+
+
+
+ 0 ? 'active' : 'default'}.svg`}
+ alt="์๋ฆผ"
+ width={24}
+ height={24}
+ className="opacity-70"
+ />
+
+
+
+
+
+
์๋ฆผ
+
+
+ {notifications.length ? (
+ renderNotificationItems()
+ ) : (
+
+
+
์๋ฆผ์ด ์์ต๋๋ค
+
+ )}
+ {hasNextPage &&
}
+
+
+
+
+ );
+};
diff --git a/src/components/molecules/notification-list/notification-list.test.tsx b/src/components/molecules/notification-list/notification-list.test.tsx
new file mode 100644
index 00000000..39fafa45
--- /dev/null
+++ b/src/components/molecules/notification-list/notification-list.test.tsx
@@ -0,0 +1,80 @@
+import { server } from '@/mocks/server';
+import useAuthStore from '@/stores/useAuthStore';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { render, screen, waitFor } from '@testing-library/react';
+import { http, HttpResponse } from 'msw';
+import { Suspense } from 'react';
+import { NotificationList } from '.';
+
+const createTestQueryClient = () =>
+ new QueryClient({
+ defaultOptions: {
+ queries: {
+ retry: false,
+ },
+ },
+ });
+
+const renderWithClient = (ui: React.ReactElement) => {
+ const queryClient = createTestQueryClient();
+ return render(
+
+ Loading...}>{ui}
+ ,
+ );
+};
+
+const mockUser = {
+ userId: 1,
+ email: 'test@test.com',
+ nickname: 'Test User',
+ profileImage: null,
+ position: null,
+ skills: null,
+ isFollowing: false,
+ isFollower: false,
+ rate: 0,
+};
+
+describe('์๋ ๋ชฉ๋ก ์ปดํฌ๋ํธ ํ
์คํธ', () => {
+ beforeAll(() => {
+ server.listen();
+ useAuthStore.setState({ user: mockUser });
+ });
+
+ afterEach(() => {
+ server.resetHandlers();
+ });
+
+ afterAll(() => server.close());
+
+ it('์ ์ฝ์ ์๋์ด 1๊ฐ ์ด์์ธ ๊ฒฝ์ฐ badge๊ฐ ํ์๋๋ค', async () => {
+ // Set cookie before making requests
+ document.cookie = 'accessToken=test-token';
+
+ server.use(
+ http.get('/api/notification', () => {
+ return HttpResponse.json({ items: [] });
+ }),
+
+ http.get('/api/notification/unread-count', () => {
+ return HttpResponse.json({ unreadCount: 1 });
+ }),
+ );
+
+ renderWithClient();
+
+ const badge = await screen.findByRole('status');
+ expect(badge).toBeInTheDocument();
+ });
+
+ it('๋ก๊ทธ์ธ์ด ์๋ ๊ฒฝ์ฐ list๊ฐ ๋ ๋๋ง ๋์ง ์๋๋ค', async () => {
+ useAuthStore.setState({ user: null });
+ renderWithClient();
+
+ //๋น๋๊ธฐ ์ปดํฌ๋ํธ๋ผ wait
+ await waitFor(() => {
+ expect(screen.queryByText('Notification')).not.toBeInTheDocument();
+ });
+ });
+});
diff --git a/src/components/molecules/participant-card/index.tsx b/src/components/molecules/participant-card/index.tsx
new file mode 100644
index 00000000..12b00a0f
--- /dev/null
+++ b/src/components/molecules/participant-card/index.tsx
@@ -0,0 +1,21 @@
+import { Avatar } from '@/components/atoms/avatar';
+import { UserSummary } from '@/types';
+import { getDisplayNickname, getDisplayProfileImage } from '@/utils/fallback';
+import Link from 'next/link';
+
+export const ParticipantCard = ({
+ userId,
+ nickname,
+ profileImage,
+ email,
+}: UserSummary) => {
+ return (
+
+
+ {getDisplayNickname(nickname, email)}
+
+ );
+};
diff --git a/src/components/molecules/position-badge/index.tsx b/src/components/molecules/position-badge/index.tsx
new file mode 100644
index 00000000..53bb2d63
--- /dev/null
+++ b/src/components/molecules/position-badge/index.tsx
@@ -0,0 +1,67 @@
+'use client';
+
+import { Badge } from '@/components/atoms/badge';
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from '@/components/ui/tooltip';
+import { Position } from '@/types/enums';
+
+/** ์ซ์ enum Skill์ ํค ์ด๋ฆ์ผ๋ก ์ด๋ฏธ์ง ์ฃผ์์ ๋งค์นญ ์ํค๊ธฐ */
+export const allPositionlKeys = Object.keys(Position).filter((key) =>
+ isNaN(Number(key)),
+);
+
+const positionWithDescription: Record<
+ (typeof allPositionlKeys)[number],
+ string
+> = {
+ PM: 'ํ๋ก์ ํธ ๋งค๋์ ',
+ PL: 'ํํธ ๋ฆฌ๋',
+ AA: '๊ฐ๋ฐ ์ค๊ณ ๋ด๋น์',
+ TA: '๊ธฐ์ ๋ด๋น์',
+ DA: '๋ฐ์ดํฐ ๋ถ์๊ฐ',
+ QA: 'ํ์ง ๋ณด์ฆ ๋ด๋น์',
+ FE: 'ํ๋ก ํธ์๋ ๊ฐ๋ฐ์',
+ BE: '๋ฐฑ์๋ ๊ฐ๋ฐ์',
+ FS: 'ํ์คํ ๊ฐ๋ฐ์',
+};
+
+const positionLogoMap: { [key: string]: string } = {};
+
+allPositionlKeys.forEach((position) => {
+ positionLogoMap[position] = `/logos/${position.replace(
+ /[^a-zA-Z0-9]/g,
+ '',
+ )}.png`;
+});
+
+type PositionBadgeProps = {
+ name: string;
+ positionClickHandler?: (name: string) => void;
+};
+
+export const PositionBadge = ({
+ name,
+ positionClickHandler,
+}: PositionBadgeProps) => {
+ return (
+
+
+ positionClickHandler?.(name)}
+ >
+
+
+
+
+ {positionWithDescription[name]}
+
+
+ );
+};
diff --git a/src/components/molecules/recommend-group/recommend-group-card.tsx b/src/components/molecules/recommend-group/recommend-group-card.tsx
new file mode 100644
index 00000000..b87e4837
--- /dev/null
+++ b/src/components/molecules/recommend-group/recommend-group-card.tsx
@@ -0,0 +1,41 @@
+import { Badge } from '@/components/atoms/badge';
+import { HalfRoundBadge } from '@/components/atoms/badge/half-round-badge';
+import { RecommendDeadline } from '@/components/atoms/recommend/recommend-deadline';
+import { RecommendGroupTitle } from '@/components/atoms/recommend/recommend-group-title';
+import { Group, GroupTypeName } from '@/types';
+import {
+ formatRelativeTime,
+ formatYearMonthDayWithDot,
+} from '@/utils/dateUtils';
+import { routes } from '@/utils/routes';
+import Link from 'next/link';
+
+type RecommendGroupCardProps = {
+ item: Group;
+};
+
+// TODO : ์น์
๋ณ๋ก component ๋๋๊ธฐ
+export const RecommendGroupCard = ({ item }: RecommendGroupCardProps) => {
+ return (
+
+ );
+};
diff --git a/src/components/molecules/search-input/search-input.tsx b/src/components/molecules/search-input/search-input.tsx
new file mode 100644
index 00000000..35756547
--- /dev/null
+++ b/src/components/molecules/search-input/search-input.tsx
@@ -0,0 +1,81 @@
+'use client';
+
+import { Input } from '@/components/ui/input';
+import { usePathname, useRouter, useSearchParams } from 'next/navigation';
+import { ComponentProps, useEffect, useRef } from 'react';
+import { cn } from '@/lib/utils';
+import Image from 'next/image';
+
+type SearchInputProps = {
+ inputClassName?: string;
+ containerClassName?: string;
+};
+
+/**
+ * ์ฌ์ฉ์ ์ด๋ฆ, ๋ชจ์ ๋ช
์ ๊ฒ์ํ๊ธฐ ์ํ ์ปดํฌ๋ํธ
+ *
+ * @param props ๊ธฐ๋ณธ input ์์์ props
+ * @returns ๊ฒ์ ์ธํ ์ปดํฌ๋ํธ
+ */
+export const SearchInput = ({
+ inputClassName,
+ containerClassName,
+ ...props
+}: ComponentProps<'input'> & SearchInputProps) => {
+ const router = useRouter();
+ const pathname = usePathname();
+ const searchParams = useSearchParams();
+ const inputRef = useRef(null);
+
+ // search ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๊ฐ ๋ณ๊ฒฝ๋๋ฉด, ์ธํ ์์์ ๊ฐ์ ๋ณ๊ฒฝํ๋ค.
+ useEffect(() => {
+ if (inputRef.current) {
+ inputRef.current.value = searchParams.get('search') ?? '';
+ }
+ }, [searchParams]);
+
+ /**
+ * ์ํฐ ํค๊ฐ ๋๋ฆฌ๋ฉด, ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๊ฐ ๋ณ๊ฒฝ๋ ํ์ด์ง๋ก ์ด๋ํ๋ค.
+ *
+ * ์ธํ ์์์ ๊ฐ์ด ๋น ๋ฌธ์์ด์ธ ๊ฒฝ์ฐ, search ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ฅผ ์ญ์ ํ๋ค.
+ * ์ธํ ์์์ ๊ฐ์ด ๋น ๋ฌธ์์ด์ด ์๋ ๊ฒฝ์ฐ, search ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ ๊ฐ์ ๋ณ๊ฒฝํ๋ค.
+ *
+ * @param e keyDown ์ด๋ฒคํธ
+ */
+ const keyDownHandler: React.KeyboardEventHandler = (e) => {
+ if (e.nativeEvent.isComposing) return;
+
+ if (e.key === 'Enter') {
+ if (inputRef.current) {
+ const queryParams = new URLSearchParams(searchParams);
+ const search = inputRef.current.value;
+ if (search) {
+ queryParams.set('search', search);
+ } else {
+ queryParams.delete('search');
+ }
+ router.push(`${pathname}?${queryParams.toString()}`);
+ }
+ }
+ };
+
+ return (
+
+
+
+
+ );
+};
diff --git a/src/components/molecules/skill-badge/index.tsx b/src/components/molecules/skill-badge/index.tsx
new file mode 100644
index 00000000..b839fd3f
--- /dev/null
+++ b/src/components/molecules/skill-badge/index.tsx
@@ -0,0 +1,48 @@
+'use client';
+
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from '@/components/ui/tooltip';
+import { SkillName } from '@/types';
+import { Skill } from '@/types/enums';
+import Image from 'next/image';
+
+/** ์ซ์ enum Skill์ ํค ์ด๋ฆ์ผ๋ก ์ด๋ฏธ์ง ์ฃผ์์ ๋งค์นญ ์ํค๊ธฐ */
+export const allSkillKeys = Object.keys(Skill).filter((key) =>
+ isNaN(Number(key)),
+);
+
+const skillLogoMap: { [key: string]: string } = {};
+
+allSkillKeys.forEach((skill) => {
+ skillLogoMap[skill] = `/logos/${skill.replace(/[^a-zA-Z0-9]/g, '')}.png`;
+});
+
+type SkillBadgeProps = {
+ name: SkillName;
+ isDefault?: boolean;
+ skillClickHandler?: (name: SkillName) => void;
+};
+
+export const SkillBadge = ({
+ name,
+ isDefault = true,
+ skillClickHandler,
+}: SkillBadgeProps) => {
+ return (
+
+
+ skillClickHandler?.(name)}>
+ {isDefault && (
+
+ )}
+
+
+
+ {name}
+
+
+ );
+};
diff --git a/src/components/molecules/star-rating/index.tsx b/src/components/molecules/star-rating/index.tsx
new file mode 100644
index 00000000..f976717b
--- /dev/null
+++ b/src/components/molecules/star-rating/index.tsx
@@ -0,0 +1,96 @@
+import { Star } from '@/components/atoms/star';
+import React, { useRef, useState } from 'react';
+
+interface StarRatingProps {
+ initialRating?: number;
+ onRatingChange?: (rating: number) => void;
+ readOnly?: boolean;
+ maxRating?: number;
+}
+
+/**
+ * ๋ณ์ ์ปดํฌ๋ํธ 5์ ๊ธฐ์ค
+ * @param initialRating ์ด๊ธฐ ๋ณ์
+ * @param onRatingChange ๋ณ์ ๋ณ๊ฒฝ ์ ์ฝ๋ฐฑ
+ * @param readOnly ์ฝ๊ธฐ ์ ์ฉ ์ฌ๋ถ
+ * @param maxRating ์ต๋ ๋ณ์
+ * @returns ๋ณ์ ์ปดํฌ๋ํธ
+ */
+export const StarRating: React.FC = ({
+ initialRating = 0,
+ onRatingChange,
+ readOnly = false,
+ maxRating = 5,
+}) => {
+ const [rating, setRating] = useState(initialRating);
+ const [isDragging, setIsDragging] = useState(false);
+ const containerRef = useRef(null); //๋ณ์ ์ปจํ
์ด๋
+
+ const calculateRating = (clientX: number) => {
+ if (!containerRef.current) return undefined;
+
+ const { left, width } = containerRef.current.getBoundingClientRect(); //๋ณ์ ์ปจํ
์ด๋์ ์์น์ ๋๋น๋ฅผ ๊ฐ์ ธ์ด, width๋ ๋ณ์ ์ปจํ
์ด๋์ ๋๋น
+ const x = Math.max(0, Math.min(width, clientX - left)); //ํ์ฌ ๋ง์ฐ์ค ์์น๊ฐ 0~100
+ return Math.round((x / width) * maxRating * 10) / 10; //๋ณ์ ์ปจํ
์ด๋์ ๋๋น์ ๋ฐ๋ฅธ ๋ณ์ ๊ณ์ฐ
+ };
+
+ const mouseDownHandler = (e: React.MouseEvent) => {
+ setIsDragging(true);
+ const newRating = calculateRating(e.clientX);
+ if (newRating !== undefined) {
+ setRating(Math.min(maxRating, Math.max(0, newRating)));
+ onRatingChange?.(Math.min(maxRating, Math.max(0, newRating)));
+ }
+ };
+
+ const mouseMoveHandler = (e: React.MouseEvent) => {
+ if (isDragging) {
+ const newRating = calculateRating(e.clientX);
+ if (newRating !== undefined) {
+ setRating(Math.min(maxRating, Math.max(0, newRating)));
+ onRatingChange?.(Math.min(maxRating, Math.max(0, newRating)));
+ }
+ }
+ };
+
+ const mouseUpHandler = () => {
+ setIsDragging(false);
+ };
+
+ const mouseLeaveHandler = () => {
+ setIsDragging(false);
+ };
+
+ const eventHandlers = readOnly
+ ? {}
+ : {
+ onMouseDown: mouseDownHandler,
+ onMouseMove: mouseMoveHandler,
+ onMouseUp: mouseUpHandler,
+ onMouseLeave: mouseLeaveHandler,
+ };
+
+ return (
+
+
+ {[...Array(maxRating)].map((_, i) => {
+ const fillPercent = (() => {
+ if (rating <= i) return 0;
+ if (rating >= i + 1) return 100;
+ return (rating - i) * 100;
+ })();
+ return ;
+ })}
+
+
+ {rating.toFixed(1)} / {maxRating}
+
+
+ );
+};
diff --git a/src/components/molecules/tab/index.tsx b/src/components/molecules/tab/index.tsx
new file mode 100644
index 00000000..a9e68e2f
--- /dev/null
+++ b/src/components/molecules/tab/index.tsx
@@ -0,0 +1,42 @@
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
+import { GroupType } from '@/types';
+
+export type TabType = {
+ value: GroupType | ''; // ์ ์ฒด ์ ํ์ ''๋ก ํ๊ธฐํ๋ฉฐ, ํ์
์ด๋ฆ ์์ด ๋ชจ๋ ๊ทธ๋ฃน์ ํฌํจ
+ label: string;
+};
+
+export const Tab = ({
+ tabList,
+ children,
+ onValueChange,
+}: {
+ tabList: TabType[];
+ children: React.ReactNode;
+ onValueChange: (value: GroupType) => void;
+}) => {
+ const handleValueChange = (value: string) => {
+ onValueChange(value as GroupType);
+ };
+
+ return (
+
+
+ {tabList.map((tab) => (
+
+ {tab.label}
+
+ ))}
+
+ {tabList.map((tab) => (
+
+ {children}
+
+ ))}
+
+ );
+};
diff --git a/src/components/molecules/write-form/autoAllow.tsx b/src/components/molecules/write-form/autoAllow.tsx
new file mode 100644
index 00000000..71da8e60
--- /dev/null
+++ b/src/components/molecules/write-form/autoAllow.tsx
@@ -0,0 +1,34 @@
+import { WriteFormLabel } from '@/components/atoms/write-form/form-label';
+import { Checkbox } from '@/components/ui/checkbox';
+import { FormControl, FormField, FormItem } from '@/components/ui/form';
+import { WriteForm } from '@/types';
+import { UseFormReturn } from 'react-hook-form';
+
+type TitleProps = {
+ form: UseFormReturn;
+};
+
+export const AutoAllow = ({ form }: TitleProps) => {
+ return (
+ <>
+ (
+
+
+ {
+ /** ํด๋ฆญ ์ field์ ๊ฐ์ ๋ฐ์์ ํ ๊ธ */
+ form.setValue('autoAllow', !field.field.value);
+ }}
+ />
+
+
+
+ )}
+ />
+ >
+ );
+};
diff --git a/src/components/molecules/write-form/deadlineCalendar.tsx b/src/components/molecules/write-form/deadlineCalendar.tsx
new file mode 100644
index 00000000..4b14c1c9
--- /dev/null
+++ b/src/components/molecules/write-form/deadlineCalendar.tsx
@@ -0,0 +1,119 @@
+import { WriteFormLabel } from '@/components/atoms/write-form/form-label';
+import { Button } from '@/components/ui/button';
+import { Calendar } from '@/components/ui/calendar';
+import {
+ FormControl,
+ FormField,
+ FormItem,
+ FormMessage,
+} from '@/components/ui/form';
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from '@/components/ui/popover';
+import { WriteForm } from '@/types';
+import { formatYearMonthDayWithDot } from '@/utils/dateUtils';
+import clsx from 'clsx';
+import { CalendarIcon } from 'lucide-react';
+import { UseFormReturn } from 'react-hook-form';
+
+type TitleProps = {
+ form: UseFormReturn;
+ isDeadlineCalendarOpen: boolean;
+ deadlineSelect: (date: Date) => void;
+ openDeadlineCalendar: () => void;
+ closeDeadlineCalendar: () => void;
+ validDeadline: Date;
+};
+
+export const DeadlineCalendar = ({
+ form,
+ isDeadlineCalendarOpen,
+ deadlineSelect,
+ openDeadlineCalendar,
+ closeDeadlineCalendar,
+ validDeadline,
+}: TitleProps) => {
+ const hasError = !!form.formState.errors.deadline;
+ /** calendar์์ ๋ ์ง ์ ํ ํ calendar๊ฐ ๋ซํ๊ฒ ํ๊ธฐ ์ํ ํจ์ */
+ const deadlineSelectHandler = (
+ date: Date | undefined,
+ onChange: (date: Date | undefined) => void,
+ ) => {
+ if (!date) return;
+
+ deadlineSelect(date);
+ onChange(date);
+ closeDeadlineCalendar();
+ };
+
+ return (
+ <>
+ (
+
+
+
+
+
+ {field.value ? (
+ formatYearMonthDayWithDot(field.value)
+ ) : (
+ <>
+
+ {formatYearMonthDayWithDot(validDeadline)}
+
+ >
+ )}
+
+
+
+
+
+
+
+
+ {
+ deadlineSelectHandler(e, field.onChange);
+ }}
+ disabled={{ before: validDeadline }}
+ />
+
+
+
+ {fieldState.error?.message}
+
+ )}
+ />
+ >
+ );
+};
diff --git a/src/components/molecules/write-form/endDateCalendar.tsx b/src/components/molecules/write-form/endDateCalendar.tsx
new file mode 100644
index 00000000..435d8e0a
--- /dev/null
+++ b/src/components/molecules/write-form/endDateCalendar.tsx
@@ -0,0 +1,117 @@
+import { WriteFormLabel } from '@/components/atoms/write-form/form-label';
+import { Button } from '@/components/ui/button';
+import { Calendar } from '@/components/ui/calendar';
+import {
+ FormControl,
+ FormField,
+ FormItem,
+ FormMessage,
+} from '@/components/ui/form';
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from '@/components/ui/popover';
+import { WriteForm } from '@/types';
+import { formatYearMonthDayWithDot } from '@/utils/dateUtils';
+import clsx from 'clsx';
+import { CalendarIcon } from 'lucide-react';
+import { UseFormReturn } from 'react-hook-form';
+
+type TitleProps = {
+ form: UseFormReturn;
+ isEndDateCalendarOpen: boolean;
+ openEndDateCalendar: () => void;
+ closeEndDateCalendar: () => void;
+ validEndDate: Date;
+};
+
+export const EndDateCalendar = ({
+ form,
+ isEndDateCalendarOpen,
+ openEndDateCalendar,
+ closeEndDateCalendar,
+ validEndDate,
+}: TitleProps) => {
+ const hasError = !!form.formState.errors.endDate;
+
+ /** calendar์์ ๋ ์ง ์ ํ ํ calendar๊ฐ ๋ซํ๊ฒ ํ๊ธฐ ์ํ ํจ์ */
+ const endDateSelect = (
+ date: Date | undefined,
+ onChange: (date: Date | undefined) => void,
+ ) => {
+ if (!date) return;
+
+ onChange(date);
+ closeEndDateCalendar();
+ };
+
+ return (
+ <>
+ (
+
+
+
+
+
+ {field.value ? (
+ formatYearMonthDayWithDot(field.value)
+ ) : (
+ <>
+
+ {formatYearMonthDayWithDot(validEndDate)}
+
+ >
+ )}
+
+
+
+
+
+
+
+
+ {
+ endDateSelect(e, field.onChange);
+ }}
+ disabled={{ before: validEndDate }}
+ />
+
+
+
+ {fieldState.error?.message}
+
+ )}
+ />
+ >
+ );
+};
diff --git a/src/components/molecules/write-form/maxParticipants.tsx b/src/components/molecules/write-form/maxParticipants.tsx
new file mode 100644
index 00000000..ed237f28
--- /dev/null
+++ b/src/components/molecules/write-form/maxParticipants.tsx
@@ -0,0 +1,84 @@
+import { WriteFormLabel } from '@/components/atoms/write-form/form-label';
+import {
+ FormControl,
+ FormField,
+ FormItem,
+ FormMessage,
+} from '@/components/ui/form';
+import { Input } from '@/components/ui/input';
+import { WriteForm } from '@/types';
+import { useEffect, useState } from 'react';
+import { UseFormReturn } from 'react-hook-form';
+
+type TitleProps = {
+ form: UseFormReturn;
+};
+
+export const MaxParticipants = ({ form }: TitleProps) => {
+ const [isTooltipOpen, setIsTooltipOpen] = useState(false);
+
+ const inputBlurHandler = (e: React.ChangeEvent) => {
+ const value = Number(e.target.value);
+
+ if (value === 0) {
+ // ์
๋ ฅ๊ฐ์ด ์๊ฑฐ๋ 0์ด๋ฉด 2๋ก ์ธํ
+ form.setValue('maxParticipants', 2);
+ return;
+ }
+
+ if (value < 2 || value > 30) {
+ const settedValue = Math.max(2, Math.min(30, value));
+ form.setValue('maxParticipants', settedValue);
+
+ setIsTooltipOpen(true);
+ }
+ };
+
+ /**
+ * ์ ์ ๋ณด์ ํ ํดํ์ด 3์ด๋ค์ ์ฌ๋ผ์ง๊ฒ ํ๊ธฐ
+ */
+
+ useEffect(() => {
+ if (isTooltipOpen) {
+ const timer = setTimeout(() => {
+ setIsTooltipOpen(false);
+ }, 3000); // 3์ด
+
+ return () => clearTimeout(timer);
+ }
+ }, [isTooltipOpen]);
+
+ return (
+ <>
+ (
+
+
+
+ {
+ inputBlurHandler(e);
+ field.onBlur();
+ }}
+ />
+
+
+
+ )}
+ />
+ >
+ );
+};
diff --git a/src/components/molecules/write-form/selcetType.tsx b/src/components/molecules/write-form/selcetType.tsx
new file mode 100644
index 00000000..7da44fcf
--- /dev/null
+++ b/src/components/molecules/write-form/selcetType.tsx
@@ -0,0 +1,56 @@
+import { WriteFormLabel } from '@/components/atoms/write-form/form-label';
+import {
+ FormControl,
+ FormField,
+ FormItem,
+ FormMessage,
+} from '@/components/ui/form';
+import { Label } from '@/components/ui/label';
+import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
+import { GroupType, WriteForm } from '@/types';
+import { UseFormReturn } from 'react-hook-form';
+
+type TitleProps = {
+ form: UseFormReturn;
+};
+
+const ItemWrap = ({ children }: { children: React.ReactNode }) => {
+ return {children}
;
+};
+
+export const SelectType = ({ form }: TitleProps) => {
+ return (
+ <>
+ (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+ />
+ >
+ );
+};
diff --git a/src/components/molecules/write-form/selectPosition.tsx b/src/components/molecules/write-form/selectPosition.tsx
new file mode 100644
index 00000000..d9c7bd6b
--- /dev/null
+++ b/src/components/molecules/write-form/selectPosition.tsx
@@ -0,0 +1,91 @@
+import { WriteFormLabel } from '@/components/atoms/write-form/form-label';
+import { PositionBadge } from '@/components/molecules/position-badge';
+import {
+ FormControl,
+ FormField,
+ FormItem,
+ FormMessage,
+} from '@/components/ui/form';
+import { DEFAULT_POSITION_NAMES, PositionName, WriteForm } from '@/types';
+import clsx from 'clsx';
+import { useState } from 'react';
+import { UseFormReturn } from 'react-hook-form';
+
+type SelectPositionProps = {
+ form: UseFormReturn;
+};
+
+export const SelectPosition = ({ form }: SelectPositionProps) => {
+ const [selectedPositions, setSelectedPositions] = useState(
+ [],
+ );
+ const hasError = !!form.formState.errors.position;
+
+ const positionClickHandler = (position: PositionName) => {
+ const isSelected = selectedPositions.find(
+ (selectedSkill) => selectedSkill === position,
+ );
+
+ // toggle. ์ง๊ธ ์ ํํ ์คํฌ์ด ์ด๋ฏธ ์ ํ๋์ด ์์๋ค๋ฉด ์ ์ธ๋ผ์ผ ํจ
+ if (isSelected) {
+ const nextSelectedPositions = selectedPositions.filter(
+ (selectedSkill) => selectedSkill !== position,
+ );
+
+ setSelectedPositions(nextSelectedPositions);
+ form.setValue('position', nextSelectedPositions);
+ } else {
+ const nextSelectedPositions = [...selectedPositions, position];
+
+ setSelectedPositions(nextSelectedPositions);
+ form.setValue('position', nextSelectedPositions);
+ }
+ };
+ return (
+ <>
+ (
+
+
+
+
+
+
+
+ {DEFAULT_POSITION_NAMES.map((position, i) => (
+ -
+
+
+ ))}
+
+
+ {fieldState.error?.message}
+
+ )}
+ />
+ >
+ );
+};
diff --git a/src/components/molecules/write-form/selectSkill.tsx b/src/components/molecules/write-form/selectSkill.tsx
new file mode 100644
index 00000000..ef192458
--- /dev/null
+++ b/src/components/molecules/write-form/selectSkill.tsx
@@ -0,0 +1,90 @@
+import { WriteFormLabel } from '@/components/atoms/write-form/form-label';
+import {
+ FormControl,
+ FormField,
+ FormItem,
+ FormMessage,
+} from '@/components/ui/form';
+import { DEFAULT_SKILL_NAMES, WriteForm } from '@/types';
+import { SkillName } from '@/types/index';
+import clsx from 'clsx';
+import { useState } from 'react';
+import { UseFormReturn } from 'react-hook-form';
+import { SkillBadge } from '../skill-badge';
+
+type SelectSkillProps = {
+ form: UseFormReturn;
+};
+
+export const SelectSkill = ({ form }: SelectSkillProps) => {
+ const [selectedSkills, setSelectedSkills] = useState([]);
+ const hasError = !!form.formState.errors.skills;
+
+ const skillClickHandler = (skill: SkillName) => {
+ const isSelected = selectedSkills.find(
+ (selectedSkill) => selectedSkill === skill,
+ );
+
+ // toggle. ์ง๊ธ ์ ํํ ์คํฌ์ด ์ด๋ฏธ ์ ํ๋์ด ์์๋ค๋ฉด ์ ์ธ๋ผ์ผ ํจ
+ if (isSelected) {
+ const nextSelectedSkills = selectedSkills.filter(
+ (selectedSkill) => selectedSkill !== skill,
+ );
+
+ setSelectedSkills(nextSelectedSkills);
+ form.setValue('skills', nextSelectedSkills);
+ } else {
+ const nextSelectedSkills = [...selectedSkills, skill];
+
+ setSelectedSkills(nextSelectedSkills);
+ form.setValue('skills', nextSelectedSkills);
+ }
+ };
+ return (
+ <>
+ (
+
+
+
+
+
+
+
+ {DEFAULT_SKILL_NAMES.map((skill, i) => (
+ -
+
+
+ ))}
+
+
+ {fieldState.error?.message}
+
+ )}
+ />
+ >
+ );
+};
diff --git a/src/components/molecules/write-form/startDateCalendar.tsx b/src/components/molecules/write-form/startDateCalendar.tsx
new file mode 100644
index 00000000..a5bfa6f3
--- /dev/null
+++ b/src/components/molecules/write-form/startDateCalendar.tsx
@@ -0,0 +1,117 @@
+import { WriteFormLabel } from '@/components/atoms/write-form/form-label';
+import { Button } from '@/components/ui//button';
+import { Calendar } from '@/components/ui//calendar';
+import {
+ FormControl,
+ FormField,
+ FormItem,
+ FormMessage,
+} from '@/components/ui//form';
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from '@/components/ui//popover';
+import { WriteForm } from '@/types';
+import { formatYearMonthDayWithDot } from '@/utils/dateUtils';
+import clsx from 'clsx';
+import { CalendarIcon } from 'lucide-react';
+import { UseFormReturn } from 'react-hook-form';
+
+type TitleProps = {
+ form: UseFormReturn;
+ isStartDateCalendarOpen: boolean;
+ openStartDateCalendar: () => void;
+ closeStartDateCalendar: () => void;
+ validStartDate: Date;
+};
+
+export const StartDateCalendar = ({
+ form,
+ isStartDateCalendarOpen,
+ openStartDateCalendar,
+ closeStartDateCalendar,
+ validStartDate,
+}: TitleProps) => {
+ const hasError = !!form.formState.errors.startDate;
+
+ /** calendar์์ ๋ ์ง ์ ํ ํ calendar๊ฐ ๋ซํ๊ฒ ํ๊ธฐ ์ํ ํจ์ */
+ const startDateSelect = (
+ date: Date | undefined,
+ onChange: (date: Date | undefined) => void,
+ ) => {
+ if (!date) return;
+
+ onChange(date);
+ closeStartDateCalendar();
+ };
+
+ return (
+ <>
+ (
+
+
+
+
+
+ {field.value ? (
+ formatYearMonthDayWithDot(field.value)
+ ) : (
+ <>
+
+ {formatYearMonthDayWithDot(validStartDate)}
+
+ >
+ )}
+
+
+
+
+
+
+
+
+ {
+ startDateSelect(e, field.onChange);
+ }}
+ disabled={{ before: validStartDate }}
+ />
+
+
+
+ {fieldState.error?.message}
+
+ )}
+ />
+ >
+ );
+};
diff --git a/src/components/molecules/write-form/tiptap/desctiption.tsx b/src/components/molecules/write-form/tiptap/desctiption.tsx
new file mode 100644
index 00000000..c445888f
--- /dev/null
+++ b/src/components/molecules/write-form/tiptap/desctiption.tsx
@@ -0,0 +1,141 @@
+'use client';
+
+import {
+ FormControl,
+ FormField,
+ FormItem,
+ FormMessage,
+} from '@/components/ui/form';
+import { WriteForm } from '@/types';
+import { EmojiReplacer } from '@tiptap-extend/emoji-replacer';
+import Blockquote from '@tiptap/extension-blockquote';
+import BulletList from '@tiptap/extension-bullet-list';
+import Code from '@tiptap/extension-code';
+import CodeBlock from '@tiptap/extension-code-block';
+import Heading from '@tiptap/extension-heading';
+import Highlight from '@tiptap/extension-highlight';
+import HorizontalRule from '@tiptap/extension-horizontal-rule';
+import ListItem from '@tiptap/extension-list-item';
+import OrderedList from '@tiptap/extension-ordered-list';
+import TaskItem from '@tiptap/extension-task-item';
+import TaskList from '@tiptap/extension-task-list';
+import Typography from '@tiptap/extension-typography';
+import { EditorContent, useEditor } from '@tiptap/react';
+import StarterKit from '@tiptap/starter-kit';
+import clsx from 'clsx';
+import { useEffect } from 'react';
+import { UseFormReturn } from 'react-hook-form';
+import { emojiRules } from './emojiRules';
+
+const tiptapStyleExtensions = [
+ Heading.configure({
+ HTMLAttributes: {
+ class: 'text-xl font-bold capitalize',
+ levels: [2],
+ },
+ }),
+ Blockquote.configure({
+ HTMLAttributes: {
+ class: 'border-l-6 pl-2 border-gray-400',
+ },
+ }),
+ // Bold,
+ // Italic,
+ ListItem,
+ BulletList.configure({
+ HTMLAttributes: {
+ class: 'list-disc ml-2',
+ },
+ }),
+ OrderedList.configure({
+ HTMLAttributes: {
+ class: 'list-decimal ml-2',
+ },
+ }),
+ Code.configure({
+ HTMLAttributes: {
+ class: 'bg-gray-100 py-[1px] px-1 rounded-sm text-[inherit]',
+ },
+ }),
+ HorizontalRule.configure({
+ HTMLAttributes: {
+ class: 'my-[10px]',
+ },
+ }),
+ CodeBlock.configure({
+ HTMLAttributes: {
+ class: 'bg-gray-100 p-3 rounded-sm',
+ },
+ }),
+ TaskItem.configure({
+ nested: true,
+ }),
+ TaskList.configure({
+ itemTypeName: 'taskItem',
+ HTMLAttributes: {
+ class: 'bg-red',
+ },
+ }),
+ EmojiReplacer.configure({
+ ruleConfigs: emojiRules,
+ shouldUseExtraReplacementSpace: false,
+ }),
+];
+
+type DescriptionProps = {
+ form: UseFormReturn;
+};
+
+export const Description = ({ form }: DescriptionProps) => {
+ const hasError = !!form.formState.errors.description;
+
+ const editor = useEditor({
+ editable: true,
+ extensions: [
+ StarterKit,
+ Highlight,
+ Typography,
+ EmojiReplacer,
+ ...tiptapStyleExtensions,
+ ],
+ editorProps: {
+ attributes: {
+ class: clsx(
+ 'h-[500px] overflow-y-auto w-full mt-0 py-2 px-3 text-sm rounded-md border border-input bg-background ring-offset-background shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px]',
+ hasError
+ ? 'text-red-500 border-red-500 focus-visible:ring-red-500/20'
+ : 'focus-visible:border-ring focus-visible:ring-ring/50',
+ ),
+ },
+ },
+ });
+
+ useEffect(() => {
+ if (!editor) return;
+
+ /** useForm์ผ๋ก ๋ง๋ form์ ์ํ ์
๋ฐ์ดํธ. ๋ ๋๋ง๊ณผ๋ ๋ฌด๊ดํ ๋ก์ง์ผ๋ก useEffect์์ ๊ด๋ฆฌ */
+ const descriptionStateUpdate = () =>
+ editor.on('update', () => form.setValue('description', editor.getHTML()));
+
+ descriptionStateUpdate();
+
+ return () => {
+ editor.off('update', descriptionStateUpdate);
+ };
+ }, [editor, form]);
+
+ return (
+ (
+
+
+
+
+
+
+ )}
+ />
+ );
+};
diff --git a/src/components/molecules/write-form/tiptap/emojiRules.ts b/src/components/molecules/write-form/tiptap/emojiRules.ts
new file mode 100644
index 00000000..db888ef4
--- /dev/null
+++ b/src/components/molecules/write-form/tiptap/emojiRules.ts
@@ -0,0 +1,123 @@
+/** ์ด๋ชจ์ง ๋จ์ถ์ด๋ก ๋์ฒด์ํฌ ์ด๋ชจ์ง ๋ชฉ๋ก */
+export const emojiRules = [
+ { find: ':)', replace: ':)' },
+ { find: ':/', replace: ':/' },
+ { find: ':smile:', replace: '๐' },
+ { find: ':laughing:', replace: '๐' },
+ { find: ':blush:', replace: '๐' },
+ { find: ':heart:', replace: 'โค๏ธ' },
+ { find: ':+1:', replace: '๐' },
+ { find: ':-1:', replace: '๐' },
+ { find: ':tada:', replace: '๐' },
+ { find: ':rocket:', replace: '๐' },
+ { find: ':fire:', replace: '๐ฅ' },
+ { find: ':bug:', replace: '๐' },
+ { find: ':memo:', replace: '๐' },
+ { find: ':sparkles:', replace: 'โจ' },
+ { find: ':star:', replace: 'โญ' },
+ { find: ':warning:', replace: 'โ ๏ธ' },
+ { find: ':lock:', replace: '๐' },
+ { find: ':key:', replace: '๐' },
+ { find: ':construction:', replace: '๐ง' },
+ { find: ':recycle:', replace: 'โป๏ธ' },
+ { find: ':package:', replace: '๐ฆ' },
+ { find: ':zap:', replace: 'โก' },
+ { find: ':mag:', replace: '๐' },
+ { find: ':heavy_check_mark:', replace: 'โ๏ธ' },
+ { find: ':hourglass_flowing_sand:', replace: 'โณ' },
+ { find: ':globe_with_meridians:', replace: '๐' },
+ { find: ':iphone:', replace: '๐ฑ' },
+ { find: ':speech_balloon:', replace: '๐ฌ' },
+ { find: ':thought_balloon:', replace: '๐ญ' },
+ { find: ':eyes:', replace: '๐' },
+ { find: ':zzz:', replace: '๐ค' },
+ { find: ':clap:', replace: '๐' },
+ { find: ':confetti_ball:', replace: '๐' },
+ { find: ':moneybag:', replace: '๐ฐ' },
+ { find: ':star2:', replace: '๐' },
+ { find: ':balloon:', replace: '๐' },
+ { find: ':gift:', replace: '๐' },
+ { find: ':camera:', replace: '๐ท' },
+ { find: ':mag_right:', replace: '๐' },
+ { find: ':pencil:', replace: 'โ๏ธ' },
+ { find: ':chart_with_upwards_trend:', replace: '๐' },
+ { find: ':chart_with_downwards_trend:', replace: '๐' },
+ { find: ':bookmark:', replace: '๐' },
+ { find: ':hourglass:', replace: 'โ' },
+ { find: ':new:', replace: '๐' },
+ { find: ':soon:', replace: '๐' },
+ { find: ':bell:', replace: '๐' },
+ { find: ':link:', replace: '๐' },
+ { find: ':paperclip:', replace: '๐' },
+ { find: ':pushpin:', replace: '๐' },
+ { find: ':mega:', replace: '๐ฃ' },
+ { find: ':bookmark_tabs:', replace: '๐' },
+ { find: ':wrench:', replace: '๐ง' },
+ { find: ':hammer:', replace: '๐จ' },
+ { find: ':tools:', replace: '๐ ๏ธ' },
+ { find: ':nut_and_bolt:', replace: '๐ฉ' },
+ { find: ':gear:', replace: 'โ๏ธ' },
+ { find: ':link:', replace: '๐' },
+ { find: ':file_folder:', replace: '๐' },
+ { find: ':open_file_folder:', replace: '๐' },
+ { find: ':page_facing_up:', replace: '๐' },
+ { find: ':clipboard:', replace: '๐' },
+ { find: ':calendar:', replace: '๐
' },
+ { find: ':date:', replace: '๐' },
+ { find: ':card_index:', replace: '๐' },
+ { find: ':file_cabinet:', replace: '๐๏ธ' },
+ { find: ':bar_chart:', replace: '๐' },
+ { find: ':clipboard:', replace: '๐' },
+ { find: ':file_text:', replace: '๐' },
+ { find: ':email:', replace: 'โ๏ธ' },
+ { find: ':incoming_envelope:', replace: '๐จ' },
+ { find: ':envelope_with_arrow:', replace: '๐ฉ' },
+ { find: ':outbox_tray:', replace: '๐ค' },
+ { find: ':inbox_tray:', replace: '๐ฅ' },
+ { find: ':package:', replace: '๐ฆ' },
+ { find: ':mailbox_closed:', replace: '๐ช' },
+ { find: ':mailbox:', replace: '๐ซ' },
+ { find: ':mailbox_with_mail:', replace: '๐ฌ' },
+ { find: ':mailbox_with_no_mail:', replace: '๐ญ' },
+ { find: ':postbox:', replace: '๐ฎ' },
+ { find: ':newspaper:', replace: '๐ฐ' },
+ { find: ':iphone:', replace: '๐ฑ' },
+ { find: ':calling:', replace: '๐ฒ' },
+ { find: ':telephone_receiver:', replace: '๐' },
+ { find: ':fax:', replace: '๐ ' },
+ { find: ':satellite:', replace: '๐ก' },
+ { find: ':loudspeaker:', replace: '๐ข' },
+ { find: ':mega:', replace: '๐ฃ' },
+ { find: ':outbox_tray:', replace: '๐ค' },
+ { find: ':inbox_tray:', replace: '๐ฅ' },
+ { find: ':scroll:', replace: '๐' },
+ { find: ':pager:', replace: '๐' },
+ { find: ':bar_chart:', replace: '๐' },
+ { find: ':chart_with_upwards_trend:', replace: '๐' },
+ { find: ':chart_with_downwards_trend:', replace: '๐' },
+ { find: ':clipboard:', replace: '๐' },
+ { find: ':pushpin:', replace: '๐' },
+ { find: ':paperclip:', replace: '๐' },
+ { find: ':round_pushpin:', replace: '๐' },
+ { find: ':postal_horn:', replace: '๐ฏ' },
+ { find: ':fax:', replace: '๐ ' },
+ { find: ':battery:', replace: '๐' },
+ { find: ':electric_plug:', replace: '๐' },
+ { find: ':bulb:', replace: '๐ก' },
+ { find: ':flashlight:', replace: '๐ฆ' },
+ { find: ':candle:', replace: '๐ฏ๏ธ' },
+ { find: ':wrench:', replace: '๐ง' },
+ { find: ':hammer:', replace: '๐จ' },
+ { find: ':nut_and_bolt:', replace: '๐ฉ' },
+ { find: ':gear:', replace: 'โ๏ธ' },
+ { find: ':magnet:', replace: '๐งฒ' },
+ { find: ':ladder:', replace: '๐ช' },
+ { find: ':alembic:', replace: 'โ๏ธ' },
+ { find: ':microscope:', replace: '๐ฌ' },
+ { find: ':telescope:', replace: '๐ญ' },
+ { find: ':satellite:', replace: '๐ก' },
+ { find: ':syringe:', replace: '๐' },
+ { find: ':pill:', replace: '๐' },
+ { find: ':dna:', replace: '๐งฌ' },
+ { find: ':test_tube:', replace: '๐งช' },
+];
diff --git a/src/components/molecules/write-form/title.tsx b/src/components/molecules/write-form/title.tsx
new file mode 100644
index 00000000..f4c47802
--- /dev/null
+++ b/src/components/molecules/write-form/title.tsx
@@ -0,0 +1,34 @@
+import { WriteFormLabel } from '@/components/atoms/write-form/form-label';
+import {
+ FormControl,
+ FormField,
+ FormItem,
+ FormMessage,
+} from '@/components/ui/form';
+import { Input } from '@/components/ui/input';
+import { WriteForm } from '@/types';
+import { UseFormReturn } from 'react-hook-form';
+
+type TitleProps = {
+ form: UseFormReturn;
+};
+
+export const Title = ({ form }: TitleProps) => {
+ return (
+ <>
+ (
+
+
+
+
+
+
+
+ )}
+ />
+ >
+ );
+};
diff --git a/src/components/organisms/bookmark-card/index.tsx b/src/components/organisms/bookmark-card/index.tsx
new file mode 100644
index 00000000..477bda81
--- /dev/null
+++ b/src/components/organisms/bookmark-card/index.tsx
@@ -0,0 +1,40 @@
+'use client';
+
+import { BookmarkCardContents } from '@/components/molecules/bookmark-card-contents';
+import { Group } from '@/types';
+import { isBeforeToday } from '@/utils/dateUtils';
+import { useRouter } from 'next/navigation';
+
+export type ContentInfo = Pick<
+ Group,
+ | 'id'
+ | 'title'
+ | 'deadline'
+ | 'maxParticipants'
+ | 'position'
+ | 'skills'
+ | 'participants'
+ | 'isBookmark'
+>;
+
+type CardProps = {
+ info: ContentInfo;
+};
+
+export const BookmarkCard = ({ info }: CardProps) => {
+ const router = useRouter();
+ const isBeforeDeadline = isBeforeToday(info.deadline);
+
+ return (
+ router.push(`/groups/${info.id}`)}>
+
+
+
+ {isBeforeDeadline && (
+
+
๋ชจ์ง์ด ์ข
๋ฃ๋์์ต๋๋ค.
+
+ )}
+
+ );
+};
diff --git a/src/components/organisms/empty/index.tsx b/src/components/organisms/empty/index.tsx
new file mode 100644
index 00000000..793fe068
--- /dev/null
+++ b/src/components/organisms/empty/index.tsx
@@ -0,0 +1,37 @@
+import Image from 'next/image';
+import Link from 'next/link';
+
+type EmptyProps = {
+ mainText: string;
+ subText?: string;
+ targetUrl?: string;
+ className?: string;
+};
+
+export const Empty = ({
+ mainText,
+ subText,
+ targetUrl,
+ className,
+}: EmptyProps) => {
+ return (
+
+
+
{mainText}
+ {subText &&
{subText}
}
+ {targetUrl && (
+
+ ์์ธํ ๋ด์ฉ์ ์ด๊ณณ์์ ํ์ธํด๋ณด์ธ์
+
+ )}
+
+ );
+};
diff --git a/src/components/organisms/find-email-form/index.tsx b/src/components/organisms/find-email-form/index.tsx
new file mode 100644
index 00000000..715316ce
--- /dev/null
+++ b/src/components/organisms/find-email-form/index.tsx
@@ -0,0 +1,103 @@
+'use client';
+
+import { z } from 'zod';
+import { useForm } from 'react-hook-form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { useState } from 'react';
+import { Form } from '@/components/ui/form';
+import { InputTextField } from '@/components/molecules/input-text-field';
+import Link from 'next/link';
+import { Button } from '@/components/ui/button';
+import { request } from '@/api/request';
+
+const formSchema = z.object({
+ email: z.string().nonempty({ message: '์ด๋ฉ์ผ์ ์
๋ ฅํด์ฃผ์ธ์' }).email({
+ message: '์ ํจํ ์ด๋ฉ์ผ์ด ์๋๋๋ค.',
+ }),
+});
+
+const FindEmailForm = () => {
+ const form = useForm>({
+ resolver: zodResolver(formSchema),
+ defaultValues: {
+ email: '',
+ },
+ });
+
+ const [disabled, setDisabled] = useState(false);
+
+ // find-email์ธ ๊ฒฝ์ฐ
+ const [isExisted, setIsExisted] = useState(false);
+ const [isNotExisted, setIsNotExisted] = useState(false);
+
+ // ์ด๋ฉ์ผ ์ฐพ๊ธฐ
+ const onSubmit = async (values: z.infer) => {
+ try {
+ setDisabled(true);
+
+ // find-email์ธ ๊ฒฝ์ฐ
+ // ์ค๋ณต์ด๋ฉด false, ์ค๋ณต์ด ์๋๋ฉด true
+ const {
+ status: { success },
+ } = await request.post(
+ '/v1/user/check-email',
+ {
+ 'Content-Type': 'application/json',
+ },
+ JSON.stringify(values),
+ );
+
+ if (success) {
+ setIsExisted(false);
+ setIsNotExisted(true);
+ } else {
+ setIsExisted(true);
+ setIsNotExisted(false);
+ }
+ } catch (e) {
+ // ์ค๋ณต์ผ๋ก ์ฒ๋ฆฌํใ
์
์ ์๋ฌ๋๋ฉด ์กด์ฌํ๋๊ฑฐ์
+ // TODO: ์ด๋ฉ์ผ ์ฐพ๊ธฐ ์คํจ์ ์๋ฌ์ฝ๋ ๋ง์ถฐ์ ์ค์ ํด์ฃผ๊ธฐ
+ setIsExisted(true);
+ setIsNotExisted(false);
+ console.log(e);
+ } finally {
+ setDisabled(false);
+ }
+ };
+
+ return (
+
+
+ );
+};
+
+export default FindEmailForm;
diff --git a/src/components/organisms/find-password-form/index.tsx b/src/components/organisms/find-password-form/index.tsx
new file mode 100644
index 00000000..6c952b95
--- /dev/null
+++ b/src/components/organisms/find-password-form/index.tsx
@@ -0,0 +1,97 @@
+'use client';
+
+import { z } from 'zod';
+import { useForm } from 'react-hook-form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { useState } from 'react';
+import { Form } from '@/components/ui/form';
+import { InputTextField } from '@/components/molecules/input-text-field';
+import Link from 'next/link';
+import { Button } from '@/components/ui/button';
+import { request } from '@/api/request';
+
+const formSchema = z.object({
+ email: z.string().nonempty({ message: '์ด๋ฉ์ผ์ ์
๋ ฅํด์ฃผ์ธ์' }).email({
+ message: '์ ํจํ ์ด๋ฉ์ผ์ด ์๋๋๋ค.',
+ }),
+});
+
+const FindPassword = () => {
+ const form = useForm>({
+ resolver: zodResolver(formSchema),
+ defaultValues: {
+ email: '',
+ },
+ });
+
+ const [disabled, setDisabled] = useState(false);
+
+ const [isNotExisted, setIsNotExisted] = useState(false);
+ // reset-password์ผ ๊ฒฝ์ฐ
+ const [isSuccessEmailSend, setIsSuccessEmailSend] = useState(false);
+
+ // ์ด๋ฉ์ผ ์ฐพ๊ธฐ
+ const onSubmit = async (values: z.infer) => {
+ setDisabled(true);
+ try {
+ // reset-password์ผ ๊ฒฝ์ฐ
+ // TODO: ๋น๋ฐ๋ฒํธ ์ฐพ๊ธฐ ๋ก์ง ์์ฑ /find-email
+ const {
+ status: { success },
+ } = await request.post(
+ '/v1/user/reset-password',
+ {
+ 'Content-Type': 'application/json',
+ },
+ JSON.stringify(values),
+ );
+
+ if (success) {
+ setIsSuccessEmailSend(true);
+ setIsNotExisted(false);
+ } else {
+ throw new Error('์ด๋ฉ์ผ ์ ์ก ์คํจ');
+ }
+ } catch (e) {
+ // TODO: ์ด๋ฉ์ผ ์ฐพ๊ธฐ ์คํจ์ ์๋ฌ์ฝ๋ ๋ง์ถฐ์ ์ค์ ํด์ฃผ๊ธฐ
+ setIsNotExisted(true);
+ setIsSuccessEmailSend(false);
+ console.log(e);
+ } finally {
+ setDisabled(false);
+ }
+ };
+
+ return (
+
+
+ );
+};
+
+export default FindPassword;
diff --git a/src/components/organisms/group/index.tsx b/src/components/organisms/group/index.tsx
new file mode 100644
index 00000000..f11dd741
--- /dev/null
+++ b/src/components/organisms/group/index.tsx
@@ -0,0 +1,84 @@
+'use client';
+
+import { WriteGroupButton } from '@/components/molecules/group-create-button';
+import { Filter } from '@/components/molecules/group/filter';
+import { GroupList } from '@/components/molecules/group/group-list';
+import { SortOrder } from '@/components/molecules/group/sort-order';
+import { SearchInput } from '@/components/molecules/search-input/search-input';
+import { Tab, TabType } from '@/components/molecules/tab';
+import { GroupType } from '@/types';
+import { useRouter } from 'next/navigation';
+import { Suspense } from 'react';
+import { Loading } from '../loading';
+
+type GroupListProps = {
+ serverQueryParams: Record;
+};
+
+const tabList: TabType[] = [
+ { value: '', label: '๋ชจ๋ ๊ทธ๋ฃน' },
+ { value: GroupType.STUDY, label: '์คํฐ๋' },
+ { value: GroupType.PROJECT, label: 'ํ๋ก์ ํธ' },
+];
+
+export const Groups = ({ serverQueryParams }: GroupListProps) => {
+ const router = useRouter();
+ /*
+ * router.push๋ฅผ ์ํํ๋ ํจ์
+ * @param queries ์ฌ๋ฌ query key๋ฅผ ํ๋ฒ์ ์
๋ฐ์ดํธ ํ ์ ์๊ธฐ ๋๋ฌธ์ ์ธ์๋ฅผ Record ํ์
์ผ๋ก ๋ฐ๋๋ค
+ */
+ const updateQueryParams = (queries: Record) => {
+ const params = new URLSearchParams();
+
+ // ๊ธฐ์กด searchParams๋ฅผ params์ ๋ฃ๊ธฐ
+ Object.entries(serverQueryParams).forEach(([key, value]) => {
+ if (value !== undefined && value !== '') {
+ params.set(key, value.toString());
+ }
+ });
+
+ // ์
๋ฐ์ดํธํ ์ฟผ๋ฆฌ ์ ์ฉ
+ Object.entries(queries).forEach(([key, value]) => {
+ const prevValue = params.get(key);
+ if (value === '' || value === 'all' || value === 'null') {
+ // ์ ์ฒด ์ ํ ์ ํด๋น key ์ญ์
+ params.delete(key);
+ } else if (prevValue === value) {
+ // ์ด๋ฏธ ์ ํํ ํํฐ๋ฅผ ๋ค์ ์ ํํ ๊ฒฝ์ฐ params์์ ์ญ์
+ params.delete(key);
+ } else {
+ params.set(key, value);
+ }
+ });
+
+ router.push(`?${params.toString()}`);
+ };
+
+ return (
+
+
updateQueryParams({ type: value })}
+ >
+
+
+
+
+
+
+ }
+ >
+
+
+
+
+ );
+};
diff --git a/src/components/organisms/header/index.tsx b/src/components/organisms/header/index.tsx
new file mode 100644
index 00000000..f7925046
--- /dev/null
+++ b/src/components/organisms/header/index.tsx
@@ -0,0 +1,195 @@
+'use client';
+
+import { Avatar } from '@/components/atoms/avatar';
+import LogoutButton from '@/components/atoms/logout-button';
+import { ErrorBoundary } from '@/components/error-boundary';
+import { handleError } from '@/components/error-boundary/error-handler';
+import { NotificationList } from '@/components/molecules/notification-list';
+import { Button } from '@/components/ui/button';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu';
+import useAuthStore from '@/stores/useAuthStore';
+import { getDisplayNickname, getDisplayProfileImage } from '@/utils/fallback';
+import Image from 'next/image';
+import Link from 'next/link';
+import { usePathname } from 'next/navigation';
+import { useState } from 'react';
+
+type MenuItem = {
+ label: string;
+ href: string;
+};
+
+const menuItems: MenuItem[] = [
+ // { label: 'ํ๋ก์ ํธ ์ฐพ๊ธฐ', href: '/projects' },
+ { label: '์ฐํ ๋ชจ์', href: '/bookmark' },
+];
+
+const Logo = ({ isMobile = false }: { isMobile?: boolean }) => (
+
+
+
+);
+
+const MenuLinks = ({ onClick }: { onClick?: () => void }) => {
+ const pathname = usePathname();
+ return (
+ <>
+ {menuItems.map(({ label, href }) => (
+
+ {label}
+
+ ))}
+ >
+ );
+};
+
+const MobileMenuLinks = ({ onClick }: { onClick?: () => void }) => {
+ const pathname = usePathname();
+ return (
+ <>
+ {menuItems.map(({ label, href }) => (
+
+
+ {label}
+
+
+ ))}
+ >
+ );
+};
+
+const NotificationWithBoundary = () => (
+
+ handleError({
+ error,
+ resetErrorBoundary,
+ defaultMessage: '์๋ฆผ์ ๋ถ๋ฌ์ค๋ ์ค ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค',
+ })
+ }
+ >
+
+
+);
+
+export const Header = () => {
+ const user = useAuthStore((state) => state.user);
+ const isLoggedIn = Boolean(user);
+ const userId = user?.userId ?? 0;
+ const profileImage = getDisplayProfileImage(user?.profileImage ?? null);
+
+ const [isMenuOpen, setIsMenuOpen] = useState(false);
+
+ return (
+
+ );
+};
diff --git a/src/components/organisms/loading/index.tsx b/src/components/organisms/loading/index.tsx
new file mode 100644
index 00000000..0687257b
--- /dev/null
+++ b/src/components/organisms/loading/index.tsx
@@ -0,0 +1,65 @@
+const HandShakingIcon = () => {
+ return (
+
+ );
+};
+
+export const Loading = () => {
+ const loadingText = 'Loading....';
+
+ return (
+
+
+
+
+
+ {loadingText.split('').map((char, i) => (
+
+ {char}
+
+ ))}
+
+
+ );
+};
diff --git a/src/components/organisms/login-form/index.tsx b/src/components/organisms/login-form/index.tsx
new file mode 100644
index 00000000..502c8af9
--- /dev/null
+++ b/src/components/organisms/login-form/index.tsx
@@ -0,0 +1,111 @@
+'use client';
+
+import React, { useState } from 'react';
+import { Form } from '@/components/ui/form';
+import { InputTextField } from '@/components/molecules/input-text-field';
+import { Button } from '@/components/ui/button';
+import { useForm } from 'react-hook-form';
+import { z } from 'zod';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { useRouter } from 'next/navigation';
+import useAuthStore from '@/stores/useAuthStore';
+import { request } from '@/api/request';
+import addBookmarkWhenAuth from '@/features/auth/utils/addBookmarkWhenAuth';
+
+const formSchema = z.object({
+ email: z.string().nonempty({ message: '์ด๋ฉ์ผ์ ์
๋ ฅํด์ฃผ์ธ์' }).email({
+ message: '์ ํจํ ์ด๋ฉ์ผ์ด ์๋๋๋ค',
+ }),
+ password: z.string().nonempty({ message: '๋น๋ฐ๋ฒํธ๋ฅผ ์
๋ ฅํด์ฃผ์ธ์' }),
+});
+
+const LoginForm = () => {
+ const form = useForm>({
+ resolver: zodResolver(formSchema),
+ defaultValues: {
+ email: '',
+ password: '',
+ },
+ });
+
+ const [disabled, setDisabled] = useState(false);
+ const [isLoginFailed, setIsLoginFailed] = useState(false);
+ const fetchAndSetUser = useAuthStore((s) => s.fetchAndSetUser);
+ const router = useRouter();
+
+ // ๋ก๊ทธ์ธ
+ const onSubmit = async (values: z.infer) => {
+ try {
+ setDisabled(true);
+ // ๋ก๊ทธ์ธ ๋ก์ง ์์ฑ /login
+ // ์ฑ๊ณต์ฒ๋ฆฌ ์ถ๊ฐ
+ const {
+ status: { success },
+ } = await request.post(
+ '/v1/user/login',
+ {
+ 'Content-Type': 'application/json',
+ },
+ JSON.stringify(values),
+ {
+ credentials: 'include',
+ },
+ );
+
+ if (!success) {
+ throw new Error('๋ก๊ทธ์ธ ์คํจ');
+ }
+
+ // ๋ก๊ทธ์ธ ์ฑ๊ณต ํ ๋ถ๋งํฌ ์ ๋ณด ๋ถ๋ฌ์ค๊ธฐ
+ const bookmarkListStr = localStorage.getItem('bookmarkList');
+
+ // ๋ถ๋งํฌ ์ ๋ณด๊ฐ ์๋ค๋ฉด ์๋ฒ์ ์ ์ฅ
+ if (bookmarkListStr !== null) {
+ await addBookmarkWhenAuth(bookmarkListStr);
+ }
+
+ // ๋ก๊ทธ์ธ ์ฑ๊ณต ํ ํ์์ ๋ณด ๋ถ๋ฌ์ค๊ธฐ /me
+ await fetchAndSetUser();
+
+ const prevPathname = localStorage.getItem('login-trigger-path') || '/';
+
+ router.push(prevPathname);
+
+ localStorage.removeItem('login-trigger-path');
+ } catch (e) {
+ // TODO: ๋ก๊ทธ์ธ ์คํจ์ ์๋ฌ์ฝ๋ ๋ง์ถฐ์ ์ค์ ํด์ฃผ๊ธฐ
+ setIsLoginFailed(true);
+ console.log(e);
+ setDisabled(false);
+ }
+ };
+
+ return (
+
+
+ );
+};
+
+export default LoginForm;
diff --git a/src/components/organisms/recommend-group/index.tsx b/src/components/organisms/recommend-group/index.tsx
new file mode 100644
index 00000000..3f82c825
--- /dev/null
+++ b/src/components/organisms/recommend-group/index.tsx
@@ -0,0 +1,102 @@
+'use client';
+
+import { request } from '@/api/request';
+import { ErrorBoundary } from '@/components/error-boundary';
+import { handleError } from '@/components/error-boundary/error-handler';
+import { RecommendGroupCard } from '@/components/molecules/recommend-group/recommend-group-card';
+import { Button } from '@/components/ui/button';
+import { Skeleton } from '@/components/ui/skeleton';
+import { Group } from '@/types';
+import { isBeforeToday } from '@/utils/dateUtils';
+import { useQuery } from '@tanstack/react-query';
+import { useState } from 'react';
+import { Empty } from '../empty';
+
+export default function RecommendGroup() {
+ const { data: items = [], isLoading } = useQuery({
+ queryKey: ['recommendGroups'],
+ queryFn: async () => {
+ const response = await request.get('/v2/groups/recommend');
+ return response.items;
+ },
+ });
+
+ const validItems = items
+ .filter((item) => !isBeforeToday(item.deadline))
+ .slice(0, 10);
+
+ const [currentIndex, setCurrentIndex] = useState(0);
+ const maxIndex = Math.max(validItems.length - 1, 0);
+
+ const handlePrev = () => setCurrentIndex((prev) => Math.max(prev - 1, 0));
+ const handleNext = () =>
+ setCurrentIndex((prev) => Math.min(prev + 1, maxIndex));
+
+ if (isLoading) {
+ return (
+
+ {Array.from({ length: 10 }).map((_, i) => (
+
+ ))}
+
+ );
+ }
+
+ if (validItems.length === 0) {
+ return (
+
+ );
+ }
+
+ return (
+
+ handleError({
+ error,
+ resetErrorBoundary,
+ defaultMessage: '์ถ์ฒ ๊ทธ๋ฃน์ ๋ถ๋ฌ์ค๋ ์ค ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค',
+ })
+ }
+ >
+
+
+
+ {validItems.map((group) => (
+ -
+
+
+ ))}
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/organisms/register-form/index.tsx b/src/components/organisms/register-form/index.tsx
new file mode 100644
index 00000000..bc056dc9
--- /dev/null
+++ b/src/components/organisms/register-form/index.tsx
@@ -0,0 +1,153 @@
+'use client';
+
+import { z } from 'zod';
+import { useForm } from 'react-hook-form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { InputTextField } from '@/components/molecules/input-text-field';
+import { Button } from '@/components/ui/button';
+import { Form } from '@/components/ui/form';
+import { useState } from 'react';
+import useAuthStore from '@/stores/useAuthStore';
+import { request } from '@/api/request';
+import addBookmarkWhenAuth from '@/features/auth/utils/addBookmarkWhenAuth';
+
+// ํ์๊ฐ์
์ ์ฐ์ด๋ ์ด๋ฉ์ผ๊ณผ ๋น๋ฐ๋ฒํธ ์ ํจ์ฑ
+const registerFormSchema = z
+ .object({
+ email: z.string().nonempty({ message: '์ด๋ฉ์ผ์ ์
๋ ฅํด์ฃผ์ธ์' }).email({
+ message: '์ ํจํ ์ด๋ฉ์ผ์ด ์๋๋๋ค',
+ }),
+ password: z
+ .string()
+ .nonempty({ message: '๋น๋ฐ๋ฒํธ๋ฅผ ์
๋ ฅํด์ฃผ์ธ์' })
+ .regex(/^(?=.*[a-zA-Z])(?=.*\d)(?=.*[\W_]).{8,}$/, {
+ message: '์์ด, ์ซ์, ํน์๋ฌธ์๋ฅผ ํผํฉํ์ฌ 8์๋ฆฌ ์ด์',
+ }),
+ passwordConfirm: z.string().nonempty({
+ message: '๋น๋ฐ๋ฒํธ๋ฅผ ๋ค์ ์
๋ ฅํด์ฃผ์ธ์',
+ }),
+ })
+ .superRefine(({ password, passwordConfirm }, ctx) => {
+ if (password && passwordConfirm && password !== passwordConfirm) {
+ ctx.addIssue({
+ path: ['passwordConfirm'],
+ message: '๋น๋ฐ๋ฒํธ๊ฐ ์ผ์นํ์ง ์์ต๋๋ค',
+ code: z.ZodIssueCode.custom,
+ });
+ }
+ });
+
+const RegisterForm = () => {
+ const [isRegisterFailed, setIsRegisterFailed] = useState(false);
+
+ const registerForm = useForm>({
+ resolver: zodResolver(registerFormSchema),
+ defaultValues: {
+ email: '',
+ password: '',
+ passwordConfirm: '',
+ },
+ });
+
+ const [disabled, setDisabled] = useState(false);
+
+ const fetchAndSetUser = useAuthStore((s) => s.fetchAndSetUser);
+
+ // ํ์๊ฐ์
+ const onRegisterSubmit = async (
+ values: z.infer,
+ ) => {
+ try {
+ setDisabled(true);
+ // ํ์๊ฐ์
๋ก์ง ์์ฑ /user/signup
+ // ์๋ฌ์ฒ๋ฆฌ ๋ณ๋๋ก ํด์ค์ผ ํ ์๋ ์์
+ const {
+ status: { success: registerSuccess },
+ } = await request.post(
+ '/v1/user/signup',
+ {
+ 'Content-Type': 'application/json',
+ },
+ JSON.stringify({
+ email: values.email,
+ password: values.password,
+ }),
+ );
+
+ if (!registerSuccess) throw new Error('ํ์๊ฐ์
์คํจ');
+
+ const {
+ status: { success: loginSuccess },
+ } = await request.post(
+ '/v1/user/login',
+ {
+ 'Content-Type': 'application/json',
+ },
+ JSON.stringify(values),
+ {
+ credentials: 'include',
+ },
+ );
+
+ if (!loginSuccess) throw new Error('๋ก๊ทธ์ธ ์คํจ');
+
+ // ๋ก๊ทธ์ธ ์ฑ๊ณต ํ ๋ถ๋งํฌ ์ ๋ณด ๋ถ๋ฌ์ค๊ธฐ
+ const bookmarkListStr = localStorage.getItem('bookmarkList');
+
+ // ๋ถ๋งํฌ ์ ๋ณด๊ฐ ์๋ค๋ฉด ์๋ฒ์ ์ ์ฅ
+ if (bookmarkListStr !== null) {
+ await addBookmarkWhenAuth(bookmarkListStr);
+ }
+
+ // ๋ก๊ทธ์ธ ์ฑ๊ณต ํ ํ์์ ๋ณด ๋ถ๋ฌ์ค๊ธฐ /me
+ await fetchAndSetUser();
+ } catch (e) {
+ setIsRegisterFailed(true);
+ console.log(e);
+ setDisabled(false);
+ }
+ };
+
+ return (
+
+
+ );
+};
+
+export default RegisterForm;
diff --git a/src/components/organisms/register-optional-form/index.tsx b/src/components/organisms/register-optional-form/index.tsx
new file mode 100644
index 00000000..080454da
--- /dev/null
+++ b/src/components/organisms/register-optional-form/index.tsx
@@ -0,0 +1,137 @@
+'use client';
+
+import { InputTextField } from '@/components/molecules/input-text-field';
+import { FormRadioGroupField } from '@/components/molecules/input-radiogroup-field';
+import { FormCheckboxGroupField } from '@/components/molecules/input-checkbox-field';
+import { Button } from '@/components/ui/button';
+import { Form } from '@/components/ui/form';
+import { Position, Skill } from '@/types/enums';
+import { z } from 'zod';
+import { useForm } from 'react-hook-form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import useAuthStore from '@/stores/useAuthStore';
+import { useRouter } from 'next/navigation';
+import { useState } from 'react';
+import { request } from '@/api/request';
+
+const positions = Object.keys(Position).filter((k) => isNaN(Number(k))) as [
+ string,
+ ...string[],
+];
+const skills = Object.keys(Skill).filter((k) => isNaN(Number(k))) as [
+ string,
+ ...string[],
+];
+
+// ํ์๊ฐ์
ํ ์ต์
๋ ์ ๋ณด ์ ํจ์ฑ
+// ๊ท์น์ ๋ค์ด๋ฒ์ ๊ฐ์ด ํ์ต๋๋ค
+const optionalFormSchema = z.object({
+ nickname: z.string().max(30, '๋๋ค์์ 30์๋ฆฌ ์ดํ์ฌ์ผ ํฉ๋๋ค.'),
+ position: z.enum(positions, {
+ message: 'ํฌ์ง์
์ ์ ํํด์ฃผ์ธ์',
+ }),
+ skills: z.array(z.enum(skills)),
+});
+
+const RegisterOptionalForm = () => {
+ const fetchAndSetUser = useAuthStore((s) => s.fetchAndSetUser);
+ // ์ด ์ปดํฌ๋ํธ๋ user๊ฐ ์กด์ฌํ ๋๋ง ๋ ๋๋ง๋๋ฏ๋ก, useAuthStore์์ user๋ฅผ ๊ฐ์ ธ์ฌ ๋๋ !๋ฅผ ์ฌ์ฉํด๋ ๋ฉ๋๋ค.
+ const currentUser = useAuthStore((s) => s.user)!;
+
+ const optionalForm = useForm>({
+ resolver: zodResolver(optionalFormSchema),
+ defaultValues: {
+ nickname: '',
+ position: '',
+ skills: [],
+ },
+ });
+
+ const [disabled, setDisabled] = useState(false);
+
+ const router = useRouter();
+
+ // ์ต์
์ค์
+ const onOptionalSubmit = async (
+ values: z.infer,
+ ) => {
+ try {
+ setDisabled(true);
+ // ํ๋กํ ์ต์
์ค์
+ const newValues: z.infer = {
+ ...values,
+ nickname: values.nickname || currentUser.email, // ๋๋ค์์ด ๋น์ด์์ผ๋ฉด ์ด๋ฉ์ผ๋ก ์ค์
+ };
+
+ // request๋ json ํํ์ธ๋ฐ api๋ form-data๋ก ๋ฐ์์ ๋ณ๋์ fetch๋ก ์์ฒญํฉ๋๋ค.
+ const formData = new FormData();
+ formData.append('nickname', newValues.nickname);
+ formData.append(
+ 'position',
+ String(Position[newValues.position as keyof typeof Position]),
+ );
+
+ if (newValues.skills.length > 0) {
+ const skillValues = newValues.skills.map(
+ (skillKey) => Skill[skillKey as keyof typeof Skill],
+ );
+ formData.append('skills', skillValues.join(','));
+ }
+
+ const {
+ status: { success },
+ } = await request.patch(`/v1/user/edit`, {}, formData, {
+ credentials: 'include',
+ });
+
+ if (!success) throw new Error('ํ๋กํ ์ค์ ์คํจ!');
+
+ // ๋ฐ๋ ํ๋กํ ๋ค์ ๋ถ๋ฌ์์ ์ค์
+ await fetchAndSetUser();
+
+ const prevPathname = localStorage.getItem('login-trigger-path') || '/';
+ router.push(prevPathname);
+
+ localStorage.removeItem('login-trigger-path');
+ } catch (e) {
+ // TODO: ํ๋กํ ์๋ฌ ์ค์ //
+ console.log(e);
+ setDisabled(false);
+ }
+ };
+ return (
+
+
+ );
+};
+
+export default RegisterOptionalForm;
diff --git a/src/components/organisms/write-form/index.tsx b/src/components/organisms/write-form/index.tsx
new file mode 100644
index 00000000..d5cee5af
--- /dev/null
+++ b/src/components/organisms/write-form/index.tsx
@@ -0,0 +1,238 @@
+'use client';
+
+import { request } from '@/api/request';
+import { CategoryName } from '@/components/atoms/write-form/categoryName';
+import { ErrorBoundary } from '@/components/error-boundary';
+import { handleError } from '@/components/error-boundary/error-handler';
+import { AutoAllow } from '@/components/molecules/write-form/autoAllow';
+import { DeadlineCalendar } from '@/components/molecules/write-form/deadlineCalendar';
+import { EndDateCalendar } from '@/components/molecules/write-form/endDateCalendar';
+import { MaxParticipants } from '@/components/molecules/write-form/maxParticipants';
+import { SelectType } from '@/components/molecules/write-form/selcetType';
+import { SelectPosition } from '@/components/molecules/write-form/selectPosition';
+import { SelectSkill } from '@/components/molecules/write-form/selectSkill';
+import { StartDateCalendar } from '@/components/molecules/write-form/startDateCalendar';
+import { Description } from '@/components/molecules/write-form/tiptap/desctiption';
+import { Title } from '@/components/molecules/write-form/title';
+import { Button } from '@/components/ui/button';
+import { Form } from '@/components/ui/form';
+import {
+ DEFAULT_POSITION_NAMES,
+ DEFAULT_SKILL_NAMES,
+ GroupType,
+} from '@/types';
+import { Position, Skill } from '@/types/enums';
+
+import { zodResolver } from '@hookform/resolvers/zod';
+import { useQueryClient } from '@tanstack/react-query';
+import { addDays, isAfter } from 'date-fns';
+import { useRouter } from 'next/navigation';
+import { useMemo, useState } from 'react';
+import { useForm } from 'react-hook-form';
+import { toast } from 'sonner';
+import { z } from 'zod';
+
+const formSchema = z
+ .object({
+ title: z
+ .string()
+ .trim()
+ .nonempty({ message: '์ ๋ชฉ์ ์
๋ ฅํด์ฃผ์ธ์.' })
+ .max(100, {
+ message: '์ ๋ชฉ์ด ๋๋ฌด ๊ธธ์ด์. 100์ ์ด๋ด๋ก ์ค์ฌ์ฃผ์ธ์.',
+ }),
+ maxParticipants: z.coerce
+ .number({ message: '๋ชจ์์ ์ ์์ ์ค์ ํด์ฃผ์ธ์.' })
+ .min(2, {
+ message: '์ต์ ์ธ์์ 2๋ช
์ด์์ด์ด์ผ ํด์.',
+ })
+ .max(30, {
+ message: '์ต๋ ์ธ์์ 30๋ช
๊น์ง ๊ฐ๋ฅํฉ๋๋ค.',
+ }),
+ deadline: z.date().min(addDays(new Date(), 0), {
+ message: '๋ชจ์ง ๋ง๊ฐ์ผ์ ์ค๋๋ก๋ถํฐ 1์ผ ์ดํ๋ถํฐ ์ค์ ๊ฐ๋ฅํฉ๋๋ค.',
+ }),
+ startDate: z.date(),
+ endDate: z.date(),
+ description: z
+ .string()
+ .min(20, { message: '๋ด์ฉ์ ์ข ๋ ์์ธํ๊ฒ ์ ์ด์ฃผ์ธ์.' }),
+ autoAllow: z.boolean(),
+ type: z.enum([GroupType.STUDY, GroupType.PROJECT]),
+ skills: z
+ .array(
+ z.union([
+ z.enum(DEFAULT_SKILL_NAMES), // ๋ฏธ๋ฆฌ ์ ํด์ง skill๊ณผ
+ z.string(), // ์ ์ ๊ฐ ์
๋ ฅํ ์ปค์คํ
skill์ ํฉ์น union ํ์
ํํ๋ก ์ ํจ์ฑ ๊ฒ์ฌ
+ ]),
+ { required_error: '์ฌ์ฉ ๊ธฐ์ ์ ํ๊ฐ์ง ์ด์ ์ ํํด์ฃผ์ธ์.' }, // ์์ ์ ํ๋ ํ์ง ์์ ๊ฒฝ์ฐ
+ )
+ .min(1, { message: '์ฌ์ฉ ๊ธฐ์ ์ ํ๊ฐ์ง ์ด์ ์ ํํด์ฃผ์ธ์.' }), // ์ ํํ๋ค๊ฐ ์ง์์ [] ์ธ ๊ฒฝ์ฐ
+ position: z
+ .array(
+ z.union([
+ z.enum(DEFAULT_POSITION_NAMES), // ๋ฏธ๋ฆฌ ์ ํด์ง position๊ณผ
+ z.string(), // ์ ์ ๊ฐ ์
๋ ฅํ ์ปค์คํ
skill์ ํฉ์น union ํ์
ํํ๋ก ์ ํจ์ฑ ๊ฒ์ฌ
+ ]),
+ { required_error: 'ํฌ์ง์
์ ํ ๊ฐ์ง ์ด์ ์ ํํด์ฃผ์ธ์.' }, // ์์ ์ ํ๋ ํ์ง ์์ ๊ฒฝ์ฐ
+ )
+ .min(1, { message: 'ํฌ์ง์
์ ํ ๊ฐ์ง ์ด์ ์ ํํด์ฃผ์ธ์.' }), // ์ ํํ๋ค๊ฐ ์ง์์ [] ์ธ ๊ฒฝ์ฐ
+ })
+ .refine((data) => isAfter(data.startDate, addDays(data.deadline, 0)), {
+ message: '๋ชจ์ ์์์ผ์ ๋ชจ์ง ๋ง๊ฐ์ผ๋ก๋ถํฐ 1์ผ ์ดํ์ฌ์ผ ํฉ๋๋ค.',
+ path: ['startDate'],
+ })
+ .refine((data) => isAfter(data.endDate, addDays(data.startDate, 6)), {
+ message: '๋ชจ์ ์ข
๋ฃ์ผ์ ๋ชจ์ง ์์์ผ๋ก๋ถํฐ 7์ผ ์ดํ์ฌ์ผ ํฉ๋๋ค.',
+ path: ['endDate'],
+ });
+
+export const WriteForm = () => {
+ const [isDeadlineCalendarOpen, setIsDeadlineCalendarOpen] = useState(false);
+ const [isStartDateCalendarOpen, setIsStartDateCalendarOpen] = useState(false);
+ const [isEndDateCalendarOpen, setIsEndDateCalendarOpen] = useState(false);
+ const router = useRouter();
+ const queryClient = useQueryClient();
+ const validDeadline = addDays(new Date(), 7);
+
+ const [selectedDeadline, setSelectedDeadline] = useState(validDeadline);
+
+ const validStartDate = useMemo(
+ () => addDays(selectedDeadline, 1),
+ [selectedDeadline],
+ );
+
+ const validEndDate = useMemo(
+ () => addDays(validStartDate, 7),
+ [validStartDate],
+ );
+
+ const deadlineSelectHandler = (date: Date) => {
+ setSelectedDeadline(date);
+ };
+
+ const form = useForm>({
+ resolver: zodResolver(formSchema), // ์ ํจ์ฑ ๊ฒ์ฌ๋ zodResolver๋ก ํ๋ค
+ reValidateMode: 'onSubmit', // submit ์์๋ง ์ ํจ์ฑ ๊ฒ์ฌ
+ defaultValues: {
+ title: '',
+ description: '',
+ autoAllow: false,
+ type: GroupType.STUDY,
+ deadline: validDeadline, // ์ ํํ์ง ์์์ ๊ฒฝ์ฐ validDeadline ๊ทธ๋๋ก ์ฌ์ฉ
+ startDate: validStartDate, // ์ ํํ์ง ์์์ ๊ฒฝ์ฐ validStartDate ์ผ์ ๊ทธ๋๋ก ์ฌ์ฉ
+ endDate: validEndDate, // ์ ํํ์ง ์์์ ๊ฒฝ์ฐ validEndDate ๊ทธ๋๋ก ์ฌ์ฉ
+ },
+ });
+
+ const cancelClickHandler = () => {
+ router.push('/');
+ };
+
+ const formSubmit = async (values: z.infer) => {
+ const skills = values.skills.map(
+ (skill) => Skill[skill as keyof typeof Skill],
+ ); // server์ ๋ณด๋ผ๋ enum์ ์ธ๋ฑ์ค๋ก ๋ณด๋ด๊ธฐ๋ก ํ์ผ๋ฏ๋ก string์ enum์ ์ธ๋ฑ์ค๋ก ๋ณํ
+
+ const position = values.position.map(
+ (position) => Position[position as keyof typeof Position],
+ ); // server์ ๋ณด๋ผ๋ enum์ ์ธ๋ฑ์ค๋ก ๋ณด๋ด๊ธฐ๋ก ํ์ผ๋ฏ๋ก string์ enum์ ์ธ๋ฑ์ค๋ก ๋ณํ
+
+ try {
+ const result = await request.post(
+ `/v2/groups`,
+ { 'Content-Type': 'application/json' },
+ JSON.stringify({ ...values, skills, position }),
+ { credentials: 'include' },
+ );
+
+ if (result.status.success) {
+ await queryClient.invalidateQueries({
+ queryKey: ['items', '/v2/groups'],
+ }); // ํ์ผ๋ก ์ด๋ ํ ์๋ก์ด ๋ฐ์ดํฐ๋ก ๊ฐฑ์ ํ๊ธฐ ์ํด ๋ฌดํจํ ์ํด
+
+ router.push('/');
+ } else {
+ toast.error('์๋ฌ๊ฐ ๋ฐ์ํ์ต๋๋ค. ๋ค์ ์๋ํด์ฃผ์ธ์.');
+ console.log(
+ `Error code ${result.status.code} : ${result.status.message}`,
+ );
+ }
+ } catch (error) {
+ throw new Error(
+ `Group create error(server): ${
+ error instanceof Error ? error.message : 'Unexpected Error'
+ }`,
+ );
+ }
+ };
+
+ return (
+
+ handleError({
+ error,
+ resetErrorBoundary,
+ defaultMessage: '๊ทธ๋ฃน ์์ฑ ์ค ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค.',
+ })
+ }
+ >
+
+
+ );
+};
diff --git a/src/components/query-error-boundary/index.tsx b/src/components/query-error-boundary/index.tsx
new file mode 100644
index 00000000..546e89cf
--- /dev/null
+++ b/src/components/query-error-boundary/index.tsx
@@ -0,0 +1,45 @@
+'use client';
+
+import { QueryErrorResetBoundary } from '@tanstack/react-query';
+import { ErrorBoundary } from '@/components/error-boundary';
+import { handleError } from '@/components/error-boundary/error-handler';
+
+type QueryErrorBoundaryProps = {
+ children: React.ReactNode;
+ fallback?: React.ReactNode;
+};
+
+/**
+ * ์ฟผ๋ฆฌ ์๋ฌ ๋ฐ์ด๋๋ฆฌ
+ *
+ * useSuspenseQuery, useSuspenseInfiniteQuery ์ฌ์ฉํ๋ ๊ฒฝ์ฐ
+ * ์๋ฌ๊ฐ ๋ฐ์ํ ํ ์๋ฌ๋ฅผ ์ด๊ธฐํํ ๋ ์ฟผ๋ฆฌ์ ์๋ฌ ์ํ๋ ์ด๊ธฐํํด์ผ ํ๋ค.
+ *
+ *
+ * @param children ์์ ์ปดํฌ๋ํธ
+ * @returns ์ฟผ๋ฆฌ ์๋ฌ ๋ฐ์ด๋๋ฆฌ
+ */
+export const QueryErrorBoundary = ({
+ children,
+ fallback,
+}: QueryErrorBoundaryProps) => {
+ return (
+
+ {({ reset }) => (
+ {
+ return fallback
+ ? fallback
+ : handleError({
+ error,
+ resetErrorBoundary,
+ });
+ }}
+ onReset={reset}
+ >
+ {children}
+
+ )}
+
+ );
+};
diff --git a/src/components/ui/alert-dialog.tsx b/src/components/ui/alert-dialog.tsx
new file mode 100644
index 00000000..0863e40d
--- /dev/null
+++ b/src/components/ui/alert-dialog.tsx
@@ -0,0 +1,157 @@
+"use client"
+
+import * as React from "react"
+import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
+
+import { cn } from "@/lib/utils"
+import { buttonVariants } from "@/components/ui/button"
+
+function AlertDialog({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function AlertDialogTrigger({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogPortal({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogOverlay({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+ )
+}
+
+function AlertDialogHeader({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function AlertDialogFooter({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function AlertDialogTitle({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogDescription({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogAction({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogCancel({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ AlertDialog,
+ AlertDialogPortal,
+ AlertDialogOverlay,
+ AlertDialogTrigger,
+ AlertDialogContent,
+ AlertDialogHeader,
+ AlertDialogFooter,
+ AlertDialogTitle,
+ AlertDialogDescription,
+ AlertDialogAction,
+ AlertDialogCancel,
+}
diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx
new file mode 100644
index 00000000..e3831623
--- /dev/null
+++ b/src/components/ui/avatar.tsx
@@ -0,0 +1,52 @@
+'use client';
+
+import { cn } from '@/lib/utils';
+import * as AvatarPrimitive from '@radix-ui/react-avatar';
+import * as React from 'react';
+
+function Avatar({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AvatarImage({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AvatarFallback({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export { Avatar, AvatarImage, AvatarFallback };
diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx
new file mode 100644
index 00000000..02054139
--- /dev/null
+++ b/src/components/ui/badge.tsx
@@ -0,0 +1,46 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const badgeVariants = cva(
+ "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
+ {
+ variants: {
+ variant: {
+ default:
+ "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
+ secondary:
+ "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
+ destructive:
+ "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
+ outline:
+ "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+function Badge({
+ className,
+ variant,
+ asChild = false,
+ ...props
+}: React.ComponentProps<"span"> &
+ VariantProps & { asChild?: boolean }) {
+ const Comp = asChild ? Slot : "span"
+
+ return (
+
+ )
+}
+
+export { Badge, badgeVariants }
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
new file mode 100644
index 00000000..5165dd19
--- /dev/null
+++ b/src/components/ui/button.tsx
@@ -0,0 +1,58 @@
+import { cn } from '@/lib/utils';
+import { Slot } from '@radix-ui/react-slot';
+import { cva, type VariantProps } from 'class-variance-authority';
+import * as React from 'react';
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-[12px] text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
+ {
+ variants: {
+ variant: {
+ default:
+ 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',
+ destructive:
+ 'bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
+ outline:
+ 'border border-primary text-primary bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
+ secondary:
+ 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
+ ghost:
+ 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
+ link: 'text-primary underline-offset-4 hover:underline',
+ },
+ size: {
+ default: 'h-9 px-4 py-2 has-[>svg]:px-3',
+ sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
+ lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
+ icon: 'size-9',
+ },
+ },
+ defaultVariants: {
+ variant: 'default',
+ size: 'default',
+ },
+ },
+);
+
+function Button({
+ className,
+ variant,
+ size,
+ asChild = false,
+ ...props
+}: React.ComponentProps<'button'> &
+ VariantProps & {
+ asChild?: boolean;
+ }) {
+ const Comp = asChild ? Slot : 'button';
+
+ return (
+
+ );
+}
+
+export { Button, buttonVariants };
diff --git a/src/components/ui/calendar.tsx b/src/components/ui/calendar.tsx
new file mode 100644
index 00000000..7cd3a94e
--- /dev/null
+++ b/src/components/ui/calendar.tsx
@@ -0,0 +1,75 @@
+'use client';
+
+import * as React from 'react';
+import { ChevronLeft, ChevronRight } from 'lucide-react';
+import { DayPicker } from 'react-day-picker';
+
+import { cn } from '@/lib/utils';
+import { buttonVariants } from '@/components/ui/button';
+
+function Calendar({
+ className,
+ classNames,
+ showOutsideDays = true,
+ ...props
+}: React.ComponentProps) {
+ return (
+ .day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md'
+ : '[&:has([aria-selected])]:rounded-md',
+ ),
+ day: cn(
+ buttonVariants({ variant: 'ghost' }),
+ 'size-8 p-0 font-normal aria-selected:opacity-100',
+ ),
+ day_range_start:
+ 'day-range-start aria-selected:bg-primary aria-selected:text-primary-foreground',
+ day_range_end:
+ 'day-range-end aria-selected:bg-primary aria-selected:text-primary-foreground',
+ day_selected:
+ 'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground',
+ day_today: 'bg-accent text-accent-foreground',
+ day_outside:
+ 'day-outside text-muted-foreground aria-selected:text-muted-foreground',
+ day_disabled: 'text-muted-foreground opacity-50',
+ day_range_middle:
+ 'aria-selected:bg-accent aria-selected:text-accent-foreground',
+ day_hidden: 'invisible',
+ ...classNames,
+ }}
+ components={{
+ IconLeft: ({ className, ...props }) => (
+
+ ),
+ IconRight: ({ className, ...props }) => (
+
+ ),
+ }}
+ {...props}
+ />
+ );
+}
+
+export { Calendar };
diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx
new file mode 100644
index 00000000..fa0e4b59
--- /dev/null
+++ b/src/components/ui/checkbox.tsx
@@ -0,0 +1,32 @@
+"use client"
+
+import * as React from "react"
+import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
+import { CheckIcon } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+function Checkbox({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+ )
+}
+
+export { Checkbox }
diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx
new file mode 100644
index 00000000..ee466ba0
--- /dev/null
+++ b/src/components/ui/dialog.tsx
@@ -0,0 +1,134 @@
+'use client';
+
+import { cn } from '@/lib/utils';
+import * as DialogPrimitive from '@radix-ui/react-dialog';
+import { XIcon } from 'lucide-react';
+import * as React from 'react';
+
+function Dialog({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function DialogTrigger({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function DialogPortal({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function DialogClose({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function DialogOverlay({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function DialogContent({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ {children}
+
+
+ Close
+
+
+
+ );
+}
+
+function DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function DialogFooter({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function DialogTitle({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function DialogDescription({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogOverlay,
+ DialogPortal,
+ DialogTitle,
+ DialogTrigger,
+};
diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx
new file mode 100644
index 00000000..ec51e9cc
--- /dev/null
+++ b/src/components/ui/dropdown-menu.tsx
@@ -0,0 +1,257 @@
+"use client"
+
+import * as React from "react"
+import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
+import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+function DropdownMenu({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DropdownMenuPortal({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DropdownMenuTrigger({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DropdownMenuContent({
+ className,
+ sideOffset = 4,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+function DropdownMenuGroup({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DropdownMenuItem({
+ className,
+ inset,
+ variant = "default",
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean
+ variant?: "default" | "destructive"
+}) {
+ return (
+
+ )
+}
+
+function DropdownMenuCheckboxItem({
+ className,
+ children,
+ checked,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ )
+}
+
+function DropdownMenuRadioGroup({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DropdownMenuRadioItem({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ )
+}
+
+function DropdownMenuLabel({
+ className,
+ inset,
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean
+}) {
+ return (
+
+ )
+}
+
+function DropdownMenuSeparator({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DropdownMenuShortcut({
+ className,
+ ...props
+}: React.ComponentProps<"span">) {
+ return (
+
+ )
+}
+
+function DropdownMenuSub({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DropdownMenuSubTrigger({
+ className,
+ inset,
+ children,
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean
+}) {
+ return (
+
+ {children}
+
+
+ )
+}
+
+function DropdownMenuSubContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ DropdownMenu,
+ DropdownMenuPortal,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuLabel,
+ DropdownMenuItem,
+ DropdownMenuCheckboxItem,
+ DropdownMenuRadioGroup,
+ DropdownMenuRadioItem,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuSub,
+ DropdownMenuSubTrigger,
+ DropdownMenuSubContent,
+}
diff --git a/src/components/ui/form.tsx b/src/components/ui/form.tsx
new file mode 100644
index 00000000..657c64a6
--- /dev/null
+++ b/src/components/ui/form.tsx
@@ -0,0 +1,168 @@
+'use client';
+
+import * as React from 'react';
+import * as LabelPrimitive from '@radix-ui/react-label';
+import { Slot } from '@radix-ui/react-slot';
+import {
+ Controller,
+ FormProvider,
+ useFormContext,
+ useFormState,
+ type ControllerProps,
+ type FieldPath,
+ type FieldValues,
+} from 'react-hook-form';
+
+import { cn } from '@/lib/utils';
+import { Label } from '@/components/ui/label';
+
+const Form = FormProvider;
+
+type FormFieldContextValue<
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath = FieldPath,
+> = {
+ name: TName;
+};
+
+const FormFieldContext = React.createContext(
+ {} as FormFieldContextValue,
+);
+
+const FormField = <
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath = FieldPath,
+>({
+ ...props
+}: ControllerProps) => {
+ return (
+
+
+
+ );
+};
+
+const useFormField = () => {
+ const fieldContext = React.useContext(FormFieldContext);
+ const itemContext = React.useContext(FormItemContext);
+ const { getFieldState } = useFormContext();
+ const formState = useFormState({ name: fieldContext.name });
+ const fieldState = getFieldState(fieldContext.name, formState);
+
+ if (!fieldContext) {
+ throw new Error('useFormField should be used within ');
+ }
+
+ const { id } = itemContext;
+
+ return {
+ id,
+ name: fieldContext.name,
+ formItemId: `${id}-form-item`,
+ formDescriptionId: `${id}-form-item-description`,
+ formMessageId: `${id}-form-item-message`,
+ ...fieldState,
+ };
+};
+
+type FormItemContextValue = {
+ id: string;
+};
+
+const FormItemContext = React.createContext(
+ {} as FormItemContextValue,
+);
+
+function FormItem({ className, ...props }: React.ComponentProps<'div'>) {
+ const id = React.useId();
+
+ return (
+
+
+
+ );
+}
+
+function FormLabel({
+ className,
+ ...props
+}: React.ComponentProps) {
+ const { error, formItemId } = useFormField();
+
+ return (
+
+ );
+}
+
+function FormControl({ ...props }: React.ComponentProps) {
+ const { error, formItemId, formDescriptionId, formMessageId } =
+ useFormField();
+
+ return (
+
+ );
+}
+
+function FormDescription({ className, ...props }: React.ComponentProps<'p'>) {
+ const { formDescriptionId } = useFormField();
+
+ return (
+
+ );
+}
+
+function FormMessage({ className, ...props }: React.ComponentProps<'p'>) {
+ const { error, formMessageId } = useFormField();
+ const body = error ? String(error?.message ?? '') : props.children;
+
+ if (!body) {
+ return null;
+ }
+
+ return (
+
+ {body}
+
+ );
+}
+
+export {
+ useFormField,
+ Form,
+ FormItem,
+ FormLabel,
+ FormControl,
+ FormDescription,
+ FormMessage,
+ FormField,
+};
diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx
new file mode 100644
index 00000000..3c1cfcaf
--- /dev/null
+++ b/src/components/ui/input.tsx
@@ -0,0 +1,21 @@
+import * as React from 'react';
+
+import { cn } from '@/lib/utils';
+
+function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
+ return (
+
+ );
+}
+
+export { Input };
diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx
new file mode 100644
index 00000000..1f7ae512
--- /dev/null
+++ b/src/components/ui/label.tsx
@@ -0,0 +1,24 @@
+'use client';
+
+import * as React from 'react';
+import * as LabelPrimitive from '@radix-ui/react-label';
+
+import { cn } from '@/lib/utils';
+
+function Label({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export { Label };
diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx
new file mode 100644
index 00000000..0e285c1d
--- /dev/null
+++ b/src/components/ui/popover.tsx
@@ -0,0 +1,48 @@
+'use client';
+
+import * as React from 'react';
+import * as PopoverPrimitive from '@radix-ui/react-popover';
+
+import { cn } from '@/lib/utils';
+
+function Popover({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function PopoverTrigger({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function PopoverContent({
+ className,
+ align = 'center',
+ sideOffset = 4,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ );
+}
+
+function PopoverAnchor({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };
diff --git a/src/components/ui/progress.tsx b/src/components/ui/progress.tsx
new file mode 100644
index 00000000..3cd10c17
--- /dev/null
+++ b/src/components/ui/progress.tsx
@@ -0,0 +1,30 @@
+'use client';
+
+import { cn } from '@/lib/utils';
+import * as ProgressPrimitive from '@radix-ui/react-progress';
+import * as React from 'react';
+
+function Progress({
+ className,
+ value,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ );
+}
+
+export { Progress };
diff --git a/src/components/ui/radio-group.tsx b/src/components/ui/radio-group.tsx
new file mode 100644
index 00000000..5e6778cb
--- /dev/null
+++ b/src/components/ui/radio-group.tsx
@@ -0,0 +1,45 @@
+"use client"
+
+import * as React from "react"
+import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
+import { CircleIcon } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+function RadioGroup({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function RadioGroupItem({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+ )
+}
+
+export { RadioGroup, RadioGroupItem }
diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx
new file mode 100644
index 00000000..dcbbc0ca
--- /dev/null
+++ b/src/components/ui/select.tsx
@@ -0,0 +1,185 @@
+"use client"
+
+import * as React from "react"
+import * as SelectPrimitive from "@radix-ui/react-select"
+import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+function Select({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SelectGroup({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SelectValue({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SelectTrigger({
+ className,
+ size = "default",
+ children,
+ ...props
+}: React.ComponentProps & {
+ size?: "sm" | "default"
+}) {
+ return (
+
+ {children}
+
+
+
+
+ )
+}
+
+function SelectContent({
+ className,
+ children,
+ position = "popper",
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+ {children}
+
+
+
+
+ )
+}
+
+function SelectLabel({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SelectItem({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ )
+}
+
+function SelectSeparator({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SelectScrollUpButton({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+function SelectScrollDownButton({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+export {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectLabel,
+ SelectScrollDownButton,
+ SelectScrollUpButton,
+ SelectSeparator,
+ SelectTrigger,
+ SelectValue,
+}
diff --git a/src/components/ui/separator.tsx b/src/components/ui/separator.tsx
new file mode 100644
index 00000000..275381ca
--- /dev/null
+++ b/src/components/ui/separator.tsx
@@ -0,0 +1,28 @@
+"use client"
+
+import * as React from "react"
+import * as SeparatorPrimitive from "@radix-ui/react-separator"
+
+import { cn } from "@/lib/utils"
+
+function Separator({
+ className,
+ orientation = "horizontal",
+ decorative = true,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Separator }
diff --git a/src/components/ui/skeleton.tsx b/src/components/ui/skeleton.tsx
new file mode 100644
index 00000000..32ea0ef7
--- /dev/null
+++ b/src/components/ui/skeleton.tsx
@@ -0,0 +1,13 @@
+import { cn } from "@/lib/utils"
+
+function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+export { Skeleton }
diff --git a/src/components/ui/sonner.tsx b/src/components/ui/sonner.tsx
new file mode 100644
index 00000000..564a6547
--- /dev/null
+++ b/src/components/ui/sonner.tsx
@@ -0,0 +1,25 @@
+'use client';
+
+import { useTheme } from 'next-themes';
+import { Toaster as Sonner, ToasterProps } from 'sonner';
+
+const Toaster = ({ ...props }: ToasterProps) => {
+ const { theme = 'system' } = useTheme();
+
+ return (
+
+ );
+};
+
+export { Toaster };
diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx
new file mode 100644
index 00000000..c481005c
--- /dev/null
+++ b/src/components/ui/tabs.tsx
@@ -0,0 +1,66 @@
+'use client';
+
+import * as TabsPrimitive from '@radix-ui/react-tabs';
+import * as React from 'react';
+
+import { cn } from '@/lib/utils';
+
+function Tabs({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function TabsList({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function TabsTrigger({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function TabsContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export { Tabs, TabsContent, TabsList, TabsTrigger };
diff --git a/src/components/ui/tooltip.tsx b/src/components/ui/tooltip.tsx
new file mode 100644
index 00000000..98415dfb
--- /dev/null
+++ b/src/components/ui/tooltip.tsx
@@ -0,0 +1,68 @@
+'use client';
+
+import * as TooltipPrimitive from '@radix-ui/react-tooltip';
+import * as React from 'react';
+
+import { cn } from '@/lib/utils';
+
+function TooltipProvider({
+ delayDuration = 0,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function Tooltip({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ );
+}
+
+function TooltipTrigger({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function TooltipContent({
+ className,
+ sideOffset = 0,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+ {children}
+
+
+
+ );
+}
+
+export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
diff --git a/src/features/auth/components/AutoLoginClientManager.tsx b/src/features/auth/components/AutoLoginClientManager.tsx
new file mode 100644
index 00000000..659f57ac
--- /dev/null
+++ b/src/features/auth/components/AutoLoginClientManager.tsx
@@ -0,0 +1,41 @@
+'use client';
+
+import useAuthStore from '@/stores/useAuthStore';
+import { useEffect } from 'react';
+
+type AuthClientProviderProps = {
+ hasRefreshToken: boolean;
+};
+
+// localStorage์ ์๋ฒ๊ฐ ๋ถ์ผ์น ํ ์๋ ์์ด์ ์ฌ์ ์์ ํ๋กํ 1ํ ์
๋ฐ์ดํธ
+const AutoLoginClientManager = ({
+ hasRefreshToken,
+}: AuthClientProviderProps) => {
+ const fetchAndSetUser = useAuthStore((s) => s.fetchAndSetUser);
+ const clearUser = useAuthStore((s) => s.clearUser);
+
+ // ์ฌ์ ์์ ๋ฌด์กฐ๊ฑด ํ๋ฒ๋ง ์คํ๋์ผํจ!! 0๋ฒ๋ ์๋๊ณ 2๋ฒ๋ ์๋จ
+ useEffect(() => {
+ const getProfile = async () => {
+ try {
+ // ํ์์ ๋ณด ๋ถ๋ฌ์ค๊ธฐ /me
+ await fetchAndSetUser();
+ } catch (e) {
+ console.log(e);
+ clearUser();
+ }
+ };
+
+ if (hasRefreshToken) {
+ getProfile();
+ } else {
+ clearUser();
+ }
+
+ // eslint-disable-next-line
+ }, []);
+
+ return null;
+};
+
+export default AutoLoginClientManager;
diff --git a/src/features/auth/components/AutoLoginManager.tsx b/src/features/auth/components/AutoLoginManager.tsx
new file mode 100644
index 00000000..19402e74
--- /dev/null
+++ b/src/features/auth/components/AutoLoginManager.tsx
@@ -0,0 +1,11 @@
+import AutoLoginClientManager from '@/features/auth/components/AutoLoginClientManager';
+import { cookies } from 'next/headers';
+
+// ์ฌ์ฉ์ ์ ์์ http only ์ฟ ํค ์ฌ๋ถ๋ฅผ ํ๋จํฉ๋๋ค
+// refresh ํ๋จํ ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ์ธ AutoLoginClient์์ ์๋๋ก๊ทธ์ธ์ ํ ์ง ๊ฒฐ์ ํฉ๋๋ค
+export const AutoLoginManager = async () => {
+ const cookieStore = await cookies();
+ const hasRefreshToken = cookieStore.has('refreshToken');
+
+ return ;
+};
diff --git a/src/features/auth/components/ClientAuthGuard.tsx b/src/features/auth/components/ClientAuthGuard.tsx
new file mode 100644
index 00000000..5d9ba120
--- /dev/null
+++ b/src/features/auth/components/ClientAuthGuard.tsx
@@ -0,0 +1,23 @@
+'use client';
+
+import { useRouter } from 'next/navigation';
+import { useEffect } from 'react';
+import useAuthStore from '@/stores/useAuthStore';
+
+type ClientAuthGuardProps = {
+ children: React.ReactNode;
+};
+
+export default function ClientAuthGuard({ children }: ClientAuthGuardProps) {
+ const user = useAuthStore((state) => state.user);
+ const router = useRouter();
+
+ useEffect(() => {
+ if (!user) {
+ router.replace('/login');
+ }
+ // eslint-disable-next-line
+ }, [user]);
+
+ return <>{children}>;
+}
diff --git a/src/features/auth/components/LoginTriggerManager.tsx b/src/features/auth/components/LoginTriggerManager.tsx
new file mode 100644
index 00000000..e2aedfd8
--- /dev/null
+++ b/src/features/auth/components/LoginTriggerManager.tsx
@@ -0,0 +1,33 @@
+'use client';
+
+import { useRouter } from 'next/navigation';
+import { useEffect } from 'react';
+
+type TriggerManagerProps = {
+ prevPathname: string;
+};
+
+const LoginTriggerManager = ({ prevPathname }: TriggerManagerProps) => {
+ const router = useRouter();
+ useEffect(() => {
+ // ๋ค๋ก๊ฐ๊ธฐ ๋ฒํผ๊ฐ์ ๊ฒฝ๋ก๋ก ๋ก๊ทธ์ธ ํ์ด์ง ์ ๊ทผ์ layout์ด ์บ์ฌ๋๊ฑธ ๋ถ๋ฌ์์ ์ฟ ํค ๋ค์ ํ์ธ์ฉ
+ router.refresh();
+
+ // ์ธ์ฆ ํ์ด์ง๋ค ์ฌ์ด์์ ์ด๋ํ๋ ๊ฒฝ์ฐ ์ ์ฅํ์ง ์๋๋ก ์ฒ๋ฆฌ!!!!!!!!!!!!!!!
+ if (
+ prevPathname === '/login' ||
+ prevPathname === '/register' ||
+ prevPathname === '/find-email' ||
+ prevPathname === '/find-password'
+ ) {
+ return;
+ }
+
+ localStorage.setItem('login-trigger-path', prevPathname);
+ // eslint-disable-next-line
+ }, []);
+
+ return null;
+};
+
+export default LoginTriggerManager;
diff --git a/src/features/auth/index.ts b/src/features/auth/index.ts
deleted file mode 100644
index e69de29b..00000000
diff --git a/src/features/auth/utils/addBookmarkWhenAuth.ts b/src/features/auth/utils/addBookmarkWhenAuth.ts
new file mode 100644
index 00000000..f684fcb5
--- /dev/null
+++ b/src/features/auth/utils/addBookmarkWhenAuth.ts
@@ -0,0 +1,24 @@
+import { request } from '@/api/request';
+
+// ๋ถ๋งํฌ ์ ๋ณด๋ฅผ ๋ก๊ทธ์ธ ํ ์๋ฒ์ ์ ์ฅํ๋ ํจ์
+const addBookmarkWhenAuth = async (bookmarkListStr: string) => {
+ let bookmarkList: number[] = [];
+ bookmarkList = JSON.parse(bookmarkListStr) as number[];
+
+ try {
+ // TODO: api ์์ ์์ฒญ
+ await request.post(
+ `/v2/bookmark/addids`,
+ { 'Content-Type': 'application/json' },
+ JSON.stringify({ groupIds: bookmarkList }),
+ { credentials: 'include' },
+ );
+
+ // db์ ๋ถ๋งํฌ ์ถ๊ฐ ์ฑ๊ณต ํ ๋ก์ปฌ ์คํ ๋ฆฌ์ง์์ ๋ถ๋งํฌ ์ ๋ณด ์ญ์
+ localStorage.removeItem('bookmarkList');
+ } catch (e) {
+ console.error('๋ถ๋งํฌ ์ถ๊ฐ ์ค ์ค๋ฅ ๋ฐ์:', e);
+ }
+};
+
+export default addBookmarkWhenAuth;
diff --git a/src/features/auth/utils/checkAuthCookie.ts b/src/features/auth/utils/checkAuthCookie.ts
new file mode 100644
index 00000000..7df4fe2c
--- /dev/null
+++ b/src/features/auth/utils/checkAuthCookie.ts
@@ -0,0 +1,56 @@
+import { cookies } from 'next/headers';
+import { UserInfoResponse } from '@/types/response';
+
+// ์์
+const checkAuthCookie = async () => {
+ const cookieStore = await cookies();
+
+ const accessToken = cookieStore.get('accessToken');
+ const refreshToken = cookieStore.get('refreshToken');
+
+ // ๋๊ฐ๋ค ์๋ ๊ฒฝ์ฐ
+ if (accessToken && refreshToken) {
+ let cookieString = '';
+
+ if (accessToken) {
+ cookieString += `${process.env.ACCESS_TOKEN}=${accessToken.value}; `;
+ }
+ if (refreshToken) {
+ cookieString += `${process.env.REFRESH_TOKEN}=${refreshToken.value}; `;
+ }
+
+ try {
+ const response = await fetch(
+ process.env.NEXT_PUBLIC_API_BASE_URL + '/v1/user/info',
+ {
+ headers: {
+ Cookie: cookieString,
+ },
+ },
+ );
+
+ if (!response.ok) {
+ throw new Error('Failed to fetch user info');
+ }
+
+ const responseBody: UserInfoResponse = await response.json();
+
+ return responseBody.status.success;
+ } catch (e) {
+ // refreshToken์ด ๋ง๋ฃ๋์๊ฑฐ๋, accessToken์ด ๋ง๋ฃ๋ ๊ฒฝ์ฐ
+ // TODO: refresh token์ ์ด์ฉํด์ accessToken์ ๊ฐฑ์ ํ๋ ๋ก์ง ์ถ๊ฐ ํ์
+ console.log(e);
+ return false;
+ }
+ }
+
+ // accessToken์ด ์๊ณ refreshToken์ด ์๋ ๊ฒฝ์ฐ
+ if (!accessToken && refreshToken) {
+ // refreshToken์ด ๋ง๋ฃ๋์๊ฑฐ๋, accessToken์ด ๋ง๋ฃ๋ ๊ฒฝ์ฐ
+ return false;
+ }
+
+ return false;
+};
+
+export default checkAuthCookie;
diff --git a/src/features/auth/utils/fetchRefreshToken.ts b/src/features/auth/utils/fetchRefreshToken.ts
new file mode 100644
index 00000000..49c1f9cd
--- /dev/null
+++ b/src/features/auth/utils/fetchRefreshToken.ts
@@ -0,0 +1,38 @@
+'use client';
+
+// ๋ฌด์กฐ๊ฑด ํด๋ผ์ด์ธํธ์์
+// ์ฑ๊ณต ์ฌ๋ถ๋ฅผ ๋ฐํํ๋ ํจ์
+const fetchRefreshToken = async () => {
+ try {
+ // ์ฟ ํค๋ฅผ ์๋ก๊ณ ์นจํ๊ธฐ ์ํ ์์ฒญ
+ const response = await fetch(
+ process.env.NEXT_PUBLIC_API_BASE_URL + '/v1/user/refresh',
+ {
+ method: 'POST',
+ credentials: 'include', // ์ฟ ํค๋ฅผ ํฌํจํ์ฌ ์์ฒญ
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ },
+ );
+
+ if (!response.ok) {
+ throw new Error('ํ ํฐ ๊ฐฑ์ ์์ฒญ ์คํจ');
+ }
+
+ const {
+ status: { success },
+ } = await response.json();
+
+ if (!success) {
+ throw new Error('ํ ํฐ ๊ฐฑ์ ์คํจ');
+ }
+
+ return success as boolean;
+ } catch (error) {
+ console.error('token ์๋ก ๊ณ ์นจ ์ค ์ค๋ฅ ๋ฐ์:', error);
+ return false;
+ }
+};
+
+export default fetchRefreshToken;
diff --git a/src/features/auth/utils/setUserInfo.ts b/src/features/auth/utils/setUserInfo.ts
new file mode 100644
index 00000000..a038f078
--- /dev/null
+++ b/src/features/auth/utils/setUserInfo.ts
@@ -0,0 +1,32 @@
+import { request } from '@/api/request';
+import { User } from '@/types';
+import { UserInfoResponse } from '@/types/response';
+
+// ISSUE: ์คํ ์ด์ ๋ฃ์ผ๋ฉด ์ข์๊ฑฐ ๊ฐ์์ ์ผ๋จ ์ ๊ฐ ์์
ํ๊ฑฐ๋ผ ๋ถ๋ฆฌํด๋์ด์
+export const fetchAndSetUser = async (
+ setUser: (user: User) => void,
+): Promise => {
+ try {
+ const responseBody: UserInfoResponse = await request.get(
+ '/v1/user/info',
+ {},
+ {
+ credentials: 'include',
+ },
+ );
+
+ if (responseBody.status.success) {
+ setUser({
+ //@ts-expect-error ๋ฐฑ์๋์์ ์ ๊ณตํ๋ ํ์
์ด ์ด์ํด์ ์์๋ก ์ฒ๋ฆฌ
+ userId: responseBody.items.items.id,
+ ...responseBody.items.items,
+ });
+ } else {
+ throw new Error('์ ์ ์ ๋ณด ๋ถ๋ฌ์ค๊ธฐ ์คํจ');
+ }
+ } catch (error) {
+ console.error('์ ์ ์ ๋ณด ๋ถ๋ฌ์ค๊ธฐ ์คํจ', error);
+ // ํ์์ ์๋ฌ ์ฒ๋ฆฌ ๋ก์ง ์ถ๊ฐ
+ throw error;
+ }
+};
diff --git a/src/features/bookmark/components/bookmark-button-container.tsx b/src/features/bookmark/components/bookmark-button-container.tsx
new file mode 100644
index 00000000..0ee8ce43
--- /dev/null
+++ b/src/features/bookmark/components/bookmark-button-container.tsx
@@ -0,0 +1,67 @@
+'use client';
+
+import { request } from '@/api/request';
+import { BookmarkButton } from '@/features/bookmark/components/bookmark-button';
+import useAuthStore from '@/stores/useAuthStore';
+import { useMutation } from '@tanstack/react-query';
+import { useEffect, useState } from 'react';
+import { toast } from 'sonner';
+import { addBookmarkItem, getBookmarkList, removeBookmarkItem } from '../utils';
+
+type BookmarkButtonContainerProps = {
+ groupId: number;
+ isBookmark: boolean;
+};
+
+/**
+ * ๋ถ๋งํฌ ๋ฒํผ์ ์ํ(isBookmark)๋ฅผ ๋ด๋ถ์์ ์ฒ๋ฆฌํ๋ ์ปจํ
์ด๋ ์ปดํฌ๋ํธ
+ *
+ * - ๋ก๊ทธ์ธํ์ง ์์ ์ฌ์ฉ์์ ๊ฒฝ์ฐ, localStorage์์ ๋ถ๋งํฌ ์ฌ๋ถ๋ฅผ ํ์ธํฉ๋๋ค.
+ */
+export const BookmarkButtonContainer = ({
+ groupId,
+ isBookmark: initialIsBookmark,
+}: BookmarkButtonContainerProps) => {
+ const user = useAuthStore((state) => state.user);
+ const [isBookmark, setIsBookmark] = useState(initialIsBookmark);
+
+ const { mutate } = useMutation({
+ mutationFn: (nextBookmarkStatus: boolean) =>
+ request.patch(
+ '/v2/bookmark',
+ { 'Content-Type': 'application/json' },
+ { groupId, isBookmark: nextBookmarkStatus },
+ { credentials: 'include' },
+ ),
+ onError: () => {
+ toast.error('์ฐํ๊ธฐ์ ์คํจํ์ต๋๋ค.');
+ setIsBookmark((prev) => !prev);
+ },
+ });
+
+ useEffect(() => {
+ if (!user) {
+ const bookmarkList = getBookmarkList();
+ setIsBookmark(bookmarkList.includes(groupId));
+ }
+ }, [groupId, user]);
+
+ const toggleBookmark = async () => {
+ const nextBookmarkState = !isBookmark;
+ setIsBookmark(nextBookmarkState);
+
+ if (!user) {
+ if (nextBookmarkState) addBookmarkItem(groupId);
+ else removeBookmarkItem(groupId);
+ } else {
+ mutate(nextBookmarkState);
+ }
+ };
+
+ return (
+
+ );
+};
diff --git a/src/features/bookmark/components/bookmark-button.tsx b/src/features/bookmark/components/bookmark-button.tsx
new file mode 100644
index 00000000..11178d95
--- /dev/null
+++ b/src/features/bookmark/components/bookmark-button.tsx
@@ -0,0 +1,24 @@
+'use client';
+
+import Image from 'next/image';
+
+type Props = {
+ isBookmark: boolean;
+ bookmarkToggleHandler: () => void;
+};
+
+export const BookmarkButton = ({
+ isBookmark,
+ bookmarkToggleHandler,
+}: Props) => {
+ return (
+
+ );
+};
diff --git a/src/features/bookmark/utils/index.ts b/src/features/bookmark/utils/index.ts
new file mode 100644
index 00000000..aa1c7b20
--- /dev/null
+++ b/src/features/bookmark/utils/index.ts
@@ -0,0 +1,25 @@
+/** localStorage์์ ์ฐ ๋ชฉ๋ก์ ์กฐํํ๋ ํจ์ */
+export const getBookmarkList = () => {
+ let bookmarkList: number[] = [];
+ const bookmarkListStr = localStorage.getItem('bookmarkList');
+
+ if (bookmarkListStr !== null) {
+ bookmarkList = JSON.parse(bookmarkListStr) as number[];
+ }
+
+ return bookmarkList;
+};
+
+/** ํด๋น ์์๋ฅผ localStorage ์ฐ ๋ชฉ๋ก์ ์ถ๊ฐํ๋ ํจ์ */
+export const addBookmarkItem = (itemId: number) => {
+ const bookmarkList = getBookmarkList();
+ bookmarkList.push(itemId);
+ localStorage.setItem('bookmarkList', JSON.stringify(bookmarkList));
+};
+
+/** ํด๋น ์์๋ฅผ localStorage ์ฐ ๋ชฉ๋ก์์ ์ ๊ฑฐํ๋ ํจ์ */
+export const removeBookmarkItem = (itemId: number) => {
+ let bookmarkList = getBookmarkList();
+ bookmarkList = bookmarkList.filter((bookmark) => bookmark !== itemId);
+ localStorage.setItem('bookmarkList', JSON.stringify(bookmarkList));
+};
diff --git a/src/features/group/components/apply-join-button.tsx b/src/features/group/components/apply-join-button.tsx
new file mode 100644
index 00000000..46628a5b
--- /dev/null
+++ b/src/features/group/components/apply-join-button.tsx
@@ -0,0 +1,37 @@
+'use client';
+
+import { request } from '@/api/request';
+import { LoginRequireButton } from '@/components/atoms/login-require-button';
+import { useMutation } from '@tanstack/react-query';
+import { useParams } from 'next/navigation';
+import { toast } from 'sonner';
+
+export const ApplyJoinButton = ({ onSuccess }: { onSuccess: () => void }) => {
+ const { groupId } = useParams<{ groupId: string }>();
+ const { mutate, isPending } = useMutation({
+ mutationFn: () =>
+ request.post(
+ `/v2/groups/${groupId}/applications`,
+ {},
+ JSON.stringify({}),
+ { credentials: 'include' },
+ ),
+ onError: () => {
+ toast.error('์ฐธ์ฌ ์ ์ฒญ์ ์คํจํ์์ต๋๋ค. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.');
+ },
+ onSuccess: () => {
+ onSuccess();
+ toast.success('์ฐธ์ฌ ์ ์ฒญ์ด ๋์์ต๋๋ค.');
+ },
+ });
+
+ const joinGroupHandler = () => {
+ mutate();
+ };
+
+ return (
+
+ ์ฐธ์ฌ ์ ์ฒญ
+
+ );
+};
diff --git a/src/features/group/components/cancel-group-button.tsx b/src/features/group/components/cancel-group-button.tsx
new file mode 100644
index 00000000..f2584b29
--- /dev/null
+++ b/src/features/group/components/cancel-group-button.tsx
@@ -0,0 +1,38 @@
+'use client';
+
+import { request } from '@/api/request';
+import { Button } from '@/components/ui/button';
+import { useMutation } from '@tanstack/react-query';
+import { useParams, useRouter } from 'next/navigation';
+import { toast } from 'sonner';
+
+export const CancelGroupButton = () => {
+ const router = useRouter();
+ const { groupId } = useParams<{ groupId: string }>();
+
+ const { mutate, isPending } = useMutation({
+ mutationFn: () =>
+ request.delete(`/v2/groups/${groupId}`, { credentials: 'include' }),
+ onError: () => {
+ toast.error('๋ชจ์ ์ทจ์์ ์คํจํ์์ต๋๋ค. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.');
+ },
+ onSuccess: () => {
+ router.push('/');
+ toast.success('๋ชจ์์ด ์ทจ์๋์์ต๋๋ค.');
+ },
+ });
+
+ const cancelGroupHandler = () => {
+ mutate();
+ };
+
+ return (
+
+ );
+};
diff --git a/src/features/group/components/cancel-join-button.tsx b/src/features/group/components/cancel-join-button.tsx
new file mode 100644
index 00000000..a89ca702
--- /dev/null
+++ b/src/features/group/components/cancel-join-button.tsx
@@ -0,0 +1,38 @@
+import { request } from '@/api/request';
+import { Button } from '@/components/ui/button';
+import { useMutation } from '@tanstack/react-query';
+import { useParams } from 'next/navigation';
+import { toast } from 'sonner';
+
+export const CancelJoinButton = ({ onSuccess }: { onSuccess: () => void }) => {
+ const { groupId } = useParams<{ groupId: string }>();
+ const { mutate, isPending } = useMutation({
+ mutationFn: () =>
+ request.delete(`/v2/groups/${groupId}/applications`, {
+ credentials: 'include',
+ }),
+ onError: () => {
+ toast.error(
+ '์ฐธ์ฌ ์ ์ฒญ ์ทจ์์ ์คํจํ์์ต๋๋ค. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.',
+ );
+ },
+ onSuccess: () => {
+ onSuccess();
+ toast.success('์ฐธ์ฌ ์ ์ฒญ์ด ์ทจ์๋์์ต๋๋ค.');
+ },
+ });
+
+ const cancelJoinHandler = () => {
+ mutate();
+ };
+
+ return (
+
+ );
+};
diff --git a/src/features/group/components/group-action-buttons.tsx b/src/features/group/components/group-action-buttons.tsx
new file mode 100644
index 00000000..527fc2bd
--- /dev/null
+++ b/src/features/group/components/group-action-buttons.tsx
@@ -0,0 +1,68 @@
+'use client';
+
+import { invalidateTag } from '@/actions/invalidate';
+import { ShareButton } from '@/components/atoms/share-button';
+import { ApplyJoinButton } from '@/features/group/components/apply-join-button';
+import { CancelGroupButton } from '@/features/group/components/cancel-group-button';
+import { CancelJoinButton } from '@/features/group/components/cancel-join-button';
+import useAuthStore from '@/stores/useAuthStore';
+import { useState } from 'react';
+
+type GroupActionButtonsProps = {
+ groupId: number;
+ hostId: number;
+ isApplicant: boolean;
+ isJoined: boolean;
+ autoAllow: boolean;
+};
+
+export const GroupActionButtons = ({
+ groupId,
+ hostId,
+ isApplicant,
+ isJoined,
+ autoAllow,
+}: GroupActionButtonsProps) => {
+ const user = useAuthStore((state) => state.user);
+ const [userType, setUserType] = useState<
+ 'none' | 'host' | 'applicant' | 'nonApplicant'
+ >(() => {
+ if (user === null) return 'none';
+ if (user?.userId === hostId) return 'host';
+ if (isApplicant) return 'applicant';
+ return 'nonApplicant';
+ });
+
+ const successApply = () => {
+ setUserType('applicant');
+
+ // ์๋ ์๋ฝ์ผ ๊ฒฝ์ฐ, ๋ชจ์ ์์ธ ํ์ด์ง ์๋ก ๋ถ๋ฌ์ค๊ธฐ
+ if (autoAllow) {
+ invalidateTag(`group-detail-${groupId}`);
+ }
+ };
+
+ const successCancelApply = () => {
+ setUserType('nonApplicant');
+
+ // ์๋ฝ๋ ์ฐธ์ฌ์์ผ ๊ฒฝ์ฐ, ๋ชจ์ ์์ธ ํ์ด์ง ์๋ก ๋ถ๋ฌ์ค๊ธฐ
+ if (isJoined) {
+ invalidateTag(`group-detail-${groupId}`);
+ }
+ };
+
+ if (userType === 'host') {
+ return (
+ <>
+
+
+ >
+ );
+ }
+
+ if (userType === 'applicant') {
+ return ;
+ }
+
+ return ;
+};
diff --git a/src/features/group/components/group-description.tsx b/src/features/group/components/group-description.tsx
new file mode 100644
index 00000000..9923ac89
--- /dev/null
+++ b/src/features/group/components/group-description.tsx
@@ -0,0 +1,26 @@
+'use client';
+
+import { GroupType, GroupTypeName } from '@/types';
+import DOMPurify from 'isomorphic-dompurify';
+
+const sanitizeHTML = (markdown: string) => {
+ const renderedHTML = DOMPurify.sanitize(markdown);
+ return { __html: renderedHTML };
+};
+
+export const GroupDescription = ({
+ description,
+ groupType,
+}: {
+ description: string;
+ groupType: GroupType;
+}) => {
+ return (
+
+
+ {GroupTypeName[groupType]} ์๊ฐ
+
+
+
+ );
+};
diff --git a/src/features/group/components/group-detail-card.tsx b/src/features/group/components/group-detail-card.tsx
new file mode 100644
index 00000000..885d1b7c
--- /dev/null
+++ b/src/features/group/components/group-detail-card.tsx
@@ -0,0 +1,148 @@
+import { Avatar } from '@/components/atoms/avatar';
+import { Badge } from '@/components/atoms/badge';
+import { GroupProgress } from '@/components/atoms/group/particiapant-progress';
+import { PositionBadge } from '@/components/molecules/position-badge';
+import { SkillBadge } from '@/components/molecules/skill-badge';
+import { BookmarkButtonContainer } from '@/features/bookmark/components/bookmark-button-container';
+import { ParticipantListModal } from '@/features/group/components/participant-list-modal';
+import { GroupDetail, GroupTypeName } from '@/types';
+import { Position, Skill } from '@/types/enums';
+import { formatYearMonthDayWithDot } from '@/utils/dateUtils';
+import { getDisplayNickname, getDisplayProfileImage } from '@/utils/fallback';
+import Link from 'next/link';
+
+type GroupDetailCardProps = {
+ info: GroupDetail;
+ isRecruiting: boolean;
+};
+
+export const GroupDetailCard = ({
+ info,
+ isRecruiting,
+}: GroupDetailCardProps) => {
+ return (
+
+
+
+
+
+
+
+
+
+ {formatYearMonthDayWithDot(new Date(info.group.deadline))}
+
+
+
+ {formatYearMonthDayWithDot(new Date(info.group.startDate))} ~{' '}
+ {formatYearMonthDayWithDot(new Date(info.group.endDate))}
+
+
+
+
+ {info.group.position.map((position, i) => (
+ -
+
+
+ ))}
+
+
+
+
+
+ {info.group.skills?.map((skill, i) => (
+ -
+
+
+ ))}
+
+
+
+
+
+ ๋ชจ์ง ํํฉ ({isRecruiting ? '๋ชจ์ง ์ค' : '๋ชจ์ง ๋ง๊ฐ'})
+
+
+
+
+
+
+
+
+
+ {info.group.participants
+ .slice(0, 5)
+ .map(({ userId, profileImage, email, nickname }, index) => (
+
+ ))}
+
0 ? '-ml-3' : ''
+ }`}
+ />
+
+
+
+
+
+ );
+};
+
+const getZIndexClass = (index: number) => {
+ const zIndexMap = ['z-10', 'z-20', 'z-30', 'z-40', 'z-50'];
+ return zIndexMap[index] ?? 'z-0';
+};
+
+const GroupInfoItem = ({
+ label,
+ children,
+}: {
+ label: string;
+ children: React.ReactNode;
+}) => {
+ return (
+
+ );
+};
diff --git a/src/features/group/components/participant-list-modal.tsx b/src/features/group/components/participant-list-modal.tsx
new file mode 100644
index 00000000..300b5de8
--- /dev/null
+++ b/src/features/group/components/participant-list-modal.tsx
@@ -0,0 +1,51 @@
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from '@/components/ui/dialog';
+import { MemberInfo } from '@/features/user/group/components/member-list-modal/member-info';
+import { UserSummary } from '@/types';
+import { PlusIcon } from 'lucide-react';
+
+type ParticipantListModalProps = {
+ participants: UserSummary[];
+ className?: string;
+};
+
+export const ParticipantListModal = ({
+ participants,
+ className = '',
+}: ParticipantListModalProps) => {
+ return (
+
+ );
+};
diff --git a/src/features/rating/index.test.ts b/src/features/rating/index.test.ts
new file mode 100644
index 00000000..c5a82c42
--- /dev/null
+++ b/src/features/rating/index.test.ts
@@ -0,0 +1,53 @@
+import { request } from "../../api/request";
+import { setRating, updateRating } from "./index";
+import { server } from '../../mocks/server';
+
+describe("setRating", () => {
+ beforeAll(() => server.listen());
+ afterEach(() => server.resetHandlers());
+ afterAll(() => server.close());
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ jest.spyOn(request, 'post');
+ jest.spyOn(request, 'patch');
+ });
+
+ it("์ ์ ๋ฑ๋ก์ด ์ฑ๊ณตํ๋ฉด success: true๋ฅผ ๋ฐํํ๋ค", async () => {
+ const targetUserId = 1;
+ const rate = 4.5;
+ const response = await setRating(targetUserId, rate);
+
+ expect(request.post).toHaveBeenCalledWith(
+ '/rating',
+ { 'Content-Type': 'application/json' },
+ JSON.stringify({ rate, targetUserId })
+ );
+ expect(response.result.success).toBe(true);
+ });
+
+ it("5์ ์ ์ด๊ณผํ๋ ํ์ ์ ๋ฑ๋กํ๋ฉด success: false๋ฅผ ๋ฐํํ๋ค", async () => {
+ const targetUserId = 1;
+ const rate = 5.5; // 5์ ์ด๊ณผ
+
+ const response = await setRating(targetUserId, rate);
+
+ expect(request.post).toHaveBeenCalled();
+ expect(response.result.success).toBe(false);
+ });
+
+ it("ํ์ ์์ ์ด ์ฑ๊ณตํ๋ฉด success: true๋ฅผ ๋ฐํํ๋ค", async () => {
+ const ratingId = 1;
+ const rate = 4.5;
+
+ const response = await updateRating(ratingId, rate);
+
+ expect(request.patch).toHaveBeenCalledWith(
+ '/ratings',
+ { rate },
+ String(ratingId)
+ );
+ expect(response.result.success).toBe(true);
+ });
+
+});
diff --git a/src/features/rating/index.ts b/src/features/rating/index.ts
new file mode 100644
index 00000000..78b4d8c1
--- /dev/null
+++ b/src/features/rating/index.ts
@@ -0,0 +1,22 @@
+import { request } from '@/api/request';
+
+export const setRating = async (targetUserId: number, rate: number) => {
+ return await request.post(
+ '/rating',
+ {
+ 'Content-Type': 'application/json',
+ },
+ JSON.stringify({
+ rate,
+ targetUserId,
+ }),
+ );
+};
+
+export const updateRating = async (ratingId: number, rate: number) => {
+ return await request.patch(
+ `/ratings/${String(ratingId)}`,
+ { 'Content-Type': 'application/json' },
+ { rate },
+ );
+};
diff --git a/src/features/reply/components/add-rereply-button.tsx b/src/features/reply/components/add-rereply-button.tsx
new file mode 100644
index 00000000..32a7a786
--- /dev/null
+++ b/src/features/reply/components/add-rereply-button.tsx
@@ -0,0 +1,11 @@
+'use client';
+
+export const AddRereplyButton = ({ onClick }: { onClick: () => void }) => {
+ return (
+
+
+
+ );
+};
diff --git a/src/features/reply/components/reply-content.tsx b/src/features/reply/components/reply-content.tsx
new file mode 100644
index 00000000..819f97cb
--- /dev/null
+++ b/src/features/reply/components/reply-content.tsx
@@ -0,0 +1,132 @@
+'use client';
+
+import { request } from '@/api/request';
+import { Button } from '@/components/ui/button';
+import useAuthStore from '@/stores/useAuthStore';
+import { Reply } from '@/types';
+import { useMutation } from '@tanstack/react-query';
+import { useParams } from 'next/navigation';
+import { useState } from 'react';
+import { toast } from 'sonner';
+import { ReplyMeta } from './reply-meta';
+
+type ReplyContentProps = Reply & { parentId?: number; onDelete?: () => void };
+
+export const ReplyContent = ({
+ content: initalContent,
+ replyId,
+ writer,
+ createdAt,
+ parentId,
+ deleted: isDeleted, // ์ญ์ ๋ ๋๊ธ์ธ์ง ์ฌ๋ถ
+ onDelete,
+}: ReplyContentProps) => {
+ const { groupId } = useParams();
+ const [isEditing, setIsEditing] = useState(false);
+ const [isLocallyDeleted, setIsLocallyDeleted] = useState(isDeleted);
+ const [content, setContent] = useState(initalContent);
+ const user = useAuthStore((state) => state.user);
+
+ const isWriter = user && writer.userId == user.userId;
+
+ const { mutate: updateReply } = useMutation({
+ mutationFn: async (enteredContent: string) =>
+ request.patch(
+ `/v2/groups/${groupId}/replies/${replyId}`,
+ { 'Content-Type': 'application/json' },
+ {
+ content: enteredContent,
+ },
+ { credentials: 'include' },
+ ),
+ onSuccess: () => {
+ setIsEditing(false);
+ },
+ onError: () => {
+ toast.error('๋๊ธ ์์ ์ ์คํจํ์์ต๋๋ค.');
+ },
+ });
+
+ const { mutate: deleteReply } = useMutation({
+ mutationFn: async () =>
+ request.delete(`/v2/groups/${groupId}/replies/${replyId}`, {
+ credentials: 'include',
+ }),
+ onSuccess: () => {
+ // ์๋ฒ์์ ์ญ์ ์์ฒญ์ด ์ฑ๊ณตํ๋ฉด UI์์๋ ๋ฐ์
+ onDelete?.();
+ setIsLocallyDeleted(true);
+ },
+ onError: () => {
+ toast.error('๋๊ธ ์ญ์ ์ ์คํจํ์์ต๋๋ค.');
+ },
+ });
+
+ const editButtonClickHandler = () => {
+ setIsEditing(true);
+ };
+
+ const saveButtonClickHandler = () => {
+ if (!content.trim() || content === initalContent) return;
+ updateReply(content);
+ };
+
+ const deleteButtonClickHandler = () => {
+ setIsEditing(false);
+ deleteReply();
+ };
+
+ if (isLocallyDeleted && parentId) return null;
+
+ return (
+
+ );
+};
diff --git a/src/features/reply/components/reply-form.tsx b/src/features/reply/components/reply-form.tsx
new file mode 100644
index 00000000..ebbe3312
--- /dev/null
+++ b/src/features/reply/components/reply-form.tsx
@@ -0,0 +1,84 @@
+'use client';
+
+import { request } from '@/api/request';
+import { LoginRequireButton } from '@/components/atoms/login-require-button';
+import { useTargetReplyStore } from '@/stores/useTargetReply';
+import { useMutation, useQueryClient } from '@tanstack/react-query';
+import { useParams } from 'next/navigation';
+import { useState } from 'react';
+import { toast } from 'sonner';
+
+type ReplyFormProps = {
+ onSuccess?: () => void;
+ parentReplyId?: number;
+ isOpenRereplyList?: boolean;
+};
+
+export const ReplyForm = ({
+ onSuccess,
+ parentReplyId,
+ isOpenRereplyList,
+}: ReplyFormProps) => {
+ const { groupId } = useParams();
+ const [replyContent, setReplyContent] = useState('');
+ const setTargetReply = useTargetReplyStore((state) => state.setTargetReply);
+
+ const endpoint =
+ parentReplyId === undefined
+ ? `/v2/groups/${groupId}/replies`
+ : `/v2/groups/${groupId}/replies/${parentReplyId}`;
+
+ const queryClient = useQueryClient();
+
+ const { mutate } = useMutation({
+ mutationFn: (content: string) =>
+ request.post(
+ endpoint,
+ { 'Content-Type': 'application/json' },
+ JSON.stringify({ content }),
+ { credentials: 'include' },
+ ),
+ onSuccess: (data) => {
+ // ๋ชฉ๋ก ๋ฌดํจํ ํ, ์์ฑํ ๋๊ธ๋ก ์ด๋
+ queryClient.invalidateQueries({
+ queryKey: ['items', endpoint],
+ });
+
+ onSuccess?.();
+ setReplyContent('');
+
+ if (parentReplyId === undefined) {
+ setTargetReply({ targetReplyId: data.items, targetRereplyId: null });
+ } else if (!isOpenRereplyList) {
+ setTargetReply({
+ targetReplyId: parentReplyId,
+ targetRereplyId: data.items,
+ });
+ }
+ },
+ onError: () => {
+ toast.error('๋๊ธ ๋ฑ๋ก์ ์คํจํ์์ต๋๋ค.');
+ },
+ });
+
+ const submitReplyButtonClickHandler = async () => {
+ if (!replyContent.trim()) return;
+ mutate(replyContent);
+ };
+
+ return (
+
+ );
+};
diff --git a/src/features/reply/components/reply-item.tsx b/src/features/reply/components/reply-item.tsx
new file mode 100644
index 00000000..47bedac6
--- /dev/null
+++ b/src/features/reply/components/reply-item.tsx
@@ -0,0 +1,24 @@
+import { Reply } from '@/types';
+import { ReplyContent } from './reply-content';
+import { RereplySection } from './rereply-section';
+
+export const ReplyItem = ({
+ content,
+ writer,
+ createdAt,
+ replyId,
+ deleted,
+}: Reply) => {
+ return (
+
+ );
+};
diff --git a/src/features/reply/components/reply-list.tsx b/src/features/reply/components/reply-list.tsx
new file mode 100644
index 00000000..67530b97
--- /dev/null
+++ b/src/features/reply/components/reply-list.tsx
@@ -0,0 +1,94 @@
+'use client';
+
+import { useReplyScrollIntoView } from '@/features/reply/hooks/useReplyScrollIntoView';
+import { useTargetReplyParams } from '@/features/reply/hooks/useTargetReplyParams ';
+import { useFetchInView } from '@/hooks/useFetchInView';
+import { useFetchItems } from '@/hooks/useFetchItems';
+import { ReplyItem } from './reply-item';
+
+import { Loading } from '@/components/organisms/loading';
+import { useTargetReplyStore } from '@/stores/useTargetReply';
+import { Reply } from '@/types';
+import flattenPages from '@/utils/flattenPages';
+import { useParams } from 'next/navigation';
+import { useEffect } from 'react';
+
+const DATA_SIZE = 20;
+
+export const ReplyList = () => {
+ const { groupId } = useParams();
+ const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } =
+ useFetchItems({
+ url: `/v2/groups/${groupId}/replies`,
+ queryParams: {
+ size: DATA_SIZE,
+ },
+ options: {
+ staleTime: 0,
+ },
+ });
+
+ const { ref } = useFetchInView({
+ fetchNextPage,
+ isLoading,
+ isFetchingNextPage,
+ });
+
+ const { notificationTargetReplyId } = useTargetReplyParams();
+ const setTargetReply = useTargetReplyStore((state) => state.setTargetReply);
+
+ useEffect(() => {
+ if (notificationTargetReplyId) {
+ setTargetReply({
+ targetReplyId: notificationTargetReplyId,
+ });
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [notificationTargetReplyId]);
+
+ const { itemRefs: replyRefs, bottomRef } = useReplyScrollIntoView({
+ data,
+ replyType: 'reply',
+ hasNextPage,
+ });
+
+ const replies = flattenPages(data.pages);
+
+ if (isLoading) {
+ return (
+
+
+
+ );
+ }
+
+ if (replies.length === 0) {
+ return (
+
+ ์์ง ๋๊ธ์ด ์์ต๋๋ค.
+
+ );
+ }
+
+ return (
+
+
+ {replies.map((reply) => (
+ - {
+ replyRefs.current[reply.replyId] = el;
+ }}
+ >
+
+
+ ))}
+
+
+ {hasNextPage && !isFetchingNextPage && (
+
+ )}
+
+ );
+};
diff --git a/src/features/reply/components/reply-meta.tsx b/src/features/reply/components/reply-meta.tsx
new file mode 100644
index 00000000..ffd41f4f
--- /dev/null
+++ b/src/features/reply/components/reply-meta.tsx
@@ -0,0 +1,27 @@
+'use client';
+
+import { Avatar } from '@/components/atoms/avatar';
+import { Reply } from '@/types';
+import { formatDateTime } from '@/utils/dateUtils';
+import { getDisplayNickname, getDisplayProfileImage } from '@/utils/fallback';
+
+type ReplyMetaProps = Pick;
+
+export const ReplyMeta = ({ writer, createdAt }: ReplyMetaProps) => {
+ return (
+
+
{}}
+ />
+
+
{getDisplayNickname(writer.nickname, writer.email)}
+
+ {formatDateTime(createdAt)}
+
+
+
+ );
+};
diff --git a/src/features/reply/components/reply-section.tsx b/src/features/reply/components/reply-section.tsx
new file mode 100644
index 00000000..7111830e
--- /dev/null
+++ b/src/features/reply/components/reply-section.tsx
@@ -0,0 +1,28 @@
+'use client';
+
+import { ErrorBoundary } from '@/components/error-boundary';
+import { handleError } from '@/components/error-boundary/error-handler';
+import { ReplyForm } from './reply-form';
+import { ReplyList } from './reply-list';
+
+export const ReplySection = () => {
+ return (
+
+
+ ๋๊ธ
+
+
+ handleError({
+ error,
+ resetErrorBoundary,
+ defaultMessage: '๋๊ธ์ ๋ถ๋ฌ์ค๋ ์ค ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค',
+ })
+ }
+ >
+
+
+
+
+ );
+};
diff --git a/src/features/reply/components/rereply-form-toggle.tsx b/src/features/reply/components/rereply-form-toggle.tsx
new file mode 100644
index 00000000..e61cab4a
--- /dev/null
+++ b/src/features/reply/components/rereply-form-toggle.tsx
@@ -0,0 +1,36 @@
+'use client';
+
+import { useState } from 'react';
+import { AddRereplyButton } from './add-rereply-button';
+import { ReplyForm } from './reply-form';
+
+type RereplyFormToggleProps = {
+ parentReplyId: number;
+ openRereplyList: () => void;
+ isOpenRereplyList: boolean;
+};
+
+export const RereplyFormToggle = ({
+ parentReplyId,
+ openRereplyList,
+ isOpenRereplyList,
+}: RereplyFormToggleProps) => {
+ const [isWriting, setIsWriting] = useState(false);
+
+ const rereplyFormSuccessHandler = () => {
+ setIsWriting(false);
+ openRereplyList();
+ };
+
+ return isWriting ? (
+
+
+
+ ) : (
+ setIsWriting(true)} />
+ );
+};
diff --git a/src/features/reply/components/rereply-item.tsx b/src/features/reply/components/rereply-item.tsx
new file mode 100644
index 00000000..b42ba913
--- /dev/null
+++ b/src/features/reply/components/rereply-item.tsx
@@ -0,0 +1,18 @@
+import { Reply } from '@/types';
+import { forwardRef, useState } from 'react';
+import { ReplyContent } from './reply-content';
+
+/** ๋๋๊ธ ์ญ์ ๋ฅผ ์ํด ReplyContent๋ฅผ ๋๋ฌ์ธ๊ธฐ ์ํด ๋ง๋ ์ปดํฌ๋ํธ */
+export const RereplyItem = forwardRef((props, ref) => {
+ const [isDeleted, setIsDeleted] = useState(props.deleted);
+
+ if (isDeleted) return null;
+
+ return (
+
+ setIsDeleted(true)} />
+
+ );
+});
+
+RereplyItem.displayName = 'RereplyItem';
diff --git a/src/features/reply/components/rereply-list.tsx b/src/features/reply/components/rereply-list.tsx
new file mode 100644
index 00000000..ff885f63
--- /dev/null
+++ b/src/features/reply/components/rereply-list.tsx
@@ -0,0 +1,67 @@
+'use client';
+
+import { useReplyScrollIntoView } from '@/features/reply/hooks/useReplyScrollIntoView';
+import { useFetchInView } from '@/hooks/useFetchInView';
+import { useFetchItems } from '@/hooks/useFetchItems';
+import { Reply } from '@/types';
+import flattenPages from '@/utils/flattenPages';
+import { useParams } from 'next/navigation';
+import { RereplyItem } from './rereply-item';
+
+type RereplyListProps = {
+ parentReplyId: number;
+};
+
+const DATA_SIZE = 20;
+
+export const RereplyList = ({ parentReplyId }: RereplyListProps) => {
+ const { groupId } = useParams();
+ const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } =
+ useFetchItems({
+ url: `/v2/groups/${groupId}/replies/${parentReplyId}`,
+ queryParams: {
+ size: DATA_SIZE,
+ },
+ options: {
+ staleTime: 0,
+ },
+ });
+
+ const { ref } = useFetchInView({
+ fetchNextPage,
+ isLoading,
+ isFetchingNextPage,
+ });
+
+ const { itemRefs: rereplyRefs, bottomRef } = useReplyScrollIntoView({
+ data,
+ replyType: 'rereply',
+ hasNextPage,
+ });
+
+ const rereplies = flattenPages(data.pages).filter(
+ (rereply) => !rereply.deleted,
+ );
+
+ if (rereplies.length === 0) return null;
+
+ return (
+
+
+ {rereplies.map((rereply) => (
+ {
+ rereplyRefs.current[rereply.replyId] = el;
+ }}
+ {...rereply}
+ />
+ ))}
+
+
+ {hasNextPage && !isFetchingNextPage && (
+
+ )}
+
+ );
+};
diff --git a/src/features/reply/components/rereply-section.tsx b/src/features/reply/components/rereply-section.tsx
new file mode 100644
index 00000000..675dbe9d
--- /dev/null
+++ b/src/features/reply/components/rereply-section.tsx
@@ -0,0 +1,56 @@
+'use client';
+
+import { useTargetReplyParams } from '@/features/reply/hooks/useTargetReplyParams ';
+import { useTargetReplyStore } from '@/stores/useTargetReply';
+import { useEffect, useState } from 'react';
+import { RereplyFormToggle } from './rereply-form-toggle';
+import { RereplyList } from './rereply-list';
+
+export const RereplySection = ({
+ parentReplyId,
+}: {
+ parentReplyId: number;
+}) => {
+ const [isOpen, setIsOpen] = useState(false);
+ const { notificationTargetReplyId, notificationTargetRereplyId } =
+ useTargetReplyParams();
+ const setTargetReply = useTargetReplyStore((state) => state.setTargetReply);
+
+ useEffect(() => {
+ if (
+ notificationTargetRereplyId &&
+ parentReplyId === notificationTargetReplyId
+ ) {
+ setIsOpen(true);
+ setTargetReply({ targetRereplyId: notificationTargetRereplyId });
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [notificationTargetReplyId, notificationTargetRereplyId, parentReplyId]);
+
+ const toggleRereplyListHandler = () => {
+ setTargetReply({ targetReplyId: null, targetRereplyId: null });
+ setIsOpen(true);
+ };
+
+ return (
+
+
+
+
๋๋๊ธ
+
+
+ {isOpen &&
}
+
+
+
+ );
+};
diff --git a/src/features/reply/hooks/useReplyScrollIntoView.ts b/src/features/reply/hooks/useReplyScrollIntoView.ts
new file mode 100644
index 00000000..89f7d01f
--- /dev/null
+++ b/src/features/reply/hooks/useReplyScrollIntoView.ts
@@ -0,0 +1,104 @@
+import { useTargetReplyStore } from '@/stores/useTargetReply';
+import { Reply } from '@/types';
+import { Page } from '@/utils/flattenPages';
+import type { InfiniteData } from '@tanstack/react-query';
+import { usePathname, useSearchParams } from 'next/navigation';
+import { useEffect, useRef } from 'react';
+import { toast } from 'sonner';
+
+interface UseReplyScrollIntoViewProps {
+ data: InfiniteData>;
+ replyType: 'reply' | 'rereply';
+ hasNextPage: boolean;
+}
+
+/**
+ * useTargetReplyStore์ ์ ์ฅ๋ ๋ชฉํ ๋๊ธ๋ก ์คํฌ๋กค ์ด๋์ํค๊ธฐ ์ํ ์ปค์คํ
ํ
+ *
+ * @param data - useInfiniteQuery๋ก ๊ฐ์ ธ์จ ํ์ด์ง๋ค์ด์
๋ ๋๊ธ ๋ฐ์ดํฐ
+ * @param replyType - ๋ชฉํ๋ฌผ์ด ๋๊ธ์ธ์ง ๋๋๊ธ์ธ์ง ํ์
+ * @param hasNextPage - ์ถ๊ฐ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ฌ ์ ์๋์ง ์ฌ๋ถ
+ *
+ * @returns itemRefs - ๋๊ธ ๊ฐ๊ฐ์ ๋์๋๋ ref ๊ฐ์ฒด
+ * @returns bottomRef - ์์๊ฐ ์กด์ฌํ์ง ์์ ๊ฒฝ์ฐ ์คํฌ๋กคํ fallback ์์น
+ */
+export const useReplyScrollIntoView = ({
+ data,
+ replyType,
+ hasNextPage,
+}: UseReplyScrollIntoViewProps) => {
+ const itemRefs = useRef>({});
+ const bottomRef = useRef(null);
+ const { targetReplyId, targetRereplyId, setTargetReply } =
+ useTargetReplyStore();
+
+ const pathname = usePathname();
+ const searchParams = useSearchParams();
+
+ const hasSetToastRef = useRef(false);
+
+ const getTargetId = (): number | undefined => {
+ if (replyType === 'reply') return targetReplyId ?? undefined;
+ if (replyType === 'rereply') return targetRereplyId ?? undefined;
+ };
+
+ const clearTargetQueryAndState = () => {
+ const params = new URLSearchParams(searchParams.toString());
+
+ if (replyType === 'reply') {
+ params.delete('replyId');
+ setTargetReply({ targetReplyId: null });
+ } else {
+ params.delete('replyId');
+ params.delete('rereplyId');
+ setTargetReply({ targetRereplyId: null });
+ }
+
+ const query = params.toString();
+ const newUrl = query ? `${pathname}?${query}` : pathname;
+ window.history.replaceState(null, '', newUrl);
+ };
+
+ const scrollToElement = (element: HTMLElement | null) => {
+ element?.scrollIntoView({ behavior: 'smooth', block: 'center' });
+ };
+
+ const showReplyNotFoundToast = () => {
+ const params = new URLSearchParams(searchParams.toString());
+
+ if (params.has('replyId')) {
+ toast.error('์กด์ฌํ์ง ์๋ ๋๊ธ์
๋๋ค.');
+ } else if (params.has('rereplyId')) {
+ toast.error('์ญ์ ๋ ๋๋๊ธ์
๋๋ค.');
+ }
+
+ clearTargetQueryAndState();
+ setTargetReply({ targetReplyId: null, targetRereplyId: null });
+ };
+
+ useEffect(() => {
+ const targetId = getTargetId();
+ if (!targetId) return;
+
+ const targetElement = itemRefs.current[targetId];
+
+ if (targetElement) {
+ scrollToElement(targetElement);
+ clearTargetQueryAndState();
+ } else {
+ scrollToElement(bottomRef.current);
+
+ if (!hasNextPage && !hasSetToastRef.current) {
+ hasSetToastRef.current = true;
+ setTimeout(showReplyNotFoundToast, 1000);
+ }
+ }
+
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [targetReplyId, targetRereplyId, data]);
+
+ return {
+ itemRefs,
+ bottomRef,
+ };
+};
diff --git a/src/features/reply/hooks/useTargetReplyParams .ts b/src/features/reply/hooks/useTargetReplyParams .ts
new file mode 100644
index 00000000..6978ffe2
--- /dev/null
+++ b/src/features/reply/hooks/useTargetReplyParams .ts
@@ -0,0 +1,18 @@
+import { useSearchParams } from 'next/navigation';
+
+export const useTargetReplyParams = () => {
+ const searchParams = useSearchParams();
+
+ const replyIdParam = searchParams.get('replyId');
+ const replyId =
+ typeof replyIdParam === 'string' ? Number(replyIdParam) : null;
+
+ const rereplyIdParam = searchParams.get('rereplyId');
+ const rereplyId =
+ typeof rereplyIdParam === 'string' ? Number(rereplyIdParam) : null;
+
+ return {
+ notificationTargetReplyId: replyId,
+ notificationTargetRereplyId: rereplyId,
+ };
+};
diff --git a/src/features/user/components/account-settings-dialog.tsx b/src/features/user/components/account-settings-dialog.tsx
new file mode 100644
index 00000000..175cbcca
--- /dev/null
+++ b/src/features/user/components/account-settings-dialog.tsx
@@ -0,0 +1,53 @@
+'use client';
+
+import { useState } from 'react';
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from '@/components/ui/dialog';
+import { ChangePasswordForm } from './account-settings/change-password-form';
+
+/**
+ * ๊ณ์ ์ค์ ๋ชจ๋ฌ ์ปดํฌ๋ํธ
+ *
+ * ๊ณ์ ์ค์ ๋ชจ๋ฌ์์ ๋น๋ฐ๋ฒํธ๋ฅผ ๋ณ๊ฒฝํ ์ ์๋ค.
+ *
+ * @returns ๊ณ์ ์ค์ ๋ชจ๋ฌ ์ปดํฌ๋ํธ
+ */
+export const AccountSettingsDialog = () => {
+ const [isDialogOpen, setIsDialogOpen] = useState(false);
+
+ const closeDialog = () => {
+ setIsDialogOpen(false);
+ };
+
+ return (
+ <>
+
+ >
+ );
+};
diff --git a/src/features/user/components/account-settings/change-password-form.tsx b/src/features/user/components/account-settings/change-password-form.tsx
new file mode 100644
index 00000000..43cec2cd
--- /dev/null
+++ b/src/features/user/components/account-settings/change-password-form.tsx
@@ -0,0 +1,87 @@
+import { useForm } from 'react-hook-form';
+import { z } from 'zod';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { Button } from '@/components/ui/button';
+import { Form } from '@/components/ui/form';
+import { InputTextField } from '@/components/molecules/input-text-field';
+import { useChangePassword } from '@/features/user/hooks/useChangePassword';
+
+const schema = z.object({
+ newPassword: z
+ .string()
+ .nonempty({ message: '์ ๋น๋ฐ๋ฒํธ๋ฅผ ์
๋ ฅํด์ฃผ์ธ์' })
+ .regex(/^(?=.*[a-zA-Z])(?=.*\d)(?=.*[\W_]).{8,}$/, {
+ message:
+ '์์ด ๋/์๋ฌธ์, ์ซ์, ํน์๋ฌธ์๋ฅผ ํผํฉํ์ฌ 8์๋ฆฌ ์ด์ ์
๋ ฅํด์ฃผ์ธ์.',
+ }),
+ currentPassword: z
+ .string()
+ .nonempty({ message: '๊ธฐ์กด ๋น๋ฐ๋ฒํธ๋ฅผ ์
๋ ฅํด์ฃผ์ธ์' }),
+});
+
+type ChangePasswordFormProps = {
+ closeDialog: () => void;
+};
+
+/**
+ * ๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ ํผ ์ปดํฌ๋ํธ
+ *
+ * @param closeDialog ๋ชจ๋ฌ ๋ซ๋ ํจ์
+ * @returns ๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ ํผ ์ปดํฌ๋ํธ
+ */
+export const ChangePasswordForm = ({
+ closeDialog,
+}: ChangePasswordFormProps) => {
+ const formMethods = useForm>({
+ resolver: zodResolver(schema),
+ defaultValues: {
+ newPassword: '',
+ currentPassword: '',
+ },
+ });
+
+ const { mutateAsync: changePassword } = useChangePassword();
+
+ const onSubmit = async (data: z.infer) => {
+ try {
+ await changePassword({
+ newPassword: data.newPassword,
+ currentPassword: data.currentPassword,
+ });
+ closeDialog();
+ } catch (error) {
+ console.error(error);
+ }
+ };
+
+ return (
+
+
+ );
+};
diff --git a/src/features/user/components/current-user-profile.tsx b/src/features/user/components/current-user-profile.tsx
new file mode 100644
index 00000000..dcf833d0
--- /dev/null
+++ b/src/features/user/components/current-user-profile.tsx
@@ -0,0 +1,82 @@
+'use client';
+
+import { Avatar } from '@/components/atoms/avatar';
+import useAuthStore from '@/stores/useAuthStore';
+import { getSkill } from '@/types/enums';
+import { EditUserProfileDialog } from '@/features/user/components/edit-user-profile-dialog';
+import { AccountSettingsDialog } from '@/features/user/components/account-settings-dialog';
+import { WithdrawDialog } from '@/features/user/components/withdraw-dialog';
+import { getDisplayNickname, getDisplayProfileImage } from '@/utils/fallback';
+import { Separator } from '@/components/ui/separator';
+
+/**
+ * ํ์ฌ ๋ก๊ทธ์ธ ํ ์ ์ ์ ํ๋กํ ์ปดํฌ๋ํธ
+ *
+ * ํ์ฌ ๋ก๊ทธ์ธ ํ ์ ์ ์ ํ๋กํ์ ๋ณด์ฌ์ค๋ค.
+ *
+ * @returns ํ์ฌ ๋ก๊ทธ์ธ ํ ์ ์ ์ ํ๋กํ ์ปดํฌ๋ํธ
+ */
+
+export const CurrentUserProfile = () => {
+ const user = useAuthStore((state) => state.user);
+
+ if (!user) {
+ return ๋์ ์ ๋ณด๋ฅผ ๋ถ๋ฌ์ค๋ ๋ฐ ์คํจํ์ด์. ๋ค์ ๋ก๊ทธ์ธ ํด์ฃผ์ธ์.
;
+ }
+
+ const { nickname, email, profileImage, skills } = user;
+
+ return (
+ <>
+
+
+
+
+
+ {getDisplayNickname(nickname, email)}
+
+
+
+
+
๊ธฐ์ ์คํ
+ {skills && skills.length === 0 ? (
+
+ ์ค์ ๋ ๊ธฐ์ ์คํ์ด ์์ด์.
+
+ ) : (
+
+ {skills?.map((skill) => (
+ -
+ {getSkill(skill)}
+
+ ))}
+
+ )}
+
+
+ ์ด๋ฉ์ผ
+
+ {email}
+
+
+
+
+
+
+ >
+ );
+};
diff --git a/src/features/user/components/edit-user-profile-dialog.tsx b/src/features/user/components/edit-user-profile-dialog.tsx
new file mode 100644
index 00000000..8d6dbeea
--- /dev/null
+++ b/src/features/user/components/edit-user-profile-dialog.tsx
@@ -0,0 +1,44 @@
+'use client';
+
+import { useState } from 'react';
+import Image from 'next/image';
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from '@/components/ui/dialog';
+import { EditUserProfileForm } from '@/features/user/components/edit-user-profile-form/edit-user-profile-form';
+
+/**
+ * ํ๋กํ ์์ ๋ชจ๋ฌ ์ปดํฌ๋ํธ
+ *
+ * @returns ํ๋กํ ์์ ๋ชจ๋ฌ ์ปดํฌ๋ํธ
+ */
+
+export const EditUserProfileDialog = () => {
+ const [isDialogOpen, setIsDialogOpen] = useState(false);
+
+ const closeDialog = () => {
+ setIsDialogOpen(false);
+ };
+
+ return (
+
+ );
+};
diff --git a/src/features/user/components/edit-user-profile-form/edit-user-profile-form.tsx b/src/features/user/components/edit-user-profile-form/edit-user-profile-form.tsx
new file mode 100644
index 00000000..af590a02
--- /dev/null
+++ b/src/features/user/components/edit-user-profile-form/edit-user-profile-form.tsx
@@ -0,0 +1,126 @@
+'use client';
+
+import { FormProvider, useForm, SubmitHandler } from 'react-hook-form';
+import { z } from 'zod';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { EditableAvatar } from '@/features/user/components/edit-user-profile-form/editable-avatar';
+import { Form } from '@/components/ui/form';
+import { InputTextField } from '@/components/molecules/input-text-field';
+import { InputSelectField } from '@/components/molecules/input-select-field';
+import { SkillSelector } from '@/features/user/components/edit-user-profile-form/skill-selector';
+import { Button } from '@/components/ui/button';
+import { Position, Skill } from '@/types/enums';
+import useAuthStore from '@/stores/useAuthStore';
+import { useUpdateProfileMutation } from '@/features/user/hooks/useUpdateProfileMutation';
+import { getDisplayProfileImage, getDisplayNickname } from '@/utils/fallback';
+
+const schema = z.object({
+ nickname: z.string().nonempty('๋๋ค์์ ์
๋ ฅํด์ฃผ์ธ์.'),
+ profileImageFile: z.custom().nullable(),
+ position: z.string().nullable(),
+ skills: z.array(z.nativeEnum(Skill)),
+});
+
+type EditUserProfileFormProps = {
+ closeDialog: () => void;
+};
+
+export type FormData = z.infer;
+
+/**
+ * ์ฌ์ฉ์ ํ๋กํ ์์ ํผ
+ *
+ * @param closeDialog ๋ชจ๋ฌ ๋ซ๋ ํจ์
+ * @returns ์ฌ์ฉ์ ํ๋กํ ์์ ํผ ์ปดํฌ๋ํธ
+ */
+export const EditUserProfileForm = ({
+ closeDialog,
+}: EditUserProfileFormProps) => {
+ const user = useAuthStore((state) => state.user);
+
+ const formMethods = useForm({
+ resolver: zodResolver(schema),
+ defaultValues: {
+ nickname: user?.nickname ?? user?.email.split('@')[0],
+ profileImageFile: null,
+ position: user?.position ? String(user.position) : null,
+ skills: user?.skills ?? [],
+ },
+ });
+
+ const { mutateAsync: updateProfile } = useUpdateProfileMutation();
+ const formSubmitHandler: SubmitHandler = async (data) => {
+ const { nickname, position, skills, profileImageFile } = data;
+
+ try {
+ await updateProfile({
+ nickname,
+ position: Number(position),
+ skills,
+ ...(profileImageFile && { file: profileImageFile }),
+ });
+ closeDialog();
+ } catch (error) {
+ console.error(error);
+ }
+ };
+
+ return (
+
+
+
+
+ );
+};
diff --git a/src/features/user/components/edit-user-profile-form/editable-avatar.tsx b/src/features/user/components/edit-user-profile-form/editable-avatar.tsx
new file mode 100644
index 00000000..2eab7a91
--- /dev/null
+++ b/src/features/user/components/edit-user-profile-form/editable-avatar.tsx
@@ -0,0 +1,117 @@
+'use client';
+
+import { Avatar } from '@/components/atoms/avatar';
+import { Button } from '@/components/ui/button';
+import { validateImageFile } from '@/features/user/utils/validateImageFile';
+import { useRef, useState } from 'react';
+import { toast } from 'sonner';
+import { useFormContext } from 'react-hook-form';
+import { type FormData } from '@/features/user/components/edit-user-profile-form/edit-user-profile-form';
+import { request } from '@/api/request';
+
+type EditableAvatarProps = {
+ imageSrc: string;
+ fallback: string;
+};
+
+/**
+ * ์์ ๊ฐ๋ฅํ ์๋ฐํ ์ปดํฌ๋ํธ
+ *
+ * @param imageSrc ์ด๋ฏธ์ง ์ฃผ์(๋ฌธ์์ด)
+ * @param fallback ์๋ฐํ ํด๋ฐฑ ๋ฌธ์์ด(๋ฌธ์์ด)
+ * @returns ์์ ๊ฐ๋ฅํ ์๋ฐํ ์ปดํฌ๋ํธ
+ */
+export const EditableAvatar = ({ imageSrc, fallback }: EditableAvatarProps) => {
+ const { register, setValue } = useFormContext();
+
+ const { ref: registerRef, ...rest } = register('profileImageFile');
+
+ const [currentImageSrc, setCurrentImageSrc] = useState(imageSrc);
+ const fileInputRef = useRef(null);
+
+ /**
+ * ํ์ผ์ด ๋ณ๊ฒฝ๋ ๋ ์คํ๋๋ ํจ์
+ *
+ * ์ ํจํ ์ด๋ฏธ์ง ํ์ผ์ธ ๊ฒฝ์ฐ, ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ด๋ฏธ์ง ์ฃผ์๋ฅผ ์
๋ฐ์ดํธํ๊ณ , ํ์ผ์ ํผ ๋ฐ์ดํฐ์ ์ถ๊ฐํ๋ค.
+ * ๊ทธ๋ ์ง ์์ ๊ฒฝ์ฐ, ํ ์คํธ ๋ฉ์์ง๋ฅผ ํ์.
+ *
+ * @param e ํ์ผ ๋ณ๊ฒฝ ์ด๋ฒคํธ
+ */
+ const fileChangeHandler: React.ChangeEventHandler = (e) => {
+ const file = e.target.files?.[0];
+ if (file) {
+ const { isValid, errorMessage } = validateImageFile(file);
+ if (isValid) {
+ setCurrentImageSrc(URL.createObjectURL(file));
+ setValue('profileImageFile', file);
+ } else {
+ toast.error(errorMessage);
+ }
+ }
+ if (fileInputRef.current) {
+ fileInputRef.current.value = '';
+ }
+ };
+
+ /**
+ * ํ๋กํ ์ด๋ฏธ์ง ์ ๊ฑฐ ๋ฒํผ ํด๋ฆญ ํธ๋ค๋ฌ
+ *
+ * ํ๋กํ ์ด๋ฏธ์ง๋ฅผ ์ ๊ฑฐํ๊ณ , ์์ ํผ์์ ๋ณด์ด๋ ์ด๋ฏธ์ง ๊ฒฝ๋ก๋ฅผ ์
๋ฐ์ดํธํ๋ค.
+ * ํ๋กํ ์ด๋ฏธ์ง ์ ๊ฑฐ ์์ฒญ์ด ์คํจํ๋ฉด, ํ ์คํธ ๋ฉ์์ง๋ฅผ ํ์ํ๋ค.
+ */
+ const removeProfileImgButtonClickHandler = async () => {
+ try {
+ await request.delete('/v1/user/profile-image-delete', {
+ credentials: 'include',
+ });
+ setCurrentImageSrc('/images/default-profile.png');
+ setValue('profileImageFile', null);
+ } catch {
+ toast.error('ํ๋กํ ์ด๋ฏธ์ง ์ญ์ ์ ์คํจํ์์ต๋๋ค.', {
+ description: '์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.',
+ });
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
{
+ registerRef(e);
+ fileInputRef.current = e;
+ }}
+ onChange={fileChangeHandler}
+ />
+
+ );
+};
diff --git a/src/features/user/components/edit-user-profile-form/skill-selector.tsx b/src/features/user/components/edit-user-profile-form/skill-selector.tsx
new file mode 100644
index 00000000..69c45c6e
--- /dev/null
+++ b/src/features/user/components/edit-user-profile-form/skill-selector.tsx
@@ -0,0 +1,48 @@
+'use client';
+
+import { useController, useFormContext } from 'react-hook-form';
+import { Skill } from '@/types/enums';
+import { Button } from '@/components/ui/button';
+import { Label } from '@/components/ui/label';
+import { type FormData } from '@/features/user/components/edit-user-profile-form/edit-user-profile-form';
+
+export const SkillSelector = () => {
+ const { control } = useFormContext();
+
+ const { field } = useController({
+ control,
+ name: 'skills',
+ });
+
+ const itemClickHandler = (enumValue: Skill) => {
+ if (field.value.includes(enumValue)) {
+ field.onChange(field.value.filter((skill) => skill !== enumValue));
+ return;
+ }
+ field.onChange([...field.value, enumValue]);
+ };
+
+ return (
+
+
+
์ฌ์ฉ ์ค์ธ ๊ธฐ์ ํ๊ทธ๋ฅผ ์ ํํด์ฃผ์ธ์.
+
+ {Object.entries(Skill)
+ .filter(([key]) => isNaN(Number(key)))
+ .map(([key, value]) => (
+ -
+
+
+ ))}
+
+
+ );
+};
diff --git a/src/features/user/components/other-user-profile.tsx b/src/features/user/components/other-user-profile.tsx
new file mode 100644
index 00000000..1ec2fd5e
--- /dev/null
+++ b/src/features/user/components/other-user-profile.tsx
@@ -0,0 +1,115 @@
+'use client';
+
+import { request } from '@/api/request';
+import { Avatar } from '@/components/atoms/avatar';
+import { UserProfileLoading } from '@/features/user/components/user-profile-loading';
+import { ToggleFollowButton } from '@/features/user/follow/components/toggle-follow-button';
+import useAuthStore from '@/stores/useAuthStore';
+import { User } from '@/types';
+import { getSkill } from '@/types/enums';
+import { CommonResponse } from '@/types/response';
+import { getDisplayNickname, getDisplayProfileImage } from '@/utils/fallback';
+import { useQuery } from '@tanstack/react-query';
+import { notFound, useParams } from 'next/navigation';
+
+/**
+ * ํ์ฌ ๋ก๊ทธ์ธ ํ ์ ์ ๊ฐ ์๋ ๋ค๋ฅธ ์ ์ ์ ํ๋กํ ์ปดํฌ๋ํธ
+ *
+ * ๋ค๋ฅธ ์ ์ ์ ํ๋กํ์ ๋ณด์ฌ์ค๋ค.
+ *
+ * @returns ํ์ฌ ๋ก๊ทธ์ธ ํ ์ ์ ๊ฐ ์๋ ๋ค๋ฅธ ์ ์ ์ ํ๋กํ ์ปดํฌ๋ํธ
+ */
+export const OtherUserProfile = () => {
+ const { id } = useParams();
+
+ const currentUser = useAuthStore((state) => state.user);
+
+ const {
+ data: userResponse,
+ isLoading,
+ isError,
+ } = useQuery>({
+ queryKey: ['user', id],
+ queryFn: () =>
+ request.get(
+ `/v1/user/${id}`,
+ {},
+ {
+ credentials: 'include',
+ },
+ ),
+ staleTime: 0,
+ });
+
+ const user = userResponse?.data;
+
+ if (isLoading) return ;
+ if (isError) return Error
;
+
+ // ์ ์ ๊ฐ ์กด์ฌํ์ง ์์ผ๋ฉด, 404 Not Found ํ์ด์ง๋ก ์ด๋ํ๋ค.
+ if (!user) notFound();
+
+ const { nickname, email, profileImage, skills } = user;
+
+ // @ts-expect-error ํน์ ์ ์ ์ ๋ณด ์กฐํ์ ๊ฒฝ์ฐ ๋ฐฑ์๋์์ ํ๋ก์ ์ฌ๋ถ๋ฅผ ์ฃผ์ง ์์.
+ const isFollowing = user?.followers.some(
+ // @ts-expect-error ํน์ ์ ์ ์ ๋ณด ์กฐํ์ ๊ฒฝ์ฐ ๋ฐฑ์๋์์ ํ๋ก์ ์ฌ๋ถ๋ฅผ ์ฃผ์ง ์์.
+ (follower) => follower.id === currentUser?.id,
+ );
+
+ return (
+ <>
+
+
+
+
+
+ {getDisplayNickname(nickname, email)}
+
+
+
+
+
+
+ ์ด๋ฉ์ผ
+
+ {email}
+
+
+
+
+
+ >
+ );
+};
diff --git a/src/features/user/components/user-page-tabs.tsx b/src/features/user/components/user-page-tabs.tsx
new file mode 100644
index 00000000..f6ae2646
--- /dev/null
+++ b/src/features/user/components/user-page-tabs.tsx
@@ -0,0 +1,42 @@
+'use client';
+
+import { useParams } from 'next/navigation';
+import { usePathname } from 'next/navigation';
+
+import Link from 'next/link';
+
+const getIsActive = (pathname: string, href: string) => {
+ if (pathname.includes('social') && href.includes('social')) {
+ return true;
+ }
+
+ return pathname === href;
+};
+
+export const UserPageTabs = () => {
+ const { id } = useParams<{ id: string }>();
+
+ const pathname = usePathname();
+
+ return (
+
+ {[
+ { label: 'ํ', href: `/users/${id}` },
+ { label: '์์
', href: `/users/${id}/social/followings` },
+ { label: '๊ฐ์คํ ๋ชจ์', href: `/users/${id}/groups/created` },
+ { label: '์ข
๋ฃ๋ ๋ชจ์', href: `/users/${id}/groups/ended` },
+ ].map(({ label, href }) => {
+ return (
+ -
+
+ {label}
+
+
+ );
+ })}
+
+ );
+};
diff --git a/src/features/user/components/user-profile-loading.tsx b/src/features/user/components/user-profile-loading.tsx
new file mode 100644
index 00000000..e5582cec
--- /dev/null
+++ b/src/features/user/components/user-profile-loading.tsx
@@ -0,0 +1,21 @@
+import { Skeleton } from '@/components/ui/skeleton';
+
+export const UserProfileLoading = () => {
+ return (
+
+ );
+};
diff --git a/src/features/user/components/user-profile.tsx b/src/features/user/components/user-profile.tsx
new file mode 100644
index 00000000..da13a1dd
--- /dev/null
+++ b/src/features/user/components/user-profile.tsx
@@ -0,0 +1,25 @@
+'use client';
+
+import { useParams } from 'next/navigation';
+import useAuthStore from '@/stores/useAuthStore';
+import { CurrentUserProfile } from '@/features/user/components/current-user-profile';
+import { OtherUserProfile } from '@/features/user/components/other-user-profile';
+
+/**
+ * ์ ์ ํ๋กํ ์ปดํฌ๋ํธ
+ *
+ * ๋ง์ฝ ํ์ฌ ๋ก๊ทธ์ธํ ์ฌ์ฉ์์ id์ id ์ธ๊ทธ๋จผํธ ๊ฐ์ด ๊ฐ๋ค๋ฉด, CurrentUserProfile ์ปดํฌ๋ํธ๋ฅผ ๋ฐํํ๋ค.
+ * ๊ทธ๋ ์ง ์์ผ๋ฉด, OtherUserProfile ์ปดํฌ๋ํธ๋ฅผ ๋ฐํํ๋ค.
+ */
+export const UserProfile = () => {
+ const { id } = useParams<{ id: string }>();
+ const user = useAuthStore((state) => state.user);
+
+ return (
+
+
+ {/* @ts-expect-error ๋ฐฑ์๋์์ ์ฃผ๋ ์ฌ์ฉ์ ์์ด๋ ํ๋กํผํฐ๊ฐ userId๊ฐ ์๋ id์ฌ์ ์ผ๋จ ์๋์ ๊ฐ์ด ๋ณ๊ฒฝ */}
+ {String(user?.id) === id ?
:
}
+
+ );
+};
diff --git a/src/features/user/components/withdraw-dialog.tsx b/src/features/user/components/withdraw-dialog.tsx
new file mode 100644
index 00000000..eefb8b2d
--- /dev/null
+++ b/src/features/user/components/withdraw-dialog.tsx
@@ -0,0 +1,74 @@
+'use client';
+
+import { useState } from 'react';
+import { useRouter } from 'next/navigation';
+import { toast } from 'sonner';
+import {
+ AlertDialog,
+ AlertDialogTrigger,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+} from '@/components/ui/alert-dialog';
+import { request } from '@/api/request';
+import useAuthStore from '@/stores/useAuthStore';
+
+/**
+ * ํ์ ํํด ๋ชจ๋ฌ ์ปดํฌ๋ํธ
+ *
+ * @returns ํ์ ํํด ๋ชจ๋ฌ ์ปดํฌ๋ํธ
+ */
+export const WithdrawDialog = () => {
+ const [isDialogOpen, setIsDialogOpen] = useState(false);
+
+ const router = useRouter();
+ const clearUser = useAuthStore((state) => state.clearUser);
+
+ const withdrawButtonClickHandler = async () => {
+ try {
+ await request.delete('/v1/user/delete', {
+ credentials: 'include',
+ });
+ toast.success('ํ์ ํํด ์๋ฃ', {
+ description: 'ํ์ ํํด๊ฐ ์๋ฃ๋์์ต๋๋ค.',
+ });
+ clearUser();
+ router.push('/');
+ } catch (error) {
+ console.error(error);
+ toast.error('ํ์ ํํด ์คํจ', {
+ description: 'ํ์ ํํด์ ์คํจํ์ต๋๋ค. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.',
+ });
+ }
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+ ์ ๋ง๋ก ํํดํ์๊ฒ ์ต๋๊น?
+
+ ํ์ ํํด ํ ๋ชจ๋ ๋ฐ์ดํฐ๊ฐ ์ญ์ ๋ฉ๋๋ค.
+
+
+
+ ์ทจ์
+
+ ํํด
+
+
+
+
+ >
+ );
+};
diff --git a/src/features/user/follow/components/follow-list-items-loading.tsx b/src/features/user/follow/components/follow-list-items-loading.tsx
new file mode 100644
index 00000000..9221e335
--- /dev/null
+++ b/src/features/user/follow/components/follow-list-items-loading.tsx
@@ -0,0 +1,29 @@
+'use client';
+
+import { Skeleton } from '@/components/ui/skeleton';
+
+type FollowListItemsLoadingProps = {
+ itemCount?: number;
+};
+
+export default function FollowListItemsLoading({
+ itemCount = 6,
+}: FollowListItemsLoadingProps) {
+ return (
+ <>
+ {Array.from({ length: itemCount }).map((_, i) => (
+
+
+
+ ))}
+ >
+ );
+}
diff --git a/src/features/user/follow/components/follow-tabs.tsx b/src/features/user/follow/components/follow-tabs.tsx
new file mode 100644
index 00000000..697f507f
--- /dev/null
+++ b/src/features/user/follow/components/follow-tabs.tsx
@@ -0,0 +1,39 @@
+'use client';
+
+import { useParams, usePathname } from 'next/navigation';
+
+import Link from 'next/link';
+
+export const FollowTabs = () => {
+ const { id } = useParams<{ id: string }>();
+
+ const pathname = usePathname();
+
+ const type = pathname.split('/').at(-1);
+
+ return (
+
+ {[
+ {
+ label: 'ํ๋ก์',
+ value: 'followings',
+ href: `/users/${id}/social/followings`,
+ },
+ {
+ label: 'ํ๋ก์',
+ value: 'followers',
+ href: `/users/${id}/social/followers`,
+ },
+ ].map(({ label, value, href }) => (
+ -
+
+ {label}
+
+
+ ))}
+
+ );
+};
diff --git a/src/features/user/follow/components/followers-list.tsx b/src/features/user/follow/components/followers-list.tsx
new file mode 100644
index 00000000..d0ca3070
--- /dev/null
+++ b/src/features/user/follow/components/followers-list.tsx
@@ -0,0 +1,147 @@
+'use client';
+
+import { Avatar } from '@/components/atoms/avatar';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu';
+import FollowListItemsLoading from '@/features/user/follow/components/follow-list-items-loading';
+import { RemoveFollowerButton } from '@/features/user/follow/components/remove-follower-button';
+import { ToggleFollowButton } from '@/features/user/follow/components/toggle-follow-button';
+import { useFetchInView } from '@/hooks/useFetchInView';
+import { useFetchItems } from '@/hooks/useFetchItems';
+import useAuthStore from '@/stores/useAuthStore';
+import { User } from '@/types/index';
+import { getDisplayNickname, getDisplayProfileImage } from '@/utils/fallback';
+import flattenPages, { Page } from '@/utils/flattenPages';
+import Image from 'next/image';
+import Link from 'next/link';
+import { useParams, useSearchParams } from 'next/navigation';
+
+export const FollowersList = () => {
+ const { id } = useParams();
+ const user = useAuthStore((state) => state.user);
+ const isCurrentUser = id === String(user?.userId);
+
+ const searchParams = useSearchParams();
+
+ const search = searchParams.get('search');
+
+ const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } =
+ useFetchItems({
+ url: `/v1/follow/${id}/followers`,
+ ...(search && { queryParams: { name: search } }),
+ options: {
+ refetchOnMount: true,
+ staleTime: 0,
+ retry: 0,
+ gcTime: 0,
+ },
+ });
+
+ const followersCount = (data.pages[0] as Page & { totalCount: number })
+ .totalCount;
+
+ const { ref } = useFetchInView({
+ fetchNextPage,
+ isLoading,
+ isFetchingNextPage,
+ });
+
+ const followersList = flattenPages(data.pages);
+
+ return (
+
+
+
+
+ ์ด ํ๋ก์ ์ :{' '}
+
+
+ {followersCount ?? 0}
+
+
+
+ {followersList.length === 0 ? (
+
+
+ ์์ง ํ๋ก์๊ฐ ์์ด์.
+
+
+ ) : (
+
+ ํ๋ก์ {followersCount ?? null}
+ {followersList.map(
+ // @ts-expect-error ํ์ฌ User ํ์
์๋ id ํ๋กํผํฐ๊ฐ ์์ -> ์ถํ ์์ ํ์
+ ({ id: userId, nickname, profileImage, email, isFollowing }) => (
+ -
+
+
+
+
+
+
+ {getDisplayNickname(nickname, email)}
+
+
+ {email}
+
+
+
+ {user && String(user?.userId) !== String(userId) && (
+
+
+
+
+
+ {isCurrentUser && (
+
+ )}
+
+ {!isCurrentUser && (
+ svg]:text-red-600!'
+ : 'text-black [&_svg]:text-black'
+ } bg-white h-[28px] cursor-pointer text-sm font-semibold rounded-lg py-1 px-3 gap-x-[6px]`}
+ />
+ )}
+
+
+ )}
+
+
+
+ ),
+ )}
+ {isFetchingNextPage && }
+ {!isFetchingNextPage && hasNextPage && (
+
+ )}
+
+ )}
+
+ );
+};
diff --git a/src/features/user/follow/components/following-list.tsx b/src/features/user/follow/components/following-list.tsx
new file mode 100644
index 00000000..5cea4cb8
--- /dev/null
+++ b/src/features/user/follow/components/following-list.tsx
@@ -0,0 +1,139 @@
+'use client';
+
+import { Avatar } from '@/components/atoms/avatar';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu';
+import FollowListItemsLoading from '@/features/user/follow/components/follow-list-items-loading';
+import { ToggleFollowButton } from '@/features/user/follow/components/toggle-follow-button';
+import { useFetchInView } from '@/hooks/useFetchInView';
+import { useFetchItems } from '@/hooks/useFetchItems';
+import useAuthStore from '@/stores/useAuthStore';
+import { User } from '@/types';
+import { getDisplayNickname, getDisplayProfileImage } from '@/utils/fallback';
+import flattenPages, { Page } from '@/utils/flattenPages';
+import Image from 'next/image';
+import Link from 'next/link';
+import { useParams, useSearchParams } from 'next/navigation';
+
+export const FollowingList = () => {
+ const searchParams = useSearchParams();
+ const { id } = useParams();
+ const user = useAuthStore((state) => state.user);
+
+ const search = searchParams.get('search');
+
+ const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } =
+ useFetchItems({
+ url: `/v1/follow/${id}/following`,
+ ...(search && { queryParams: { name: search } }),
+ options: {
+ refetchOnMount: true,
+ staleTime: 0,
+ retry: 0,
+ gcTime: 0,
+ },
+ });
+
+ const followingCount = (data.pages[0] as Page & { totalCount: number })
+ .totalCount;
+
+ const { ref } = useFetchInView({
+ fetchNextPage,
+ isLoading,
+ isFetchingNextPage,
+ });
+
+ const followingList = flattenPages(data.pages);
+
+ return (
+
+
+
+
+ ์ด ํ๋ก์ ์ :{' '}
+
+
+ {followingCount ?? 0}
+
+
+
+ {followingList.length === 0 ? (
+
+
+ ์์ง ํ๋ก์ฐ ํ๋ ์ฌ๋์ด ์์ด์.
+
+
+ ) : (
+
+ {followingList?.map(
+ /* @ts-expect-error ํ์ฌ User ํ์
์๋ id ํ๋กํผํฐ๊ฐ ์์ -> ์ถํ ์์ ํ์ */
+ ({ id: userId, nickname, profileImage, email, isFollowing }) => (
+ -
+
+
+
+
+
+
+ {getDisplayNickname(nickname, email)}
+
+
+ {email}
+
+
+
+ {user && String(user?.userId) !== String(userId) && (
+
+
+
+
+
+ {
+ svg]:text-red-600!'
+ : 'text-black [&_svg]:text-black'
+ } bg-white h-[28px] cursor-pointer text-sm font-semibold rounded-lg py-1 px-3 gap-x-[6px]`}
+ />
+ }
+
+
+ )}
+
+
+
+ ),
+ )}
+ {isFetchingNextPage && }
+ {!isFetchingNextPage && hasNextPage && (
+
+ )}
+
+ )}
+
+ );
+};
diff --git a/src/features/user/follow/components/remove-follower-button.tsx b/src/features/user/follow/components/remove-follower-button.tsx
new file mode 100644
index 00000000..2c1a623a
--- /dev/null
+++ b/src/features/user/follow/components/remove-follower-button.tsx
@@ -0,0 +1,29 @@
+import { useRemoveFollower } from '@/features/user/follow/hooks/useRemoveFollower';
+import { Button } from '@/components/ui/button';
+import { X } from 'lucide-react';
+
+type RemoveFollowerButtonProps = {
+ userId: string;
+};
+
+export const RemoveFollowerButton = ({ userId }: RemoveFollowerButtonProps) => {
+ const { mutate: removeFollower, isPending } = useRemoveFollower({ userId });
+
+ const removeFollowerButtonClickHandler: React.MouseEventHandler<
+ HTMLButtonElement
+ > = (e) => {
+ e.preventDefault();
+ removeFollower();
+ };
+
+ return (
+
+ );
+};
diff --git a/src/features/user/follow/components/toggle-follow-button.tsx b/src/features/user/follow/components/toggle-follow-button.tsx
new file mode 100644
index 00000000..19f39fbb
--- /dev/null
+++ b/src/features/user/follow/components/toggle-follow-button.tsx
@@ -0,0 +1,62 @@
+'use client';
+
+import { useRouter } from 'next/navigation';
+import { useToggleFollow } from '@/features/user/follow/hooks/useToggleFollow';
+import { Button } from '@/components/ui/button';
+import { X, Plus } from 'lucide-react';
+import useAuthStore from '@/stores/useAuthStore';
+
+type ToggleFollowButtonProps = {
+ userId?: string;
+ isFollowing: boolean;
+ usedIn: string;
+ className?: string;
+};
+
+export const ToggleFollowButton = ({
+ userId,
+ isFollowing,
+ usedIn,
+ className,
+}: ToggleFollowButtonProps) => {
+ const user = useAuthStore((state) => state.user);
+
+ const router = useRouter();
+
+ const { mutate: toggleFollow, isPending } = useToggleFollow({
+ ...(userId && { userId }),
+ isFollowing,
+ usedIn,
+ });
+
+ const toggleFollowButtonClickHandler = (
+ e: React.MouseEvent,
+ ) => {
+ e.preventDefault();
+ if (!user) {
+ router.push('/login');
+ return;
+ }
+ toggleFollow();
+ };
+
+ return (
+
+ );
+};
diff --git a/src/features/user/follow/hooks/useRemoveFollower.ts b/src/features/user/follow/hooks/useRemoveFollower.ts
new file mode 100644
index 00000000..1ec2792f
--- /dev/null
+++ b/src/features/user/follow/hooks/useRemoveFollower.ts
@@ -0,0 +1,34 @@
+'use client';
+
+import { useParams } from 'next/navigation';
+import { useMutation, useQueryClient } from '@tanstack/react-query';
+import { request } from '@/api/request';
+import { toast } from 'sonner';
+
+export const useRemoveFollower = ({ userId }: { userId: string }) => {
+ const queryClient = useQueryClient();
+ const { id } = useParams();
+
+ return useMutation({
+ mutationFn() {
+ return request.delete(`/v1/follow/${userId}/unfollower`, {
+ credentials: 'include',
+ });
+ },
+ onError() {
+ toast.error('ํ๋ก์ ์ญ์ ์คํจ', {
+ description: 'ํ๋ก์ ์ญ์ ์ ์คํจํ์ด์. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.',
+ });
+ },
+ onSettled() {
+ return Promise.all([
+ queryClient.invalidateQueries({
+ queryKey: ['items', `/v1/follow/${id}/followers`],
+ }),
+ queryClient.invalidateQueries({
+ queryKey: ['user', id, 'followers count'],
+ }),
+ ]);
+ },
+ });
+};
diff --git a/src/features/user/follow/hooks/useToggleFollow.ts b/src/features/user/follow/hooks/useToggleFollow.ts
new file mode 100644
index 00000000..5ce8462c
--- /dev/null
+++ b/src/features/user/follow/hooks/useToggleFollow.ts
@@ -0,0 +1,71 @@
+import { useMutation, useQueryClient } from '@tanstack/react-query';
+import { request } from '@/api/request';
+import { toast } from 'sonner';
+import { useParams } from 'next/navigation';
+
+/**
+ * ํ๋ก์ฐ, ์ธํ๋ก์ฐ ๊ธฐ๋ฅ์ ์ํ ์ปค์คํ
ํ
+ *
+ * usedIn prop๋ฅผ ์ถ๊ฐํ ์ด์ ๋ ์ด ์ปค์คํ
ํ
์ ํธ์ถํ ๋ฒํผ์ด ์ฌ์ฉ๋ ์์น์ ๋ฐ๋ผ ๋ฌดํจํํ ์ฟผ๋ฆฌ ํค๋ฅผ ๋ค๋ฅด๊ฒ ์ค์ ํ๊ธฐ ์ํจ
+ *
+ * @param userId: ํ๋ก์ฐ, ์ธํ๋ก์ฐ ๋์ ์ ์ ID
+ * @param isFollowing: ํ๋ก์ฐ ์ํ
+ * @param usedIn: ํด๋น ์ปค์คํ
ํ
์ ํธ์ถํ ๋ฒํผ์ด ์ฌ์ฉ๋ ์์น (ํ๋กํ, ํ๋ก์ ๋ชฉ๋ก, ํ๋ก์ ๋ชฉ๋ก)
+ * @returns useMutation ๋ฐํ๊ฐ
+ */
+export const useToggleFollow = ({
+ userId,
+ isFollowing,
+ usedIn,
+}: {
+ userId?: string;
+ isFollowing: boolean;
+ usedIn: string;
+}) => {
+ const { id } = useParams();
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn() {
+ if (isFollowing) {
+ return request.delete(`/v1/follow/${userId ?? id}/unfollow`, {
+ credentials: 'include',
+ });
+ }
+ return request.post(
+ `/v1/follow/${userId ?? id}`,
+ {
+ 'Content-Type': 'application/json',
+ },
+ JSON.stringify({ id: userId ?? id }),
+ { credentials: 'include' },
+ );
+ },
+ onError() {
+ toast.error('์๋ฌ๊ฐ ๋ฐ์ํ์ด์.', {
+ description:
+ '์์ฒญ ์ฌํญ์ ์ฒ๋ฆฌํ๋ ๋ฐ ์๋ฌ๊ฐ ๋ฐ์ํ์ด์. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.',
+ });
+ },
+ onSettled() {
+ if (usedIn === 'profile')
+ return Promise.all([
+ queryClient.invalidateQueries({
+ queryKey: ['user', id],
+ exact: true,
+ }),
+ queryClient.invalidateQueries({
+ queryKey: ['items', `/v1/follow/${id}/followers`],
+ }),
+ ]);
+ return Promise.all([
+ queryClient.invalidateQueries({
+ queryKey: ['user', id, `${usedIn} count`],
+ }),
+ queryClient.invalidateQueries({
+ queryKey: ['items', `/v1/follow/${id}/${usedIn}`],
+ }),
+ ]);
+ },
+ });
+};
diff --git a/src/features/user/group/components/group-list-item-loading.tsx b/src/features/user/group/components/group-list-item-loading.tsx
new file mode 100644
index 00000000..a8434f12
--- /dev/null
+++ b/src/features/user/group/components/group-list-item-loading.tsx
@@ -0,0 +1,47 @@
+import { Skeleton } from '@/components/ui/skeleton';
+
+type GroupListItemLoadingProps = {
+ itemCount?: number;
+};
+
+export const GroupListItemLoading = ({
+ itemCount = 4,
+}: GroupListItemLoadingProps) => {
+ return (
+ <>
+ {Array.from({ length: itemCount }).map((_, index) => (
+
+
+
+
+
+
+
+
+
+ {[1, 2, 3].map((i) => (
+ -
+
+
+ ))}
+
+
+ {[1, 2, 3, 4].map((i) => (
+ -
+
+
+ ))}
+
+
+
+
+
+ ))}
+ >
+ );
+};
diff --git a/src/features/user/group/components/group-list-item.tsx b/src/features/user/group/components/group-list-item.tsx
new file mode 100644
index 00000000..a82dc457
--- /dev/null
+++ b/src/features/user/group/components/group-list-item.tsx
@@ -0,0 +1,119 @@
+'use client';
+
+import Link from 'next/link';
+import { Group } from '@/types';
+import { formatYearMonthDayWithDot, isBeforeToday } from '@/utils/dateUtils';
+import { MemberListDialog } from '@/features/user/group/components/member-list-modal/member-list-dialog';
+import { CalendarDays, UsersRound } from 'lucide-react';
+import { Avatar } from '@/components/atoms/avatar';
+import { getDisplayProfileImage, getDisplayNickname } from '@/utils/fallback';
+import { GroupSkills } from '@/components/atoms/group/group-skills';
+import { GroupPositions } from '@/components/atoms/group/group-positions';
+
+type GroupListItemProps = {
+ group: Group;
+ isCurrentUser: boolean;
+ status: 'PARTICIPATING' | 'RECRUITING' | 'ENDED';
+};
+
+const getIsRecruiting = (group: Group) => {
+ if (!isBeforeToday(group.deadline)) {
+ return group.participants.length < group.maxParticipants;
+ }
+ return false;
+};
+
+export const GroupListItem = ({
+ group,
+ isCurrentUser,
+ status,
+}: GroupListItemProps) => {
+ const { id, startDate, endDate, title, participants, maxParticipants, type } =
+ group;
+
+ return (
+
+
+
+
+
{title}
+ {status === 'RECRUITING' ? (
+
+ {getIsRecruiting(group) ? '๋ชจ์ง์ค' : '๋ชจ์ง์๋ฃ'}
+
+ ) : (
+
+ {type === 'study' ? '์คํฐ๋' : 'ํ๋ก์ ํธ'}
+
+ )}
+
+
+
+
+
+
+ ์งํ ๊ธฐ๊ฐ
+
+
+
+ {formatYearMonthDayWithDot(startDate)} ~{' '}
+ {formatYearMonthDayWithDot(endDate)}
+
+
+
+
+
+
+
+
+ {participants.length} / {maxParticipants}๋ช
+
+
+
+ {participants.map((participant, i) => {
+ if (i > 3) {
+ return (
+
+
+ +{participants.length - 4}
+
+
+ );
+ }
+ return (
+
= 1 ? '-ml-3' : 'ml-0'} z-10`}
+ />
+ );
+ })}
+
+
+
+
+
+ {isCurrentUser && status === 'RECRUITING' && (
+
+ )}
+ {
+ isCurrentUser && status === 'ENDED' && false
+ // TODO: ๋ณ์ ์ ๋ฌ ์ ์๋ ๋ชจ๋ฌ ๊ฐ์ ๊ฒ์ ์ถ๊ฐ
+ }
+
+ );
+};
diff --git a/src/features/user/group/components/group-list-loading.tsx b/src/features/user/group/components/group-list-loading.tsx
new file mode 100644
index 00000000..4bd5c0af
--- /dev/null
+++ b/src/features/user/group/components/group-list-loading.tsx
@@ -0,0 +1,9 @@
+import { GroupListItemLoading } from '@/features/user/group/components/group-list-item-loading';
+
+export const GroupListLoading = () => {
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/src/features/user/group/components/group-list.tsx b/src/features/user/group/components/group-list.tsx
new file mode 100644
index 00000000..97f0a20a
--- /dev/null
+++ b/src/features/user/group/components/group-list.tsx
@@ -0,0 +1,97 @@
+'use client';
+
+import { GroupListItem } from '@/features/user/group/components/group-list-item';
+import { useFetchInView } from '@/hooks/useFetchInView';
+import { useFetchItems } from '@/hooks/useFetchItems';
+import useAuthStore from '@/stores/useAuthStore';
+import { Group } from '@/types';
+import { isBeforeToday } from '@/utils/dateUtils';
+import flattenPages from '@/utils/flattenPages';
+import { useParams, useSearchParams } from 'next/navigation';
+
+type GroupListProps = {
+ status: 'PARTICIPATING' | 'RECRUITING' | 'ENDED';
+};
+
+/**
+ * ๋ชจ์ ๋ชฉ๋ก ์ปดํฌ๋ํธ
+ *
+ * ๋ชจ์ ๋ชฉ๋ก์ ๋ณด์ฌ์ค๋ค.
+ *
+ * @returns ๋ชจ์ ๋ชฉ๋ก ์ปดํฌ๋ํธ
+ */
+export const GroupList = ({ status }: GroupListProps) => {
+ const user = useAuthStore((state) => state.user);
+
+ const { id } = useParams();
+
+ const isCurrentUser = String(user?.userId) === id;
+
+ const searchParams = useSearchParams();
+
+ const { search, type, order } = Object.fromEntries(searchParams.entries());
+
+ const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } =
+ useFetchItems({
+ url: `/v2/groups/usergroup/${id}`,
+ queryParams: {
+ ...(status !== 'PARTICIPATING' && { type: type ?? 'study' }),
+ status: 'PARTICIPATING',
+ size: status !== 'ENDED' ? 10 : 50,
+ ...(search && { search }),
+ order: order === 'latest' || !order ? 'desc' : 'asc',
+ },
+ options: {
+ staleTime: 0,
+ refetchOnMount: true,
+ refetchOnWindowFocus: false,
+ retry: 0,
+ },
+ });
+
+ const { ref } = useFetchInView({
+ fetchNextPage,
+ isLoading,
+ isFetchingNextPage,
+ });
+
+ // @ts-expect-error ๊ฐ์ฒด ์์ ๋ group ํ๋กํผํฐ๊ฐ ์กด์ฌํจ.
+ let groupList = flattenPages(data.pages).map((e) => e.group);
+ if (status === 'ENDED') {
+ groupList = groupList.filter((group) => isBeforeToday(group.endDate));
+ }
+
+ if (status === 'RECRUITING') {
+ groupList = groupList.filter((group) => group.createUserId === Number(id));
+ }
+
+ if (status === 'PARTICIPATING') {
+ groupList = groupList.filter((group) => !isBeforeToday(group.endDate));
+ }
+
+ return (
+ <>
+ {groupList.length === 0 ? (
+
+
+ {status === 'RECRUITING' && '๋ชจ์ง ์ค์ธ ๋ชจ์์ด ์์ด์.'}
+ {status === 'PARTICIPATING' && '์ฐธ์ฌ ์ค์ธ ๋ชจ์์ด ์์ด์.'}
+ {status === 'ENDED' && '์ข
๋ฃ๋ ๋ชจ์์ด ์์ด์.'}
+
+
+ ) : (
+
+ {groupList.map((group) => (
+
+ ))}
+ {hasNextPage && }
+
+ )}
+ >
+ );
+};
diff --git a/src/features/user/group/components/member-list-modal/applicants-list.tsx b/src/features/user/group/components/member-list-modal/applicants-list.tsx
new file mode 100644
index 00000000..4da3094c
--- /dev/null
+++ b/src/features/user/group/components/member-list-modal/applicants-list.tsx
@@ -0,0 +1,101 @@
+'use client';
+
+import { request } from '@/api/request';
+import { Button } from '@/components/ui/button';
+import { MemberInfo } from '@/features/user/group/components/member-list-modal/member-info';
+import { useManageParticipation } from '@/features/user/group/hooks/useManageParticipation';
+import { UserSummary } from '@/types';
+import { useQuery } from '@tanstack/react-query';
+import { MemberListLoading } from '@/features/user/group/components/member-list-modal/member-list-loading';
+
+type ApplicantsListProps = {
+ groupId: string;
+};
+
+/**
+ * ๋ชจ์์ ์ฐธ์ฌ ์ ์ฒญํ ์ฌ์ฉ์ ๋ชฉ๋ก ์ปดํฌ๋ํธ
+ *
+ * ๋ชจ์์ ์ฐธ์ฌ ์ ์ฒญํ ์ฌ์ฉ์์ ๋ชฉ๋ก์ ๋ณด์ฌ์ค๋ค.
+ *
+ * @param groupId ๋ชจ์ id
+ * @returns ๋ชจ์์ ์ฐธ์ฌ ์ ์ฒญํ ์ฌ์ฉ์ ๋ชฉ๋ก ์ปดํฌ๋ํธ
+ํธ */
+export const ApplicantsList = ({ groupId }: ApplicantsListProps) => {
+ const { mutate: manageParticipation } = useManageParticipation();
+
+ const {
+ data: applicantsList,
+ isLoading,
+ isError,
+ } = useQuery({
+ queryKey: ['group-member-list', groupId, 'applicants'],
+ queryFn() {
+ return request
+ .get(`/v2/groups/${groupId}/waitinglist`)
+ .then((res) => res.items);
+ },
+ staleTime: 0,
+ refetchOnWindowFocus: false,
+ gcTime: 0,
+ });
+
+ return (
+
+ {isLoading &&
}
+ {isError && <>Error>}
+ {applicantsList && applicantsList.length === 0 && (
+
+
+ ๋ชจ์์ ์ฐธ์ฌ ์ ์ฒญํ ์ฌ์ฉ์๊ฐ ์์ด์.
+
+
+ )}
+ {applicantsList && applicantsList.length > 0 && (
+
+ {applicantsList.map((applicant: UserSummary) => (
+ -
+
+
+
+
+
+
+ ))}
+
+ )}
+
+ );
+};
diff --git a/src/features/user/group/components/member-list-modal/group-member-list.tsx b/src/features/user/group/components/member-list-modal/group-member-list.tsx
new file mode 100644
index 00000000..ca609b7e
--- /dev/null
+++ b/src/features/user/group/components/member-list-modal/group-member-list.tsx
@@ -0,0 +1,64 @@
+'use client';
+
+import { ApplicantsList } from '@/features/user/group/components/member-list-modal/applicants-list';
+import { ParticipantsList } from '@/features/user/group/components/member-list-modal/participants-list';
+import { useQueryClient } from '@tanstack/react-query';
+import { useParams } from 'next/navigation';
+import { useEffect, useState } from 'react';
+
+type GroupMemberListProps = {
+ groupId: string;
+};
+
+/**
+ * ๋ชจ์ ์ฐธ์ฌ/์ ์ฒญ์ ๋ชฉ๋ก ์ปดํฌ๋ํธ
+ *
+ * ํญ์ ๋ฐ๋ผ ๋ชจ์ ์ฐธ์ฌ/์ ์ฒญ์ ๋ชฉ๋ก์ ๋ณด์ฌ์ค๋ค.
+ *
+ * @param groupId ๋ชจ์ id
+ * @returns ๋ชจ์ ์ฐธ์ฌ/์ ์ฒญ์ ๋ชฉ๋ก ์ปดํฌ๋ํธ
+ */
+export const GroupMemberList = ({ groupId }: GroupMemberListProps) => {
+ const { id } = useParams();
+
+ const queryClient = useQueryClient();
+
+ const [currentTab, setCurrentTab] = useState<'participants' | 'applicants'>(
+ 'applicants',
+ );
+
+ useEffect(() => {
+ return () => {
+ queryClient.invalidateQueries({
+ queryKey: ['items', `/v2/groups/usergroup/${id}`],
+ });
+ };
+ // eslint-disable-next-line
+ }, []);
+
+ return (
+
+
+ {['applicants', 'participants'].map((tab) => (
+ -
+
+
+ ))}
+
+ {currentTab === 'applicants' &&
}
+ {currentTab === 'participants' &&
}
+
+ );
+};
diff --git a/src/features/user/group/components/member-list-modal/member-info.tsx b/src/features/user/group/components/member-list-modal/member-info.tsx
new file mode 100644
index 00000000..10dc01d6
--- /dev/null
+++ b/src/features/user/group/components/member-list-modal/member-info.tsx
@@ -0,0 +1,29 @@
+'use client';
+
+import { Avatar } from '@/components/atoms/avatar';
+import { UserSummary } from '@/types';
+import { getDisplayNickname, getDisplayProfileImage } from '@/utils/fallback';
+import Link from 'next/link';
+
+export const MemberInfo = ({
+ userId,
+ nickname,
+ email,
+ profileImage,
+}: UserSummary) => (
+
+
+
+
+
+
+ {getDisplayNickname(nickname, email)}
+
+ {email}
+
+
+);
diff --git a/src/features/user/group/components/member-list-modal/member-list-dialog.tsx b/src/features/user/group/components/member-list-modal/member-list-dialog.tsx
new file mode 100644
index 00000000..76705f46
--- /dev/null
+++ b/src/features/user/group/components/member-list-modal/member-list-dialog.tsx
@@ -0,0 +1,53 @@
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from '@/components/ui/dialog';
+import { GroupMemberList } from '@/features/user/group/components/member-list-modal/group-member-list';
+import { ArrowUpRight } from 'lucide-react';
+
+type MemberListDialogProps = {
+ groupId: string;
+ groupTitle: string;
+};
+
+/**
+ * ๋ชจ์ ์ฐธ์ฌ/์ ์ฒญ์ ๋ชฉ๋ก ๋ชจ๋ฌ ์ปดํฌ๋ํธ
+ *
+ * ๋ชจ์ ์ฐธ์ฌ/์ ์ฒญ์ ๋ชฉ๋ก์ ๋ณด์ฌ์ค๋ค.
+ *
+ * @param groupId ๋ชจ์ id
+ * @param groupTitle ๋ชจ์ ์ ๋ชฉ
+ * @returns ๋ชจ์ ์ฐธ์ฌ/์ ์ฒญ์ ๋ชฉ๋ก ๋ชจ๋ฌ ์ปดํฌ๋ํธ
+ */
+export const MemberListDialog = ({
+ groupId,
+ groupTitle,
+}: MemberListDialogProps) => {
+ return (
+
+
+
+ );
+};
diff --git a/src/features/user/group/components/member-list-modal/member-list-loading.tsx b/src/features/user/group/components/member-list-modal/member-list-loading.tsx
new file mode 100644
index 00000000..7cece3b9
--- /dev/null
+++ b/src/features/user/group/components/member-list-modal/member-list-loading.tsx
@@ -0,0 +1,19 @@
+'use client';
+
+import { Skeleton } from '@/components/ui/skeleton';
+
+export const MemberListLoading = () => {
+ return (
+
+ {Array.from({ length: 3 }).map((_, index) => (
+ -
+
+
+
+
+
+
+ ))}
+
+ );
+};
diff --git a/src/features/user/group/components/member-list-modal/participants-list.tsx b/src/features/user/group/components/member-list-modal/participants-list.tsx
new file mode 100644
index 00000000..dd70e871
--- /dev/null
+++ b/src/features/user/group/components/member-list-modal/participants-list.tsx
@@ -0,0 +1,61 @@
+'use client';
+
+import { request } from '@/api/request';
+import { MemberInfo } from '@/features/user/group/components/member-list-modal/member-info';
+import { UserSummary } from '@/types';
+import { useQuery } from '@tanstack/react-query';
+import { MemberListLoading } from '@/features/user/group/components/member-list-modal/member-list-loading';
+
+type ParticipantsListProps = {
+ groupId: string;
+};
+
+/**
+ * ๋ชจ์์ ์ฐธ์ฌํ ์ฌ์ฉ์ ๋ชฉ๋ก ์ปดํฌ๋ํธ
+ *
+ * ๋ชจ์์ ์ฐธ์ฌํ ์ฌ์ฉ์์ ๋ชฉ๋ก์ ๋ณด์ฌ์ค๋ค.
+ *
+ * @param groupId ๋ชจ์ id
+ * @returns ๋ชจ์์ ์ฐธ์ฌํ ์ฌ์ฉ์ ๋ชฉ๋ก ์ปดํฌ๋ํธ
+ */
+export const ParticipantsList = ({ groupId }: ParticipantsListProps) => {
+ const {
+ data: participantsList,
+ isLoading,
+ isError,
+ } = useQuery({
+ queryKey: ['group-member-list', groupId, 'participants'],
+ queryFn() {
+ return request
+ .get(`/v2/groups/${groupId}/participants`)
+ .then((res) => res.items.participants);
+ },
+ staleTime: 0,
+ refetchOnWindowFocus: false,
+ gcTime: 0,
+ });
+
+ return (
+
+ {isLoading &&
}
+ {isError && <>Error>}
+ {participantsList && (
+
+ {participantsList.map((participant: UserSummary) => (
+ -
+
+
+ ))}
+
+ )}
+
+ );
+};
diff --git a/src/features/user/group/hooks/useManageParticipation.tsx b/src/features/user/group/hooks/useManageParticipation.tsx
new file mode 100644
index 00000000..fdf2078d
--- /dev/null
+++ b/src/features/user/group/hooks/useManageParticipation.tsx
@@ -0,0 +1,50 @@
+import { useMutation } from '@tanstack/react-query';
+import { request } from '@/api/request';
+import { toast } from 'sonner';
+import { useQueryClient } from '@tanstack/react-query';
+
+/**
+ * ์ฌ์ฉ์์ ๋ชจ์ ์ฐธ๊ฐ๋ฅผ ๊ด๋ฆฌํ๋ ์ปค์คํ
ํ
+ *
+ * ๋ชจ์ ์ฐธ๊ฐ ์์ฒญ ์น์ธ ๋ฐ ๊ฑฐ์ ์ฒ๋ฆฌ
+ *
+ * @returns useMutation ํ
๋ฐํ๊ฐ
+ */
+export const useManageParticipation = () => {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn({
+ groupId,
+ userId,
+ status,
+ }: {
+ groupId: string;
+ userId: string;
+ status: 'approve' | 'deny';
+ }) {
+ return request.post(
+ `/v2/groups/${groupId}/join`,
+ {
+ 'Content-Type': 'application/json',
+ },
+ JSON.stringify({
+ userId: Number(userId),
+ status,
+ }),
+ {
+ credentials: 'include',
+ },
+ );
+ },
+ onSuccess() {
+ return queryClient.invalidateQueries({
+ queryKey: ['group-member-list'],
+ });
+ },
+ onError(error) {
+ const errMessage = JSON.parse(error.message.split('-')[1]).message;
+ toast.error(errMessage);
+ },
+ });
+};
diff --git a/src/features/user/hooks/useChangePassword.ts b/src/features/user/hooks/useChangePassword.ts
new file mode 100644
index 00000000..b145035b
--- /dev/null
+++ b/src/features/user/hooks/useChangePassword.ts
@@ -0,0 +1,43 @@
+import { useMutation } from '@tanstack/react-query';
+import { toast } from 'sonner';
+import { request } from '@/api/request';
+
+/**
+ * ๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ ์ปค์คํ
ํ
+ */
+export const useChangePassword = () => {
+ return useMutation({
+ mutationFn: ({
+ newPassword,
+ currentPassword,
+ }: {
+ newPassword: string;
+ currentPassword: string;
+ }) => {
+ return request.patch(
+ '/v1/user/password-change',
+ {
+ 'Content-Type': 'application/json',
+ },
+ {
+ newPassword,
+ confirmPassword: currentPassword,
+ },
+ {
+ credentials: 'include',
+ },
+ );
+ },
+ onSuccess() {
+ toast.success('๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ ์ฑ๊ณต', {
+ description: '๋น๋ฐ๋ฒํธ๊ฐ ๋ณ๊ฒฝ๋์์ด์',
+ });
+ },
+ onError(error) {
+ const errInfo = JSON.parse(error.message.split('-')[1]);
+ toast.error('๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ ์คํจ', {
+ description: errInfo.status.message,
+ });
+ },
+ });
+};
diff --git a/src/features/user/hooks/useUpdateProfileMutation.ts b/src/features/user/hooks/useUpdateProfileMutation.ts
new file mode 100644
index 00000000..36cd511d
--- /dev/null
+++ b/src/features/user/hooks/useUpdateProfileMutation.ts
@@ -0,0 +1,58 @@
+import { useMutation } from '@tanstack/react-query';
+import useAuthStore from '@/stores/useAuthStore';
+import { Position, Skill } from '@/types/enums';
+import { toast } from 'sonner';
+import { User } from '@/types';
+import { CommonResponse } from '@/types/response';
+import { request } from '@/api/request';
+
+/**
+ * ํ๋กํ ์์ ์ปค์คํ
ํ
+ *
+ * @param userId ์ฌ์ฉ์ ์์ด๋
+ * @returns ํ๋กํ ์์ ์ปค์คํ
ํ
+ */
+export const useUpdateProfileMutation = () => {
+ const { setUser } = useAuthStore((state) => state);
+
+ return useMutation({
+ mutationFn: (data: {
+ nickname: string;
+ position: Position | null;
+ skills: Skill[] | null;
+ file?: File;
+ }) => {
+ const formData = new FormData();
+ formData.append('nickname', data.nickname);
+
+ if (data.position) {
+ formData.append('position', JSON.stringify(data.position));
+ }
+
+ if (data.skills) {
+ formData.append('skills', data.skills.join(','));
+ }
+
+ if (data.file) {
+ formData.append('image', data.file);
+ }
+
+ return request.patch(`/v1/user/edit`, {}, formData, {
+ credentials: 'include',
+ });
+ },
+ onSuccess(response: CommonResponse) {
+ const user = response.data;
+ // @ts-expect-error ๋ฐฑ์๋์์ ์๋ตํ๋ ๊ฐ์ฒด์ userId ํ๋กํผํฐ๊ฐ ์กด์ฌํ์ง ์์. -> ์ถํ ํ์
์์ ํ์
+ setUser({ ...user, userId: user.id });
+ toast.success('ํ๋กํ ์์ ์ฑ๊ณต', {
+ description: 'ํ๋กํ์ด ์์ ๋์์ด์',
+ });
+ },
+ onError() {
+ toast.error('ํ๋กํ ์์ ์คํจ', {
+ description: 'ํ๋กํ ์์ ์ ์คํจํ์ด์. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.',
+ });
+ },
+ });
+};
diff --git a/src/features/user/utils/validateImageFile.ts b/src/features/user/utils/validateImageFile.ts
new file mode 100644
index 00000000..774e9d90
--- /dev/null
+++ b/src/features/user/utils/validateImageFile.ts
@@ -0,0 +1,35 @@
+/**
+ * ์ด๋ฏธ์ง ํ์ผ ์ ํจ์ฑ ๊ฒ์ฌ
+ *
+ * ๋ค์ ์กฐ๊ฑด์ ๋ง์กฑํด์ผ ์ ํจํ ์ด๋ฏธ์ง๋ก ๊ฐ์ฃผ
+ * - MIME ํ์
์ด image/๋ก ์์ํด์ผ ํจ
+ * - ํ์ผ ํฌ๊ธฐ๊ฐ maxFileSize ์ดํ์ฌ์ผ ์ด๋ฏธ์ง ํ์ผ์ด ์ ํจํจ
+ *
+ * @param file ๊ฒ์ฌํ ์ด๋ฏธ์ง ํ์ผ
+ * @param maxFileSize ์ต๋ ํ์ผ ํฌ๊ธฐ(๊ธฐ๋ณธ๊ฐ: 5MB)
+ * @returns ์ด๋ฏธ์ง ํ์ผ ์ ํจ์ฑ ๊ฒ์ฌ ๊ฒฐ๊ณผ ๊ฐ์ฒด
+ * - isValid: ์ ํจํ ์ด๋ฏธ์ง ํ์ผ์ธ ๊ฒฝ์ฐ true, ๊ทธ๋ ์ง ์์ ๊ฒฝ์ฐ false
+ * - errorMessage: ์ ํจํ์ง ์์ ๊ฒฝ์ฐ ์ค๋ฅ ๋ฉ์์ง
+ */
+export const validateImageFile = (
+ file: File,
+ maxFileSize = 1024 * 1024,
+): { isValid: boolean; errorMessage?: string } => {
+ if (!file.type.startsWith('image/')) {
+ return {
+ isValid: false,
+ errorMessage: '์ด๋ฏธ์ง ํ์ผ๋ง ์
๋ก๋ํ ์ ์์ด์.',
+ };
+ }
+
+ const fileSize = file.size;
+ if (fileSize > maxFileSize) {
+ return {
+ isValid: false,
+ errorMessage: `ํ์ผ ํฌ๊ธฐ๋ ${
+ maxFileSize / 1024 / 1024
+ }MB๋ฅผ ์ด๊ณผํ ์ ์์ด์.`,
+ };
+ }
+ return { isValid: true };
+};
diff --git a/src/hooks/useDebounce.ts b/src/hooks/useDebounce.ts
new file mode 100644
index 00000000..8aac9dc1
--- /dev/null
+++ b/src/hooks/useDebounce.ts
@@ -0,0 +1,39 @@
+import { useCallback, useEffect, useRef } from 'react';
+
+/**
+ * ๋๋ฐ์ด์ค ํ
+ *
+ * @param callback ํธ์ถํ ํจ์
+ * @param delay ์ง์ฐ ์๊ฐ
+ * @returns ๋๋ฐ์ด์ค๋ ์ฝ๋ฐฑ ํจ์
+ */
+
+export const useDebounce = ) => void>(
+ callback: T,
+ delay: number,
+): ((...args: Parameters) => void) => {
+ const timerRef = useRef(null);
+
+ useEffect(() => {
+ return () => {
+ if (timerRef.current) {
+ clearTimeout(timerRef.current);
+ }
+ };
+ }, []);
+
+ const debouncedCallback = useCallback(
+ (...args: Parameters) => {
+ if (timerRef.current) {
+ clearTimeout(timerRef.current);
+ }
+
+ timerRef.current = window.setTimeout(() => {
+ callback(...args);
+ }, delay);
+ },
+ [callback, delay],
+ );
+
+ return debouncedCallback;
+};
diff --git a/src/hooks/useFetchInView.ts b/src/hooks/useFetchInView.ts
new file mode 100644
index 00000000..c6155f9f
--- /dev/null
+++ b/src/hooks/useFetchInView.ts
@@ -0,0 +1,26 @@
+import { FetchNextPageOptions } from '@tanstack/react-query';
+import { useEffect } from 'react';
+import { IntersectionOptions, useInView } from 'react-intersection-observer';
+
+export const useFetchInView = ({
+ fetchNextPage,
+ options,
+ isLoading,
+ isFetchingNextPage,
+}: {
+ fetchNextPage: (options?: FetchNextPageOptions) => void;
+ options?: IntersectionOptions;
+ isLoading?: boolean;
+ isFetchingNextPage?: boolean;
+}) => {
+ const { ref, inView } = useInView({ ...options });
+
+ useEffect(() => {
+ if (inView && !isLoading && !isFetchingNextPage) {
+ fetchNextPage();
+ }
+ // eslint-disable-next-line
+ }, [inView]);
+
+ return { ref };
+};
diff --git a/src/hooks/useFetchItems.ts b/src/hooks/useFetchItems.ts
new file mode 100644
index 00000000..4ae5b571
--- /dev/null
+++ b/src/hooks/useFetchItems.ts
@@ -0,0 +1,44 @@
+import { request } from '@/api/request';
+import { Page } from '@/utils/flattenPages';
+import {
+ InfiniteData,
+ useSuspenseInfiniteQuery,
+ UseSuspenseInfiniteQueryOptions,
+} from '@tanstack/react-query';
+
+export const useFetchItems = ({
+ url,
+ queryParams = {},
+ options = {},
+ getNextPageParam,
+}: {
+ url: string;
+ queryParams?: Record;
+ options?: Partial<
+ UseSuspenseInfiniteQueryOptions, Error, InfiniteData>>
+ >;
+ getNextPageParam?: (lastPage: Page) => number | null;
+}) => {
+ const isCursorNull = queryParams.order === 'desc';
+
+ return useSuspenseInfiniteQuery, Error, InfiniteData>>({
+ queryKey: ['items', url, queryParams ?? {}],
+ queryFn: async ({ pageParam }): Promise> =>
+ request.get(
+ url,
+ {
+ ...queryParams,
+ cursor:
+ isCursorNull && pageParam === 0
+ ? 'null'
+ : (pageParam as number | string),
+ },
+ { credentials: 'include' },
+ ),
+ initialPageParam: 0,
+ getNextPageParam(lastPage) {
+ return getNextPageParam ? getNextPageParam(lastPage) : lastPage.hasNext ? lastPage.cursor : null;
+ },
+ ...options,
+ });
+};
diff --git a/src/hooks/useIsClient.ts b/src/hooks/useIsClient.ts
new file mode 100644
index 00000000..b548d1c7
--- /dev/null
+++ b/src/hooks/useIsClient.ts
@@ -0,0 +1,14 @@
+import { useEffect, useState } from 'react';
+
+// ๋ง์ดํธ ํ์ ํด๋ผ์ด์ธํธ์ธ์ง ํ์ธํ๋ ํ
+const useIsClient = () => {
+ const [isClient, setIsClient] = useState(false);
+
+ useEffect(() => {
+ setIsClient(true);
+ }, []);
+
+ return isClient;
+};
+
+export default useIsClient;
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
new file mode 100644
index 00000000..2819a830
--- /dev/null
+++ b/src/lib/utils.ts
@@ -0,0 +1,6 @@
+import { clsx, type ClassValue } from 'clsx';
+import { twMerge } from 'tailwind-merge';
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs));
+}
diff --git a/src/mocks/browser.ts b/src/mocks/browser.ts
new file mode 100644
index 00000000..ab00d904
--- /dev/null
+++ b/src/mocks/browser.ts
@@ -0,0 +1,20 @@
+import { setupWorker } from 'msw/browser';
+import { applicationsHandlers } from './handler/applications';
+import { authenticationsHandlers } from './handler/authentications';
+import { followHandlers } from './handler/follow';
+import { groupsHandlers } from './handler/groups';
+import { notificationsHandlers } from './handler/notifications';
+import { ratingHandlers } from './handler/rating';
+import { repliesHandlers } from './handler/replies';
+import { userHandlers } from './handler/user';
+
+export const worker = setupWorker(
+ ...groupsHandlers,
+ ...notificationsHandlers,
+ ...followHandlers,
+ ...authenticationsHandlers,
+ ...repliesHandlers,
+ ...ratingHandlers,
+ ...userHandlers,
+ ...applicationsHandlers,
+);
diff --git a/src/mocks/handler/applications.ts b/src/mocks/handler/applications.ts
new file mode 100644
index 00000000..75e9fd63
--- /dev/null
+++ b/src/mocks/handler/applications.ts
@@ -0,0 +1,16 @@
+import { http, HttpResponse } from 'msw';
+
+export const applicationsHandlers = [
+ http.post(
+ `${process.env.NEXT_PUBLIC_API_BASE_URL}/v2/groups/:groupId/applications`,
+ () => {
+ return HttpResponse.json({});
+ },
+ ),
+ http.delete(
+ `${process.env.NEXT_PUBLIC_API_BASE_URL}/v2/groups/:groupId/applications`,
+ () => {
+ return HttpResponse.json({});
+ },
+ ),
+];
diff --git a/src/mocks/handler/authentications.ts b/src/mocks/handler/authentications.ts
new file mode 100644
index 00000000..b0f526aa
--- /dev/null
+++ b/src/mocks/handler/authentications.ts
@@ -0,0 +1,81 @@
+import { http, HttpResponse } from 'msw';
+import { User } from '@/types';
+import { Position, Skill } from '@/types/enums';
+
+let user: User = {
+ userId: 1,
+ email: 'me@example.com',
+ nickname: null,
+ position: null,
+ skills: null,
+ profileImage: null,
+ isFollower: false,
+ isFollowing: true,
+ rate: 4,
+};
+
+export const authenticationsHandlers = [
+ http.post('http://localhost:4000/api/login', () => {
+ return new HttpResponse(JSON.stringify({ success: true }), {
+ headers: new Headers([
+ ['Set-Cookie', 'accessToken=myAccess; Path=/'],
+ ['Set-Cookie', 'refreshToken=myRefresh; Path=/'],
+ ]),
+ });
+ }),
+ http.post('http://localhost:4000/api/register', () => {
+ return new HttpResponse(JSON.stringify({ success: true }), {
+ headers: new Headers([
+ ['Set-Cookie', 'accessToken=myAccess; Path=/'],
+ ['Set-Cookie', 'refreshToken=myRefresh; Path=/'],
+ ]),
+ });
+ }),
+ http.get('http://localhost:4000/api/me', () => {
+ return HttpResponse.json<{ user: User }>({
+ user,
+ });
+ }),
+ http.post('http://localhost:4000/api/me', async ({ request }) => {
+ const body = (await request.json()) as {
+ nickname: string;
+ position: Position;
+ skills: Skill[];
+ };
+
+ user = {
+ ...user,
+ nickname: body.nickname,
+ position: body.position,
+ skills: body.skills,
+ };
+
+ return HttpResponse.json({
+ success: true,
+ });
+ }),
+ http.post('http://localhost:4000/api/find-email', () => {
+ return HttpResponse.json({
+ success: true,
+ });
+ }),
+ http.post('http://localhost:4000/api/find-password', () => {
+ return HttpResponse.json({
+ success: true,
+ });
+ }),
+ http.post('http://localhost:4000/api/logout', () => {
+ return new HttpResponse(JSON.stringify({ success: true }), {
+ headers: new Headers([
+ [
+ 'Set-Cookie',
+ 'accessToken=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT',
+ ],
+ [
+ 'Set-Cookie',
+ 'refreshToken=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT',
+ ],
+ ]),
+ });
+ }),
+];
diff --git a/src/mocks/handler/follow.ts b/src/mocks/handler/follow.ts
new file mode 100644
index 00000000..73235260
--- /dev/null
+++ b/src/mocks/handler/follow.ts
@@ -0,0 +1,109 @@
+import { http, HttpResponse } from 'msw';
+import { User } from '@/types';
+
+export const followHandlers = [
+ http.get(
+ `${process.env.NEXT_PUBLIC_API_BASE_URL}/users/:userId/followings`,
+ ({ request }) => {
+ const url = new URL(request.url);
+ const searchParams = new URLSearchParams(url.search);
+ const search = searchParams.get('search');
+ console.log('in handler', search);
+
+ const items: User[] = Array.from({ length: 10 }).map((_, index) => {
+ return {
+ userId: index + 1,
+ email: `test${index + 1}@gmail.com`,
+ nickname: `ํ
์คํธ ๋๋ค์${index + 1}`,
+ profileImage: `https://github.com/shadcn.png`,
+ position: 1,
+ skills: [1, 2, 3],
+ isFollowing: true,
+ isFollower: true,
+ rate: 4,
+ };
+ });
+
+ return HttpResponse.json({
+ hasNext: true,
+ cursor: 1,
+ items,
+ });
+ },
+ ),
+
+ http.get(
+ `${process.env.NEXT_PUBLIC_API_BASE_URL}/users/:userId/followers`,
+ ({ request }) => {
+ const url = new URL(request.url);
+ const searchParams = new URLSearchParams(url.search);
+ const search = searchParams.get('search');
+ console.log('in handler', search);
+
+ const items: User[] = Array.from({ length: 10 }).map((_, index) => {
+ return {
+ userId: index + 1,
+ email: `test${index + 1}@gmail.com`,
+ nickname: `ํ
์คํธ ๋๋ค์${index + 1}`,
+ profileImage: `https://github.com/shadcn.png`,
+ position: 1,
+ skills: [1, 2, 3],
+ isFollowing: true,
+ isFollower: true,
+ rate: 4,
+ };
+ });
+
+ return HttpResponse.json({
+ hasNext: true,
+ cursor: 1,
+ items,
+ });
+ },
+ ),
+
+ http.post(
+ `${process.env.NEXT_PUBLIC_API_BASE_URL}/users/:userId/follow`,
+ () => {
+ return HttpResponse.json({
+ message: 'ํ๋ก์ฐ ์ฑ๊ณต',
+ });
+ },
+ ),
+
+ http.delete(
+ `${process.env.NEXT_PUBLIC_API_BASE_URL}/users/:userId/unfollow`,
+ () => {
+ return HttpResponse.json({
+ message: '์ธํ๋ก์ฐ ์ฑ๊ณต',
+ });
+ },
+ ),
+
+ http.delete(
+ `${process.env.NEXT_PUBLIC_API_BASE_URL}/users/:userId/unfollower`,
+ () => {
+ return HttpResponse.json({
+ message: 'ํ๋ก์ ์ญ์ ์ฑ๊ณต',
+ });
+ },
+ ),
+
+ http.get(
+ `${process.env.NEXT_PUBLIC_API_BASE_URL}/users/:userId/followers/count`,
+ () => {
+ return HttpResponse.json({
+ count: 100,
+ });
+ },
+ ),
+
+ http.get(
+ `${process.env.NEXT_PUBLIC_API_BASE_URL}/users/:userId/followings/count`,
+ () => {
+ return HttpResponse.json({
+ count: 50,
+ });
+ },
+ ),
+];
diff --git a/src/mocks/handler/groups.ts b/src/mocks/handler/groups.ts
new file mode 100644
index 00000000..3637535f
--- /dev/null
+++ b/src/mocks/handler/groups.ts
@@ -0,0 +1,214 @@
+import { Group, GroupSort, Order } from '@/types';
+import { Position, Skill } from '@/types/enums';
+import {
+ getRandomItem,
+ getRandomItems,
+ groupTypeValues,
+ positionKeys,
+ skillKeys,
+} from '@/utils/mockUtils';
+import { addDays } from 'date-fns';
+import { http, HttpResponse } from 'msw';
+
+const titles = [
+ 'React ์คํฐ๋',
+ 'Node.js ๋ชจ์',
+ 'TypeScript ์คํฐ๋',
+ 'Next.js ํด๋ฝ',
+ 'ํ๋ก ํธ์๋ ๊ฐ๋ฐ์ ๊ทธ๋ฃน',
+ '๋ฐฑ์๋ ๋ง์คํฐ์ฆ',
+ 'ํ์คํ ํ๋ก์ ํธํ',
+ '์๊ณ ๋ฆฌ์ฆ ๋ง๋ผํค',
+ 'UI/UX ๋์์ธ ์คํฐ๋',
+ '๋ฐ์ดํฐ ์ฌ์ด์ธ์ค ํด๋ฝ',
+];
+
+const GROUP_LIST = [
+ {
+ title: '์คํฐ๋1',
+ deadline: '2025-05-24',
+ startDate: '2025-05-20',
+ endDate: '2025-05-24',
+ maxParticipants: 10,
+ description:
+ 'Next.js ์คํฐ๋
์จ๋ผ์ธ์ผ๋ก ์งํ
์คํ 7์
โ์๋ฃโ
',
+ position: [1, 3],
+ skills: [1, 2],
+ createdAt: '2025-05-20',
+ type: 'study',
+ autoAllow: true,
+ host: {
+ userId: 'abcd123',
+ nickname: '์ฌ์ฉ์1',
+ profileImage: 'https://github.com/shadcn.png',
+ email: 'qwerty@gmail.com',
+ },
+ isApplicant: false,
+ isBookmark: false,
+ participants: [
+ {
+ userId: 'abcd1',
+ nickname: 'ํ์1',
+ profileImage: null,
+ email: 'member1@gmail.com',
+ },
+ {
+ userId: 'abcd12',
+ nickname: null,
+ profileImage: 'https://github.com/shadcn.png',
+ email: 'member2@gmail.com',
+ },
+ {
+ userId: 'abcd123',
+ nickname: 'ํ์3',
+ profileImage: 'https://github.com/shadcn.png',
+ email: 'member3@gmail.com',
+ },
+ {
+ userId: 'abcd1234',
+ nickname: 'ํ์4',
+ profileImage: 'https://github.com/shadcn.png',
+ email: 'member4@gmail.com',
+ },
+ {
+ userId: 'abcd1235',
+ nickname: null,
+ profileImage: 'https://github.com/shadcn.png',
+ email: 'member5@naver.com',
+ },
+ ],
+ },
+];
+
+export const groupsHandlers = [
+ http.get(`${process.env.NEXT_PUBLIC_API_BASE_URL}/groups`, ({ request }) => {
+ const url = new URL(request.url);
+
+ const typeParam = url.searchParams.get('type');
+ const skillParam = url.searchParams.get('skill');
+ const skillNumber = skillParam ? Number(skillParam) : null;
+ const positionParam = url.searchParams.get('position');
+ const positionNumber = positionParam ? Number(positionParam) : null;
+ const searchKeyword = url.searchParams.get('search')?.toLowerCase() ?? '';
+ const sortParam = url.searchParams.get('sort') as GroupSort | null;
+ const orderParam = url.searchParams.get('order') as Order | null;
+
+ const cursorParam = url.searchParams.get('cursor');
+ const cursor = cursorParam ? Number(cursorParam) : 0;
+ const limit = 10; // ํ์ด์ง๋น ์์ดํ
์
+
+ const allItems: Group[] = Array.from({ length: 100 }, (_, index) => {
+ const offset = index * 2;
+ const baseDate = new Date(2025, 4, 26);
+ const createdAt = addDays(baseDate, offset);
+ const deadline = addDays(baseDate, offset + 1).toISOString();
+ const startDate = addDays(baseDate, offset + 5).toISOString();
+ const endDate = addDays(baseDate, offset + 10).toISOString();
+
+ const positions = getRandomItems(
+ positionKeys,
+ Math.floor(Math.random() * 3) + 1,
+ ).map((key) => Position[key]);
+ const skills = getRandomItems(
+ skillKeys,
+ Math.floor(Math.random() * 3) + 1,
+ ).map((key) => Skill[key]);
+ const type = getRandomItem(groupTypeValues.slice(0, 2));
+
+ const maxParticipants = Math.floor(Math.random() * (30 - 2 + 1)) + 2;
+ const participants = Array.from(
+ { length: Math.floor(Math.random() * maxParticipants) },
+ () => ({
+ userId: 1,
+ nickname: '๋ชจ์ฌ๋ผ์์ ์ ',
+ profileImage: null,
+ email: 'user@yopmail.com',
+ }),
+ );
+
+ const title = titles[index % titles.length];
+
+ return {
+ id: index + 1,
+ title,
+ description: `${title} ๋ชจ์งํฉ๋๋ค
๋ชจ๋ ์ฆ๊ฒ๊ฒ ๊ณต๋ถํด์!
`,
+ position: positions,
+ skills: skills,
+ participants,
+ maxParticipants,
+ autoAllow: true,
+ isBookmark: false,
+ createdAt,
+ deadline,
+ startDate,
+ endDate,
+ type,
+ };
+ });
+
+ const typeFiltered = typeParam
+ ? allItems.filter((item) => item.type === typeParam)
+ : allItems;
+ const skillFiltered =
+ skillNumber !== null
+ ? typeFiltered.filter((item) => item.skills.includes(skillNumber))
+ : typeFiltered;
+ const positionFiltered =
+ positionNumber !== null
+ ? skillFiltered.filter((item) => item.position.includes(positionNumber))
+ : skillFiltered;
+ const searchFiltered = searchKeyword
+ ? positionFiltered.filter((item) =>
+ item.title.toLowerCase().includes(decodeURIComponent(searchKeyword)),
+ )
+ : positionFiltered;
+
+ const sortedItems = [...searchFiltered].sort((a, b) => {
+ if (!sortParam) return 0;
+ const aDate = new Date(a[sortParam]);
+ const bDate = new Date(b[sortParam]);
+ if (orderParam === 'asc') return aDate.getTime() - bDate.getTime();
+ if (orderParam === 'desc') return bDate.getTime() - aDate.getTime();
+ return 0;
+ });
+
+ const paginatedItems = sortedItems.slice(cursor, cursor + limit);
+ const nextCursor =
+ cursor + limit < sortedItems.length ? cursor + limit : null;
+
+ return HttpResponse.json({
+ items: paginatedItems,
+ hasNext: nextCursor !== null,
+ cursor: nextCursor,
+ });
+ }),
+ http.post(`${process.env.NEXT_PUBLIC_API_BASE_URL}/groups`, () => {
+ return HttpResponse.json({
+ success: true,
+ });
+ }),
+ http.get(`${process.env.NEXT_PUBLIC_API_BASE_URL}/groups/:id`, () => {
+ return HttpResponse.json(GROUP_LIST[0]);
+ }),
+ http.patch(
+ `${process.env.NEXT_PUBLIC_API_BASE_URL}/bookmark`,
+ async ({ request }) => {
+ const body = (await request.json()) as {
+ id: number;
+ isBookmark: boolean;
+ };
+ const { id, isBookmark } = body;
+
+ GROUP_LIST[0].isBookmark = isBookmark;
+
+ if (id === 2) {
+ return HttpResponse.json({}, { status: 400 });
+ }
+
+ return HttpResponse.json({});
+ },
+ ),
+ http.delete(`${process.env.NEXT_PUBLIC_API_BASE_URL}/groups/:id`, () => {
+ return HttpResponse.json({});
+ }),
+];
diff --git a/src/mocks/handler/notifications.ts b/src/mocks/handler/notifications.ts
new file mode 100644
index 00000000..032234ae
--- /dev/null
+++ b/src/mocks/handler/notifications.ts
@@ -0,0 +1,60 @@
+import { Notification } from '@/types';
+import { eNotification } from '@/types/enums';
+import { InfiniteResponse } from '@/types/response';
+import { http, HttpResponse } from 'msw';
+
+export const NOTIFICATIONS = [
+ {
+ id: 9999,
+ message: '์๋ฆผ ๋ฉ์์ง1',
+ isRead: false,
+ createdAt: '2025-05-20',
+ type: eNotification.GROUP_HAS_PARTICIPANT,
+ url: 'http://localhost:3000/groups/1',
+ },
+ {
+ id: 9998,
+ message: '์๋ฆผ ๋ฉ์์ง2',
+ isRead: true,
+ createdAt: '2025-05-20',
+ type: eNotification.FOLLOWER_ADDED,
+ },
+];
+
+export const notificationsHandlers = [
+ http.get(
+ `${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/notifications`,
+ ({ request }) => {
+ const url = new URL(request.url);
+ const searchParams = new URLSearchParams(url.search);
+ const isRead = searchParams.get('isRead');
+
+ const filteredNotifications = NOTIFICATIONS.filter((notification) => {
+ return isRead === 'false' ? !notification.isRead : true;
+ }).map((notification) => ({
+ ...notification,
+ createdAt: new Date(notification.createdAt),
+ url: notification.url ?? null,
+ }));
+
+ return HttpResponse.json>({
+ status: {
+ code: 200,
+ message: 'success',
+ success: true,
+ },
+ data: filteredNotifications,
+ hasNext: false,
+ cursor: 0,
+ });
+ },
+ ),
+ http.get(
+ `${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/notifications/unread-count`,
+ () => {
+ return HttpResponse.json({
+ unreadCount: 1,
+ });
+ },
+ ),
+];
diff --git a/src/mocks/handler/rating.ts b/src/mocks/handler/rating.ts
new file mode 100644
index 00000000..b2ce6cf7
--- /dev/null
+++ b/src/mocks/handler/rating.ts
@@ -0,0 +1,51 @@
+import { http, HttpResponse } from 'msw';
+
+type RatingBody = {
+ targetUserId?: number;
+ rate: number;
+}
+
+export const ratingHandlers = [
+ http.post('http://localhost:4000/api/rating', async ({ request }) => {
+ const body = await request.json() as RatingBody;
+ const { rate } = body;
+
+ if (rate > 5) {
+ return HttpResponse.json({
+ result: {
+ success: false,
+ message: 'ํ์ ์ 5์ ์ ์ด๊ณผํ ์ ์์ต๋๋ค.'
+ }
+ });
+ }
+
+ return HttpResponse.json({
+ result: {
+ success: true,
+ },
+ });
+ }),
+
+ http.patch('http://localhost:4000/api/ratings/:id', async ({ request, params }) => {
+ const body = await request.json() as RatingBody;
+ const { rate } = body;
+ const { id } = params;
+
+ console.log('๋ฐ์ ํ์ ์์ ์์ฒญ:', { id, rate });
+
+ if (rate > 5) {
+ return HttpResponse.json({
+ result: {
+ success: false,
+ message: 'ํ์ ์ 5์ ์ ์ด๊ณผํ ์ ์์ต๋๋ค.'
+ }
+ });
+ }
+
+ return HttpResponse.json({
+ result: {
+ success: true,
+ },
+ });
+ }),
+];
diff --git a/src/mocks/handler/replies.ts b/src/mocks/handler/replies.ts
new file mode 100644
index 00000000..8e838c9b
--- /dev/null
+++ b/src/mocks/handler/replies.ts
@@ -0,0 +1,237 @@
+import { Reply } from '@/types';
+import { http, HttpResponse } from 'msw';
+
+const REPLY_LIST: Reply[] = Array.from({ length: 33 }, (_, i) => ({
+ replyId: i + 1,
+ content: `๋๊ธ ${i + 1}`,
+ writer: {
+ userId: i + 1,
+ nickname: `w${i + 1}`,
+ profileImage: null,
+ email: `w${i + 1}@gmail.com`,
+ },
+ createdAt: '2025-05-31T07:22:02.678Z',
+ deleted: false,
+}));
+
+const REREPLY_LIST: (Reply & { parentId: number })[] = Array.from(
+ { length: 500 },
+ (_, i) => ({
+ replyId: i + 1 + 10000,
+ parentId: (i % REPLY_LIST.length) + 1,
+ content: `๋๋๊ธ ${i + 1}`,
+ writer: {
+ userId: i + 1,
+ nickname: `q${i + 1}`,
+ profileImage: null,
+ email: `q${i + 1}@gmail.com`,
+ },
+ createdAt: '2025-06-10T10:39:05.678Z',
+ deleted: false,
+ }),
+);
+
+export const repliesHandlers = [
+ // ๋๊ธ ๋ฌดํ ์คํฌ๋กค๋ก ๊ฐ์ ธ์ค๊ธฐ
+ http.get(
+ `${process.env.NEXT_PUBLIC_API_BASE_URL}/v2/groups/:groupId/replies`,
+ ({ request }) => {
+ const url = new URL(request.url);
+ const cursorParam = url.searchParams.get('cursor');
+ const sizeParam = url.searchParams.get('size');
+
+ const cursor = cursorParam ? parseInt(cursorParam, 10) : 0;
+ const size = sizeParam ? parseInt(sizeParam, 10) : 10;
+
+ const foundIndex = REPLY_LIST.findIndex(
+ (item) => item.replyId === cursor,
+ );
+ const startIndex =
+ cursor === 0 ? 0 : foundIndex >= 0 ? foundIndex + 1 : REPLY_LIST.length;
+
+ const paginatedItems = REPLY_LIST.slice(startIndex, startIndex + size);
+ const nextCursor =
+ startIndex + size < REPLY_LIST.length
+ ? paginatedItems.at(-1)?.replyId
+ : null;
+ const hasNext = nextCursor !== null;
+
+ return HttpResponse.json({
+ items: paginatedItems,
+ cursor: nextCursor,
+ hasNext,
+ });
+ },
+ ),
+ // ๋๋๊ธ ๋ฌดํ ์คํฌ๋กค๋ก ๊ฐ์ ธ์ค๊ธฐ
+ http.get(
+ `${process.env.NEXT_PUBLIC_API_BASE_URL}/v2/groups/:groupId/replies/:replyId`,
+ ({ request, params }) => {
+ const url = new URL(request.url);
+ const cursorParam = url.searchParams.get('cursor');
+ const sizeParam = url.searchParams.get('size');
+
+ const cursor = cursorParam ? parseInt(cursorParam, 10) : 0;
+ const size = sizeParam ? parseInt(sizeParam, 10) : 10;
+ const parentId = Number(params.replyId); // ๋ถ๋ชจ ๋๊ธ ID
+
+ // ํด๋น ๋ถ๋ชจ ๋๊ธ์ ๋ฌ๋ฆฐ ๋๋๊ธ๋ง ํํฐ๋ง
+ const filtered = REREPLY_LIST.filter(
+ (item) => item.parentId === parentId,
+ );
+
+ const foundIndex = filtered.findIndex((item) => item.replyId === cursor);
+ const startIndex =
+ cursor === 0 ? 0 : foundIndex >= 0 ? foundIndex + 1 : filtered.length;
+
+ const paginatedItems = filtered.slice(startIndex, startIndex + size);
+ const nextCursor =
+ startIndex + size < filtered.length
+ ? paginatedItems.at(-1)?.replyId
+ : null;
+ const hasNext = nextCursor !== null;
+
+ return HttpResponse.json({
+ items: paginatedItems,
+ cursor: nextCursor,
+ hasNext,
+ });
+ },
+ ),
+ // ๋๊ธ ์ถ๊ฐ
+ http.post(
+ `${process.env.NEXT_PUBLIC_API_BASE_URL}/v2/groups/:groupId/replies`,
+ async ({ request }) => {
+ const body = (await request.json()) as { content: string };
+ const { content } = body;
+
+ const newId = REPLY_LIST.length + 1;
+ const newReplyData = {
+ replyId: newId,
+ content,
+ writer: {
+ userId: newId,
+ nickname: null,
+ profileImage: null,
+ email: `w${newId}@gmail.com`,
+ },
+ createdAt: '2025-05-23',
+ deleted: false,
+ };
+
+ REPLY_LIST.push(newReplyData);
+
+ return HttpResponse.json({
+ replyId: newReplyData.replyId,
+ });
+ },
+ ),
+ // ๋๋๊ธ ์ถ๊ฐ
+ http.post(
+ `${process.env.NEXT_PUBLIC_API_BASE_URL}/v2/groups/:groupId/replies/:replyId`,
+ async ({ request, params }) => {
+ const body = (await request.json()) as { content: string };
+ const { content } = body;
+
+ const parentId = Number(params.replyId);
+ const newId = REREPLY_LIST.length + 1 + 10000;
+ const newRereplyData = {
+ replyId: newId,
+ parentId,
+ content,
+ writer: {
+ userId: newId,
+ nickname: `n${newId}`,
+ profileImage: null,
+ email: `n${newId}@gmail.com`,
+ },
+ createdAt: '2025-05-23',
+ deleted: false,
+ };
+
+ REREPLY_LIST.push(newRereplyData);
+
+ return HttpResponse.json({
+ parentId: 10,
+ replyId: newRereplyData.replyId,
+ });
+ },
+ ),
+ // ๋๊ธ, ๋๋๊ธ ์์
+ http.patch(
+ `${process.env.NEXT_PUBLIC_API_BASE_URL}/v2/groups/:groupId/replies/:replyId`,
+ async ({ request, params }) => {
+ const body = (await request.json()) as { content: string };
+ const { content } = body;
+
+ const replyId = Number(params.replyId);
+
+ // ๋๊ธ ์์
+ const reply = REPLY_LIST.find((item) => item.replyId === replyId);
+
+ if (reply) {
+ reply.content = content;
+
+ return HttpResponse.json({});
+ }
+
+ // ๋๋๊ธ ์์
+ const rereply = REREPLY_LIST.find((item) => item.replyId === replyId);
+
+ if (rereply) {
+ rereply.content = content;
+ return HttpResponse.json({});
+ }
+
+ return new HttpResponse('ํด๋น ๋๊ธ์ ์ฐพ์ ์ ์์ต๋๋ค.', { status: 404 });
+ },
+ ), // ๋๊ธ, ๋๋๊ธ ์ญ์
+ http.delete(
+ `${process.env.NEXT_PUBLIC_API_BASE_URL}/v2/groups/:groupId/replies/:replyId`,
+ async ({ params }) => {
+ const replyId = Number(params.replyId);
+
+ // ๋๊ธ ์ญ์
+ const replyIdx = REPLY_LIST.findIndex((item) => item.replyId === replyId);
+
+ if (replyIdx !== -1) {
+ // ๋๋๊ธ์ด ํ๋๋ ์์ผ๋ฉด ๋๊ธ ์์ ์ญ์ , ๋ฌ๋ ค ์์ผ๋ฉด content์ isDeleted๋ง ๋ณ๊ฒฝ
+ if (REREPLY_LIST.some((item) => item.parentId === replyId)) {
+ REPLY_LIST[replyIdx].content = '์ญ์ ๋ ๋๊ธ์
๋๋ค.';
+ REPLY_LIST[replyIdx].deleted = true;
+ } else {
+ REPLY_LIST.splice(replyIdx, 1);
+ }
+
+ return HttpResponse.json({});
+ }
+
+ // ๋๋๊ธ ์ญ์
+ const rereplyIdx = REREPLY_LIST.findIndex(
+ (item) => item.replyId === replyId,
+ );
+
+ if (rereplyIdx !== -1) {
+ const parentReplyIdx = REPLY_LIST.findIndex(
+ (item) => item.replyId === REREPLY_LIST[rereplyIdx].parentId,
+ );
+
+ // ๋๋๊ธ ์ญ์
+ REREPLY_LIST.splice(rereplyIdx, 1);
+
+ // ๋ถ๋ชจ ๋๊ธ๋ ์ญ์ ๋ ์ํ๋ฉด ๋ถ๋ชจ ๋๊ธ๋ ์์ ์ญ์
+ if (
+ REPLY_LIST[parentReplyIdx].deleted &&
+ REREPLY_LIST.some((item) => item.parentId === replyId)
+ ) {
+ REPLY_LIST.splice(replyIdx, 1);
+ }
+
+ return HttpResponse.json({});
+ }
+
+ // ๋ชป ์ฐพ์ผ๋ฉด 404
+ return new HttpResponse('ํด๋น ๋๊ธ์ ์ฐพ์ ์ ์์ต๋๋ค.', { status: 404 });
+ },
+ ),
+];
diff --git a/src/mocks/handler/user.ts b/src/mocks/handler/user.ts
new file mode 100644
index 00000000..da9f6d08
--- /dev/null
+++ b/src/mocks/handler/user.ts
@@ -0,0 +1,114 @@
+import { http, HttpResponse } from 'msw';
+import { User } from '@/types';
+import { Position, Skill } from '@/types/enums';
+import { Group, GroupType } from '@/types';
+
+export const userHandlers = [
+ http.get(
+ `${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/user/:id`,
+ ({ params }) => {
+ const { id } = params;
+
+ if (id === '10') {
+ return HttpResponse.json(null);
+ }
+
+ return HttpResponse.json({
+ userId: 1,
+ email: 'test@test.com',
+ nickname: 'ํ
์คํธ๋๋ค์',
+ profileImage: 'https://github.com/shadcn.png',
+ position: Position.FE,
+ skills: [Skill.Java, Skill.JavaScript, Skill.Spring],
+ rate: 4.5,
+ isFollowing: false,
+ isFollower: false,
+ });
+ },
+ ),
+
+ http.delete(`${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/user/delete`, () => {
+ if (Math.trunc(Math.random() * 100) % 2) {
+ return HttpResponse.json({
+ success: true,
+ message: 'ํ์ ํํด ์๋ฃ',
+ });
+ }
+ return HttpResponse.json({
+ success: false,
+ message: '์๋ฌ ๋ฉ์์ง',
+ });
+ }),
+
+ http.patch(
+ `${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/user/password`,
+ async ({ request }) => {
+ const body = (await request.json()) as {
+ newPassword: string;
+ confirmPassword: string;
+ };
+ console.log(body?.newPassword, body?.confirmPassword);
+ return new HttpResponse(null, {
+ status: Math.trunc(Math.random() * 100) % 2 === 0 ? 200 : 500,
+ });
+ },
+ ),
+
+ http.patch(
+ `${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/user/edit`,
+ async ({ params, request }) => {
+ const body = await request.formData();
+ const nickname = body.get('nickname') as string;
+ const position = JSON.parse(body.get('position') as string) as Position;
+ const skills = JSON.parse(body.get('skills') as string) as Skill[];
+ const file = body.get('file') as File;
+
+ const imgSrc = file
+ ? 'https://github.com/shadcn.png'
+ : 'https://media.istockphoto.com/id/1337144146/vector/default-avatar-profile-icon-vector.jpg?s=612x612&w=0&k=20&c=BIbFwuv7FxTWvh5S3vB6bkT0Qv8Vn8N5Ffseq84ClGI=';
+
+ return HttpResponse.json({
+ userId: Number(params.id),
+ nickname,
+ position,
+ skills,
+ profileImage: imgSrc,
+ email: 'me@example.com',
+ isFollowing: false,
+ isFollower: false,
+ rate: 4.5,
+ });
+ },
+ ),
+
+ http.get(
+ `${process.env.NEXT_PUBLIC_API_BASE_URL}/user/groups`,
+ ({ request }) => {
+ const url = new URL(request.url);
+ console.log(url);
+
+ const items: Group[] = Array.from({ length: 10 }, () => ({
+ id: Math.floor(Math.random() * 1000000) + 1,
+ title: '์คํฐ๋1',
+ deadline: new Date('2025-05-22').toISOString(),
+ startDate: new Date('2025-05-20').toISOString(),
+ endDate: new Date('2025-05-24').toISOString(),
+ maxParticipants: 10,
+ participants: [],
+ description: '์คํฐ๋1 ์ค๋ช
',
+ position: [1, 3],
+ skills: [1, 2],
+ createdAt: new Date('2025-05-20'),
+ type: GroupType.STUDY,
+ autoAllow: true,
+ isBookmark: false,
+ }));
+
+ return HttpResponse.json({
+ hasNext: true,
+ items,
+ cursor: 10,
+ });
+ },
+ ),
+];
diff --git a/src/mocks/handler/ws.ts b/src/mocks/handler/ws.ts
new file mode 100644
index 00000000..937efd93
--- /dev/null
+++ b/src/mocks/handler/ws.ts
@@ -0,0 +1,107 @@
+import { Server } from 'mock-socket';
+import { eNotification } from '@/types/enums';
+
+let mockServer: Server | null = null;
+
+const getRandomNotificationType = () => {
+ const enumLength = Object.keys(eNotification).length / 2; // enum์ ํค์ ๊ฐ์ด ์๋ฐฉํฅ์ผ๋ก ๋งคํ๋์ด ์ค์ ๊ธธ์ด์ 2๋ฐฐ
+ return Math.floor(Math.random() * enumLength);
+};
+
+const createMockNotification = (id: number) => {
+ const type = getRandomNotificationType();
+ console.log('type', type);
+ let message = `์๋ฆผ ๋ฉ์์ง ${id}`;
+ let url = '/v2/groups/1'; // ๊ธฐ๋ณธ URL
+
+ switch (type) {
+ case eNotification.GROUP_HAS_PARTICIPANT:
+ message = '์๋ก์ด ์ฐธ๊ฐ์๊ฐ ๊ทธ๋ฃน์ ์ฐธ์ฌํ์ต๋๋ค.';
+ url = '/v2/groups/1/participants';
+ break;
+ case eNotification.CONFIRMED_PARTICIPANT_CANCELED:
+ message = 'ํ์ ๋ ์ฐธ๊ฐ์๊ฐ ์ทจ์ํ์ต๋๋ค.';
+ url = '/v2/groups/1';
+ break;
+ case eNotification.APPLY_APPROVED:
+ message = '๋ชจ์ ์ฐธ๊ฐ ์ ์ฒญ์ด ์น์ธ๋์์ต๋๋ค.';
+ url = '/groups/1';
+ break;
+ case eNotification.APPLY_REJECTED:
+ message = '๋ชจ์ ์ฐธ๊ฐ ์ ์ฒญ์ด ๊ฑฐ์ ๋์์ต๋๋ค.';
+ break;
+ case eNotification.FULL_CAPACITY:
+ message = '๋ชจ์ ์ ์์ด ๋ง๊ฐ๋์์ต๋๋ค.';
+ url = '/groups/1';
+ break;
+ case eNotification.FOLLOWER_ADDED:
+ message = '์๋ก์ด ํ๋ก์๊ฐ ์ถ๊ฐ๋์์ต๋๋ค.';
+ url = '/users/1';
+ break;
+ case eNotification.APPLY_CANCELED:
+ message = '๋ชจ์ ์ ์ฒญ์ด ์ทจ์๋์์ต๋๋ค.';
+ break;
+ case eNotification.COMMENT_RECEIVED:
+ message = '์๋ก์ด ๋๊ธ์ด ๋ฌ๋ ธ์ต๋๋ค.';
+ url = '/groups/1#comment-1';
+ break;
+ case eNotification.FOLLOWER_CREATE_GROUP:
+ message = 'ํ๋ก์ฐํ๋ ์ฌ์ฉ์๊ฐ ์ ๋ชจ์์ ๋ง๋ค์์ต๋๋ค.';
+ url = '/groups/1';
+ break;
+ case eNotification.REJECTED_GROUP:
+ message = '๋ชจ์ ์ ์ฒญ์ด ๊ฑฐ์ ๋์์ต๋๋ค.';
+ break;
+ case eNotification.WITHIN_24_HOUR:
+ message = '๋ถ๋งํฌํ ๋ชจ์์ด 24์๊ฐ ํ์ ์์๋ฉ๋๋ค.';
+ url = '/groups/1';
+ break;
+ }
+
+ return {
+ type,
+ notification: {
+ id,
+ message,
+ url,
+ isRead: false,
+ createdAt: new Date().toISOString(),
+ },
+ };
+};
+
+// ๋ชฉ์๋ฒ ์์ฑ
+export const initMockSocket = () => {
+ if (mockServer) {
+ console.warn('mock ์๋ฒ ์ด๋ฏธ ์คํ๋จ');
+ return;
+ }
+
+ mockServer = new Server('ws://localhost:8080');
+
+ mockServer.on('connection', socket => {
+ console.log('mock ์ฐ๊ฒฐ๋จ');
+ let notificationId = 1;
+
+ // ์ด๊ธฐ ์๋ฆผ ์ ์ก
+ setTimeout(() => {
+ socket.send(JSON.stringify(createMockNotification(notificationId++)));
+ }, 2000);
+
+ // ์ฃผ๊ธฐ์ ์ผ๋ก ์๋ฆผ ์ ์ก (5-15์ด ๋๋ค ๊ฐ๊ฒฉ)
+ const sendRandomNotification = () => {
+ const randomDelay = Math.floor(Math.random() * (15000 - 5000) + 5000);
+ setTimeout(() => {
+ socket.send(JSON.stringify(createMockNotification(notificationId++)));
+ sendRandomNotification();
+ }, randomDelay);
+ };
+
+ sendRandomNotification();
+
+ // socket.on('message', data => {
+ // console.log('์๋ฒ ์์ :', data);
+ // socket.send('echo: ' + data);
+ // });
+ });
+};
\ No newline at end of file
diff --git a/src/mocks/index.ts b/src/mocks/index.ts
new file mode 100644
index 00000000..9a93f176
--- /dev/null
+++ b/src/mocks/index.ts
@@ -0,0 +1,13 @@
+export async function initMocks() {
+ if (process.env.NODE_ENV !== 'development') return;
+ console.log('initMocks');
+ if (typeof window === 'undefined') {
+ // ์๋ฒ ์ฌ์ด๋
+ const { server } = await import('./server');
+ server.listen();
+ } else {
+ // ํด๋ผ์ด์ธํธ ์ฌ์ด๋
+ const { worker } = await import('./browser');
+ await worker.start();
+ }
+}
diff --git a/src/mocks/server.ts b/src/mocks/server.ts
new file mode 100644
index 00000000..0859d980
--- /dev/null
+++ b/src/mocks/server.ts
@@ -0,0 +1,20 @@
+import { setupServer } from 'msw/node';
+import { applicationsHandlers } from './handler/applications';
+import { authenticationsHandlers } from './handler/authentications';
+import { followHandlers } from './handler/follow';
+import { groupsHandlers } from './handler/groups';
+import { notificationsHandlers } from './handler/notifications';
+import { ratingHandlers } from './handler/rating';
+import { repliesHandlers } from './handler/replies';
+import { userHandlers } from './handler/user';
+
+export const server = setupServer(
+ ...groupsHandlers,
+ ...notificationsHandlers,
+ ...followHandlers,
+ ...authenticationsHandlers,
+ ...repliesHandlers,
+ ...userHandlers,
+ ...ratingHandlers,
+ ...applicationsHandlers,
+);
diff --git a/src/providers/MSWComponent.tsx b/src/providers/MSWComponent.tsx
new file mode 100644
index 00000000..fa6e3798
--- /dev/null
+++ b/src/providers/MSWComponent.tsx
@@ -0,0 +1,23 @@
+'use client';
+
+import { initMocks } from '@/mocks';
+import { useEffect, useState } from 'react';
+
+export const MSWComponent = ({ children }: { children: React.ReactNode }) => {
+ const [mswReady, setMswReady] = useState(false);
+ useEffect(() => {
+ const init = async () => {
+ await initMocks();
+ setMswReady(true);
+ };
+
+ if (!mswReady) {
+ init();
+ }
+ }, [mswReady]);
+
+ if (!mswReady) {
+ return null;
+ }
+ return <>{children}>;
+};
diff --git a/src/providers/ReactQueryProvider.tsx b/src/providers/ReactQueryProvider.tsx
new file mode 100644
index 00000000..a6dae7a3
--- /dev/null
+++ b/src/providers/ReactQueryProvider.tsx
@@ -0,0 +1,46 @@
+// In Next.js, this file would be called: app/providers.tsx
+'use client';
+
+import {
+ isServer,
+ QueryClient,
+ QueryClientProvider,
+} from '@tanstack/react-query';
+
+import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
+
+function makeQueryClient() {
+ return new QueryClient({
+ defaultOptions: {
+ queries: {
+ staleTime: 60 * 1000, // ๊ธฐ๋ณธ ์บ์ฑ ์๊ฐ(1๋ถ)
+ },
+ },
+ });
+}
+
+let browserQueryClient: QueryClient | undefined = undefined;
+
+export function getQueryClient() {
+ if (isServer) {
+ return makeQueryClient(); // ์๋ฒ์์ ์คํ ์ค์ธ ๊ฒฝ์ฐ ์ ํด๋ผ์ด์ธํธ๋ฅผ ๋ฐํ
+ } else {
+ if (!browserQueryClient) browserQueryClient = makeQueryClient(); // ๋ธ๋ผ์ฐ์ ์์ ํด๋ผ์ด์ธํธ๊ฐ ์๋ ๊ฒฝ์ฐ ์ ํด๋ผ์ด์ธํธ๋ฅผ ์์ฑ
+ return browserQueryClient; // ๊ธฐ์กด ๋ธ๋ผ์ฐ์ ํด๋ผ์ด์ธํธ๋ฅผ ๋ฐํ
+ }
+}
+
+export function ReactQueryProvider({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ const queryClient = getQueryClient();
+
+ return (
+
+ {children}
+
+
+ );
+}
diff --git a/src/providers/WSProvider.tsx b/src/providers/WSProvider.tsx
new file mode 100644
index 00000000..a1549d4d
--- /dev/null
+++ b/src/providers/WSProvider.tsx
@@ -0,0 +1,90 @@
+// providers/WebSocketProvider.tsx
+'use client';
+
+import useAuthStore from '@/stores/useAuthStore';
+import useNotificationStore from '@/stores/useNotificationStore';
+import { eNotification } from '@/types/enums';
+import { createContext, useContext, useEffect, useState } from 'react';
+import { Socket, io } from 'socket.io-client';
+
+type SocketMessage = {
+ id: number;
+ message: string;
+ notificationType: keyof typeof eNotification;
+ targetUserId: number;
+ url: string;
+};
+
+const SocketContext = createContext(null);
+
+export const SocketProvider = ({ children }: { children: React.ReactNode }) => {
+ const [socket, setSocket] = useState(null);
+ const user = useAuthStore((state) => state.user);
+
+ const { addNotification } = useNotificationStore();
+
+ useEffect(() => {
+ if (!user) {
+ socket?.disconnect();
+ setSocket(null);
+ return;
+ }
+ const newSocket = io(`${process.env.NEXT_PUBLIC_SOCKET_URL}`, {
+ withCredentials: true,
+ autoConnect: false,
+ transports: ['websocket', 'polling'],
+ });
+
+ // ์ฐ๊ฒฐ ์ด๋ฒคํธ ํธ๋ค๋ฌ
+ newSocket.on('connect', () => {
+ console.log('socket.io ์ฐ๊ฒฐ๋จ', user.userId);
+ // ๋ก๊ทธ์ธ ์ด๋ฒคํธ์ ์ฝ๋ฐฑ ์ถ๊ฐ
+ newSocket.emit('login', user.userId);
+ newSocket.on('loginSuccess', (response: { userId: number }) => {
+ console.log('์๋ฒ ์๋ต:', response.userId);
+ if (response.userId !== user.userId) {
+ newSocket.disconnect();
+ setSocket(null);
+ return;
+ }
+ });
+ });
+
+ // ๋ฉ์์ง ์ด๋ฒคํธ ํธ๋ค๋ฌ - connect ๋ฐ์์ ๋ฑ๋ก
+ newSocket.on('notification', (message: SocketMessage) => {
+ addNotification({
+ id: Math.random(),
+ message: message.message,
+ isRead: false,
+ createdAt: new Date(),
+ type: eNotification[
+ message.notificationType as keyof typeof eNotification
+ ],
+ url: message.url,
+ });
+ console.log('messageC ์ด๋ฒคํธ ๋ฐ์!', message);
+ console.log('store', useNotificationStore.getState().notifications);
+
+ console.log('๋ฐ์ ๋ฉ์์ง ์์ธ:', {
+ notificationType: message.notificationType,
+ targetUserId: message.targetUserId,
+ url: message.url,
+ });
+ });
+
+ newSocket.connect();
+ setSocket(newSocket);
+
+ return () => {
+ newSocket.removeAllListeners(); // ๋ชจ๋ ๋ฆฌ์ค๋ ์ ๊ฑฐ
+ newSocket.disconnect();
+ };
+ // eslint-disable-next-line
+ }, [user]);
+
+ return (
+ {children}
+ );
+};
+
+export const useSocket = () => useContext(SocketContext);
diff --git a/src/stores/useAuthStore.ts b/src/stores/useAuthStore.ts
new file mode 100644
index 00000000..87dd1995
--- /dev/null
+++ b/src/stores/useAuthStore.ts
@@ -0,0 +1,61 @@
+import { request } from '@/api/request';
+import { User } from '@/types';
+import { UserInfoResponse } from '@/types/response';
+import { create } from 'zustand';
+import { persist } from 'zustand/middleware';
+
+export type UserStore = {
+ user: User | null;
+ setUser: (user: User) => void;
+ clearUser: () => void;
+ fetchAndSetUser: () => Promise;
+};
+
+/**
+ * Zustand๋ฅผ ์ฌ์ฉํด ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์ ์ญ์ผ๋ก ๊ด๋ฆฌํ๋ ํ
+ * - user: ํ์ฌ ๋ก๊ทธ์ธํ ์ฌ์ฉ์ ์ ๋ณด, ์ด๊ธฐ๊ฐ(๋ก๊ทธ์ธ ์๋ ์ํ)์ null์
๋๋ค
+ * - setUser: ๋ก๊ทธ์ธํ ์ ์ ์ ๋ณด๋ฅผ user param์ผ๋ก ๋ฐ์ ์ค์ ํฉ๋๋ค
+ * !!ํ๋กํ ์์ ์ setUser ํจ์๋ก ์ค์ ํด์ฃผ์
์ผ ํฉ๋๋ค!!
+ * - clearUser: ํ์ฌ ๋ก๊ทธ์ธ ์ ์ ์ ๋ณด๋ฅผ null๋ก ์ค์ ํ์ฌ ์ง์๋๋ค.
+ */
+const useAuthStore = create()(
+ persist(
+ (set) => ({
+ user: null,
+ setUser: (user: User) => set({ user }),
+ clearUser: () => set({ user: null }),
+ fetchAndSetUser: async () => {
+ try {
+ const responseBody: UserInfoResponse = await request.get(
+ '/v1/user/info',
+ {},
+ {
+ credentials: 'include',
+ },
+ );
+
+ if (responseBody.status.success) {
+ set({
+ user: {
+ //@ts-expect-error ๋ฐฑ์๋์์ ์ ๊ณตํ๋ ํ์
์ด ์ด์ํด์ ์์๋ก ์ฒ๋ฆฌ
+ userId: responseBody.items.items.id,
+ ...responseBody.items.items,
+ profileImage: responseBody.items.items.profileImage ?? '/images/default-profile.png',
+ },
+ });
+ } else {
+ throw new Error('์ ์ ์ ๋ณด ๋ถ๋ฌ์ค๊ธฐ ์คํจ');
+ }
+ } catch (error) {
+ console.error('์ ์ ์ ๋ณด ๋ถ๋ฌ์ค๊ธฐ ์คํจ', error);
+ throw error; // ํ์์ ์๋ฌ ์ฒ๋ฆฌ ๋ก์ง ์ถ๊ฐ
+ }
+ },
+ }),
+ {
+ name: 'user-store',
+ },
+ ),
+);
+
+export default useAuthStore;
diff --git a/src/stores/useNotificationStore.test.ts b/src/stores/useNotificationStore.test.ts
new file mode 100644
index 00000000..e7172101
--- /dev/null
+++ b/src/stores/useNotificationStore.test.ts
@@ -0,0 +1,49 @@
+import { NOTIFICATIONS } from '@/mocks/handler/notifications';
+import { http, HttpResponse } from 'msw';
+import { server } from '@/mocks/server';
+import useNotificationStore from './useNotificationStore';
+
+beforeAll(() => server.listen());
+afterEach(() => server.resetHandlers());
+afterAll(() => server.close());
+
+describe('useNotificationStore ํ
์คํธ', () => {
+ it('clearAllNotifications: ์๋ฒ์ DELETE ์์ฒญ ํ ์ํ ์ด๊ธฐํ', async () => {
+ server.use(
+ http.delete('/api/v1/notification', () => {
+ return HttpResponse.json({ result: { success: true } });
+ })
+ );
+
+ useNotificationStore.setState({
+ notifications: NOTIFICATIONS.map(n => ({
+ ...n,
+ createdAt: new Date(n.createdAt),
+ url: n.url ?? null
+ })),
+ unreadCount: 1,
+ });
+
+ await useNotificationStore.getState().clearAllNotifications();
+
+ const { notifications, unreadCount } = useNotificationStore.getState();
+ expect(notifications).toEqual([]);
+ expect(unreadCount).toBe(0);
+ });
+
+ it('์ฝ์ ์ฒ๋ฆฌ ํ ์ ์ฝ์ ์๋ฆผ ๊ฐ์ ๊ฐ์', () => {
+ useNotificationStore.setState({
+ notifications: NOTIFICATIONS.map(n => ({
+ ...n,
+ createdAt: new Date(n.createdAt),
+ url: n.url ?? null
+ })),
+ unreadCount: 1,
+ });
+
+ useNotificationStore.getState().setReadNotification(NOTIFICATIONS[0].id);
+
+ const { unreadCount } = useNotificationStore.getState();
+ expect(unreadCount).toBe(0);
+ });
+});
\ No newline at end of file
diff --git a/src/stores/useNotificationStore.ts b/src/stores/useNotificationStore.ts
new file mode 100644
index 00000000..83cd3673
--- /dev/null
+++ b/src/stores/useNotificationStore.ts
@@ -0,0 +1,68 @@
+import { create } from 'zustand';
+import { Notification } from '@/types';
+import { request } from '@/api/request';
+
+export type NotificationState = {
+ notifications: Notification[];
+ unreadCount: number;
+ }
+
+export type NotificationActions = {
+ init: () => void;
+ addNotification: (notification: Notification) => void;
+ setNotifications: (notifications: Notification[]) => void;
+ setReadNotification: (id: number) => void;
+ setUnreadCount: (count: number) => void;
+ clearAllNotifications: () => Promise;
+}
+
+type NotificationStore = NotificationState & NotificationActions;
+
+const useNotificationStore = create((set, get) => ({
+ notifications: [],
+ unreadCount: 0,
+ addNotification: (notification: Notification) => {
+ const { notifications } = get();
+ const exists = notifications.some(n => n?.id === notification.id); // ์ด๋ฏธ ๊ฐ์ ID์ ์๋ฆผ์ด ์๋์ง ํ์ธ
+
+ if (exists) return; // ์ด๋ฏธ ์กด์ฌํ๋ฉด ์ํ ๋ณ๊ฒฝ ์์
+ set((state) => ({
+ notifications: [notification, ...state.notifications],
+ unreadCount: state.unreadCount + 1,
+ }));
+ },
+ setNotifications: (notifications: Notification[]) => set(() => ({
+ notifications,
+ })),
+ setReadNotification: async (id: number) => {
+ const { notifications } = get();
+ await request.patch(`/v1/notification/${id}/read`, {},{}, { credentials: 'include' });
+ set((state) => {
+ const updatedNotifications = notifications.map((notification: Notification) => ({
+ ...notification,
+ isRead: notification.id === id ? true : notification.isRead,
+ }));
+ return {
+ unreadCount: state.unreadCount - 1,
+ notifications: updatedNotifications,
+ };
+ });
+ },
+ setUnreadCount: (count: number) => set(() => ({
+ unreadCount: count,
+ })),
+ clearAllNotifications: async () => {
+ console.log('clearAllNotifications');
+ await fetch('/api/v1/notification', { method: 'DELETE' });
+ set(() => ({
+ notifications: [],
+ unreadCount: 0,
+ }));
+ },
+ init: () => set(() => ({
+ notifications: [],
+ unreadCount: 0,
+ })),
+}));
+
+export default useNotificationStore;
diff --git a/src/stores/useTargetReply.ts b/src/stores/useTargetReply.ts
new file mode 100644
index 00000000..bc9a6616
--- /dev/null
+++ b/src/stores/useTargetReply.ts
@@ -0,0 +1,16 @@
+import { create } from 'zustand';
+
+export type TargetReplyStore = {
+ targetReplyId: null | number;
+ targetRereplyId: null | number;
+ setTargetReply: (input: {
+ targetReplyId?: number | null;
+ targetRereplyId?: number | null;
+ }) => void;
+};
+
+export const useTargetReplyStore = create((set) => ({
+ targetReplyId: null,
+ targetRereplyId: null,
+ setTargetReply: (input) => set((state) => ({ ...state, ...input })),
+}));
diff --git a/src/types/enums.ts b/src/types/enums.ts
new file mode 100644
index 00000000..b59f5834
--- /dev/null
+++ b/src/types/enums.ts
@@ -0,0 +1,44 @@
+export enum Position {
+ 'PM',
+ 'PL',
+ 'AA',
+ 'TA',
+ 'DA',
+ 'QA',
+ 'FE',
+ 'BE',
+ 'FS',
+}
+
+export enum Skill {
+ 'Java',
+ 'JavaScript',
+ 'HTML_CSS',
+ 'REACT',
+ 'Vue',
+ 'Kotlin',
+ 'Spring',
+}
+
+/** ์๋ฆผ ํ์
*/
+export enum eNotification {
+ GROUP_HAS_PARTICIPANT, // ๋ด๊ฐ ์์ฑํ ๊ธ์ ์ฐธ์ฌ์ ์๊น -> ์ ์ฒญ ์๋ฝ ํ์ด์ง, ์์ฑ์(๋ชจ์ ์์ฑ์)ํํ
์๋, ๋ชจ์ ID
+ CONFIRMED_PARTICIPANT_CANCELED, // ํ์ ์ฐธ์ฌ์ ์ทจ์ -> ์์ธํ์ด์ง, ์์ฑ์ํํ
์๋, ๋ชจ์ ID
+ APPLY_APPROVED, // ๋ด๊ฐ ์ ์ฒญํ ๋ชจ์ ์น์ธ-> ์์ธํ์ด์ง, ์ ์ฒญ์ํํ
์๋
+ APPLY_REJECTED, // ๋ด๊ฐ ์ ์ฒญํ ๋ชจ์ ๊ฑฐ์ -> x, ์ ์ฒญ์ํํ
์๋
+ FULL_CAPACITY, // ๋ชจ์ ์ ์ ๋ง๊ฐ-> ์์ธํ์ด์ง, ์์ฑ์ํํ
์๋
+ FOLLOWER_ADDED, // ํ๋ก์ ์ถ๊ฐ๋ ๊ฒฝ์ฐ ์๋ -> ์ ์ ์์ธ ํ์ด์ง, ํ๋ก์๋ ์ฌ๋ํํ
์๋
+ APPLY_CANCELED, // ๋ด๊ฐ ์ ์ฒญํ ๋ชจ์ ์ทจ์ -> x, ์ ์ฒญ์ํํ
์๋
+ COMMENT_RECEIVED, // ๋๊ธ ๋ฌ๋ฆฐ ๊ฒฝ์ฐ -> ํด๋น ํ์ด์ง ๋๊ธ๋ก ๋ฐ๋ก, ์์ธํ์ด์ง id๋, ํด๋น ๋๊ธ id ํ์, ์์ฑ์ํํ
์๋, ๋๋๊ธ์ ๊ฒฝ์ฐ(์์ฑ์, ์๋๊ธ์์๊ฒ ์๋)
+ FOLLOWER_CREATE_GROUP, // ํ๋ก์๊ฐ ๋ชจ์์ ๋ง๋ ๊ฒฝ์ฐ ์๋ -> ์์ธํ์ด์ง, ํ๋ก์ฐํ์ฌ๋ํํ
์๋
+ REJECTED_GROUP, // ๋ชจ์ ์ ์ฒญ์ด ๊ฑฐ์ ๋ ๊ฒฝ์ฐ, ์ ์ฒญ์ํํ
์๋
+ WITHIN_24_HOUR, // ๋ถ๋งํฌํ ๋ชจ์์ด 24์๊ฐ ๋จ์ ๊ฒฝ์ฐ ์๋ -> ์์ธํ์ด์ง, ๋ถ๋งํฌํ ์ฌ๋ํํ
์๋
+}
+
+export function getPosition(position: Position): string {
+ return Position[position];
+}
+
+export function getSkill(skill: Skill): string {
+ return Skill[skill];
+}
diff --git a/src/types/index.ts b/src/types/index.ts
index c2d2409d..f02a4d03 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -1,9 +1,138 @@
//์ ์ญ ํ์ผ ํ์
์ ๋ฆฌ
-
+import { eNotification, Position, Skill } from './enums';
export type User = {
- id: string;
- name: string;
+ userId: number;
+ nickname: string | null;
email: string;
- profileImage: string;
-};
\ No newline at end of file
+ profileImage: string | null;
+ position: Position | null;
+ skills: Skill[] | null;
+ isFollowing: boolean;
+ isFollower: boolean;
+ rate: number;
+};
+
+export type UserSummary = Pick<
+ User,
+ 'userId' | 'nickname' | 'profileImage' | 'email'
+>;
+
+export enum GroupType {
+ STUDY = 'study',
+ PROJECT = 'project',
+}
+export const GroupTypeName = {
+ study: '์คํฐ๋',
+ project: 'ํ๋ก์ ํธ',
+};
+
+export type WriteFormWithCreatedAt = WriteForm & { createdAt: Date };
+/** ์ ๊ณตํด์ฃผ๋ ๊ธฐ๋ณธ skill์ ์ด๋ฆ๋ค. enum Skill๊ณผ ๋๊ธฐํ๋์ด์ผ ํจ */
+export const DEFAULT_SKILL_NAMES = [
+ 'Java',
+ 'JavaScript',
+ 'HTML_CSS',
+ 'REACT',
+ 'Vue',
+ 'Kotlin',
+ 'Spring',
+] as const;
+/** ์ ๊ณตํด์ฃผ๋ ๊ธฐ๋ณธ skill์ ์ด๋ฆ๋ค์ ํ์
. UI์ฉ */
+export type DefaultSkillName = (typeof DEFAULT_SKILL_NAMES)[number];
+/** ์ ์ ๊ฐ ์
๋ ฅํ skill๋ ์ฌ์ฉํ๊ธฐ ์ํด ๋ง๋ ํ์
. UI์ฉ */
+export type SkillName = DefaultSkillName | string;
+
+/** ์ ๊ณตํด์ฃผ๋ ๊ธฐ๋ณธ position์ ์ด๋ฆ๋ค. enum Position๊ณผ ๋๊ธฐํ๋์ด์ผ ํจ */
+export const DEFAULT_POSITION_NAMES = [
+ 'PM',
+ 'PL',
+ 'AA',
+ 'TA',
+ 'DA',
+ 'QA',
+ 'FE',
+ 'BE',
+ 'FS',
+] as const;
+/** ์ ๊ณตํด์ฃผ๋ ๊ธฐ๋ณธ position์ ์ด๋ฆ๋ค์ ํ์
. UI์ฉ */
+export type DefaultPositionName = (typeof DEFAULT_POSITION_NAMES)[number];
+/** ์ ์ ๊ฐ ์
๋ ฅํ skill๋ ์ฌ์ฉํ๊ธฐ ์ํด ๋ง๋ ํ์
. UI์ฉ */
+export type PositionName = DefaultPositionName | string;
+
+/** ๋ชจ์ ๋ง๋ค๊ธฐ ํผ์ ์ฌ์ฉ๋๋ ๋ฐ์ดํฐ๋ค์ ํ์
*/
+export type WriteForm = {
+ title: string;
+ maxParticipants: number;
+ deadline: Date;
+ startDate: Date;
+ endDate: Date;
+ description: string;
+ autoAllow: boolean;
+ type: GroupType;
+ skills: SkillName[];
+ position: PositionName[];
+};
+
+export type Group = {
+ id: number;
+ title: string;
+ deadline: string;
+ startDate: string; // ๋ชจ์์ ์์์ผ
+ endDate: string; // ๋ชจ์์ ์ข
๋ฃ์ผ
+ maxParticipants: number;
+ participants: UserSummary[];
+ description: string;
+ position: Position[];
+ skills: Skill[];
+ createdAt: Date;
+ isBookmark: boolean;
+ autoAllow: boolean;
+ type: GroupType;
+};
+
+export type GroupSort = 'createdAt' | 'deadline';
+
+export type Order = 'asc' | 'desc';
+
+export type Notification = {
+ id: number;
+ message: string | null; // ์ถํ ์ฑํ
์ ๊ฒฝ์ฐ ๊ฐ์ฅ ๊ฐ๋ตํ ์ฑํ
๋ฉ์ธ์ง ์ ๋ฌ์ฉ ํ์ฌ๋ ๋น๊ฐ์ผ๋ก ๋ฆฌํด
+ content?: string | null; // TODO: socket, notification ํ์
ํต์ผ ํ ์ ๊ฑฐ
+ isRead: boolean; // ์ฝ์ ์ฌ๋ถ default: false
+ read?: boolean; // TODO: socket, notification ํ์
ํต์ผ ํ ์ ๊ฑฐ
+ created_at?: Date; // ์๋ ์์ฑ๋ ์ง
+ createdAt: Date; // ์๋ ์์ฑ๋ ์ง
+ type: eNotification;
+ url: string | null; // ์ฐ๊ฒฐ๋๋ url -> NotificationType์ ๋ฐ๋ผ ํ์ํ ๋ถ๋ถ ๋ค๋ฆ
+};
+
+export type Reply = {
+ replyId: number;
+ content: string;
+ writer: UserSummary;
+ createdAt: string;
+ deleted: boolean; // ์ญ์ ๋ ๋๊ธ์ธ์ง ์ฌ๋ถ
+};
+
+export type GroupDetail = {
+ group: {
+ id: number;
+ title: string;
+ description: string;
+ autoAllow: boolean;
+ maxParticipants: number;
+ type: GroupType;
+ skills: Skill[];
+ position: Position[];
+ deadline: string;
+ startDate: string;
+ endDate: string;
+ createdAt: string;
+ participants: UserSummary[];
+ isBookmark: boolean;
+ };
+ host: UserSummary;
+ isApplicant: boolean;
+ isJoined: boolean;
+};
diff --git a/src/types/response.ts b/src/types/response.ts
new file mode 100644
index 00000000..fb247c5a
--- /dev/null
+++ b/src/types/response.ts
@@ -0,0 +1,32 @@
+import { User } from '.';
+
+
+// ISSUE: ๋ฐฑ์๋ ์๋ต ํ์
์์ ํ์
+export type UserInfoResponse = {
+ status: { code: number; message: string; success: boolean };
+ items: {
+ averageRating: number;
+ items: User;
+ };
+ userId?: string; // ์์ ํ๋, ๋ฐฑ์๋ ์๋ต ํ์
์์ ํ ์ ๊ฑฐ
+};
+
+export type InfiniteResponse = {
+ status: {
+ code: number;
+ message: string;
+ success: boolean;
+ };
+ data: T[];
+ hasNext: boolean;
+ cursor: number;
+};
+
+export type CommonResponse = {
+ status: {
+ code: number;
+ message: string;
+ success: boolean;
+ };
+ data: T;
+};
diff --git a/src/utils/cookie.ts b/src/utils/cookie.ts
new file mode 100644
index 00000000..aa8a8515
--- /dev/null
+++ b/src/utils/cookie.ts
@@ -0,0 +1,17 @@
+import { cookies } from 'next/headers';
+
+export const getAuthCookieHeader = async () => {
+ const ACCESS_TOKEN_KEY = process.env.ACCESS_TOKEN ?? 'accessToken';
+ const REFRESH_TOKEN_KEY = process.env.REFRESH_TOKEN ?? 'refreshToken';
+
+ const cookieStore = await cookies();
+ const accessToken = cookieStore.get(ACCESS_TOKEN_KEY);
+ const refreshToken = cookieStore.get(REFRESH_TOKEN_KEY);
+
+ return [
+ accessToken && `${ACCESS_TOKEN_KEY}=${accessToken.value}`,
+ refreshToken && `${REFRESH_TOKEN_KEY}=${refreshToken.value}`,
+ ]
+ .filter(Boolean)
+ .join('; ');
+};
diff --git a/src/utils/dateUtils.ts b/src/utils/dateUtils.ts
new file mode 100644
index 00000000..da0479ca
--- /dev/null
+++ b/src/utils/dateUtils.ts
@@ -0,0 +1,69 @@
+import {
+ differenceInDays,
+ differenceInHours,
+ differenceInMinutes,
+ differenceInSeconds,
+ format,
+ isBefore,
+} from 'date-fns';
+
+/** ๋ ์ง๋ฅผ 0000.00.00 ํํ๋ก ๋ฐํํ๋ ํจ์ */
+export const formatYearMonthDayWithDot = (utcTime: string | Date) => {
+ const date = typeof utcTime === 'string' ? new Date(utcTime) : utcTime;
+ return format(date, 'yyyy.MM.dd');
+};
+
+/** ํ์ฌ ๋ ์ง๋ณด๋ค ์ด์ ๋ ์ง์ธ์ง ํ์ธ */
+export const isBeforeToday = (utcTime: string) => {
+ const date = new Date(utcTime);
+ const now = new Date(); // ํ์ฌ ์๊ฐ ํฌํจ
+ return isBefore(date, now);
+};
+
+/**
+ * UTC ๋ฌธ์์ด์ ๋ฐ์ ํ์ฌ ์๊ฐ๊ณผ ๋น๊ตํ์ฌ ์๋ ์๊ฐ ๋๋ ๋ ์ง ๋ฌธ์์ด์ ๋ฐํํ๋ ํจ์
+ * - 1๋ถ ๋ฏธ๋ง: n์ด ์
+ * - 1์๊ฐ ๋ฏธ๋ง: n๋ถ ์
+ * - 1์ผ ๋ฏธ๋ง: n์๊ฐ ์
+ * - 7์ผ ์ดํ: n์ผ ์
+ * - ๊ทธ ์ดํ: 'yyyy๋
M์ d์ผ' ํ์์ผ๋ก ํ์
+ * @param utcTime UTC ISO ๋ฌธ์์ด (์: '2025-05-27T08:00:00Z')
+ * @returns ์๋ ์๊ฐ ๋๋ 'yyyy๋
M์ d์ผ'
+ */
+export const formatRelativeTime = (utcTime: string): string => {
+ const date = new Date(utcTime);
+ const now = new Date();
+
+ const seconds = Math.abs(differenceInSeconds(now, date));
+ const minutes = Math.abs(differenceInMinutes(now, date));
+ const hours = Math.abs(differenceInHours(now, date));
+ const days = Math.abs(differenceInDays(now, date));
+
+ if (seconds < 60) return `${seconds}์ด ์ `;
+ if (minutes < 60) return `${minutes}๋ถ ์ `;
+ if (hours < 24) return `${hours}์๊ฐ ์ `;
+ if (days <= 7) return `${days}์ผ ์ `;
+
+ return format(date, 'yyyy๋
M์ d์ผ');
+};
+
+/**
+ * UTC ๋ฌธ์์ด์ "0000๋
0์ 0์ผ ์ค์ /์คํ 0:00" ํ์์ผ๋ก ๋ฐํํ๋ ํฉ์
+ *
+ * ์: '2025-06-01T02:10:00Z' โ '2025๋
6์ 1์ผ ์ค์ 11:10'
+ *
+ * @param utcTime - UTC ISO ๋ฌธ์์ด (์: '2025-05-27T08:00:00Z')
+ * @returns ํ๊ตญ์ด ํฌ๋งท์ ๋ ์ง ๋ฐ ์๊ฐ ๋ฌธ์์ด
+ */
+export const formatDateTime = (utcTime: string): string => {
+ const date = new Date(utcTime);
+
+ return new Intl.DateTimeFormat('ko-KR', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ hour: 'numeric',
+ minute: '2-digit',
+ hour12: true,
+ }).format(date);
+};
diff --git a/src/utils/fallback.ts b/src/utils/fallback.ts
new file mode 100644
index 00000000..60fa7e98
--- /dev/null
+++ b/src/utils/fallback.ts
@@ -0,0 +1,22 @@
+/**
+ * ํ๋ฉด์ ํ์ํ ์ฌ์ฉ์ ๋๋ค์์ ๋ฐํํฉ๋๋ค.
+ * ๋๋ค์์ด ์กด์ฌํ๋ฉด ํด๋น ๋๋ค์์, ์ด๋ฉ์ผ์ @ ์๋ถ๋ถ์ ๋ฐํํฉ๋๋ค.
+ * @param nickname
+ * @param email
+ * @returns ํ๋ฉด ํ์์ฉ ์ฌ์ฉ์ ๋๋ค์
+ */
+export const getDisplayNickname = (nickname: string | null, email: string) => {
+ if(!email) return '์ด๋ฆ ์์';
+ return nickname || email.split('@')[0];
+};
+
+/**
+ * ํ๋ฉด์ ํ์ํ ํ๋กํ ์ด๋ฏธ์ง URL์ ๋ฐํํฉ๋๋ค.
+ * ํ๋กํ ์ด๋ฏธ์ง๊ฐ ์กด์ฌํ๋ฉด ํด๋น ์ด๋ฏธ์ง๋ฅผ, ์์ผ๋ฉด ๊ธฐ๋ณธ ์ด๋ฏธ์ง๋ฅผ ๋ฐํํฉ๋๋ค.
+ *
+ * @param profileImage - ํ๋กํ ์ด๋ฏธ์ง URL
+ * @returns ํ๋ฉด ํ์์ฉ ํ๋กํ ์ด๋ฏธ์ง URL
+ */
+export const getDisplayProfileImage = (profileImage: string | null) => {
+ return profileImage || '/images/default-profile.png';
+};
diff --git a/src/utils/fileUtils.ts b/src/utils/fileUtils.ts
index 6c9f3c21..766cc55c 100644
--- a/src/utils/fileUtils.ts
+++ b/src/utils/fileUtils.ts
@@ -1,2 +1,2 @@
-// ํ์ผ ์
๋ก๋ ๋ก์ง
-// ์ ์ ํ๋กํ...
\ No newline at end of file
+// ํ์ผ ์
๋ก๋ ๋ก์ง
+// ์ ์ ํ๋กํ...
diff --git a/src/utils/flattenPages.ts b/src/utils/flattenPages.ts
new file mode 100644
index 00000000..1718aa62
--- /dev/null
+++ b/src/utils/flattenPages.ts
@@ -0,0 +1,11 @@
+export type Page = {
+ status: {code:number, message:string, success:boolean},
+ data?:T[],
+ items?:T[],
+ hasNext: boolean,
+ cursor: number | null
+};
+
+export default function flattenPages(pages: Page[]) {
+ return pages.length ? pages.flatMap((page) => page.data || page.items || []) : [];
+}
diff --git a/src/utils/mockUtils.ts b/src/utils/mockUtils.ts
new file mode 100644
index 00000000..8993f58d
--- /dev/null
+++ b/src/utils/mockUtils.ts
@@ -0,0 +1,27 @@
+import { GroupType } from '@/types';
+import { Position, Skill } from '@/types/enums';
+
+/** enum Position ํค */
+export const positionKeys = Object.keys(Position).filter((key) =>
+ isNaN(Number(key)),
+) as (keyof typeof Position)[];
+
+/** enum Skill ํค */
+export const skillKeys = Object.keys(Skill).filter((key) =>
+ isNaN(Number(key)),
+) as (keyof typeof Skill)[];
+
+/** enum Group ํค */
+export const groupTypeValues = Object.values(GroupType);
+
+/** ์์ดํ
์ ๋๋คํ๊ฒ ์ ๋ ฌํด์ ๋ฐ๊ธฐ */
+export const getRandomItems = (arr: T[], count: number): T[] => {
+ const mixedArr = [...arr].sort(() => 0.5 - Math.random()); // ๋๋คํ ์ซ์๋ก ์ ๋ ฌ ์๊ธฐ
+
+ return mixedArr.slice(0, count);
+};
+
+/** ๋ฐฐ์ด์์ ๊ฐ์ ๋๋คํ๊ฒ ๋ฐ๊ธฐ */
+export const getRandomItem = (arr: T[]): T => {
+ return arr[Math.floor(Math.random() * arr.length)];
+};
diff --git a/src/utils/routes.ts b/src/utils/routes.ts
new file mode 100644
index 00000000..3acd7ef7
--- /dev/null
+++ b/src/utils/routes.ts
@@ -0,0 +1,23 @@
+export const routes = {
+ // (auth)
+ main: '/',
+ findEmail: '/find-email',
+ findPassword: '/find-password',
+ login: '/login',
+ register: '/register',
+ // (user)
+ userPage: (userId: string) => `/users/${userId}`,
+ followers: (userId: string) => `/users/${userId}/social/followers`,
+ followings: (userId: string) => `/users/${userId}/social/followings`,
+ userCreatedGroups: (userId: string) => `/users/${userId}/groups/created`,
+ userEndedGroups: (userId: string) => `/users/${userId}/groups/ended`,
+
+ // bookmark
+ bookmark: '/bookmark',
+
+ // group
+ groupDetail: (groupId: number) => `/groups/${groupId}`,
+
+ // write
+ write: '/write',
+};
diff --git a/tsconfig.json b/tsconfig.json
index c1334095..8d33a6d3 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -23,5 +23,5 @@
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
- "exclude": ["node_modules"]
+ "exclude": ["node_modules", "cypress", "**/*.cy.ts"]
}
diff --git a/yarn.lock b/yarn.lock
deleted file mode 100644
index 73616a2f..00000000
--- a/yarn.lock
+++ /dev/null
@@ -1,5207 +0,0 @@
-# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
-# yarn lockfile v1
-
-
-"@alloc/quick-lru@^5.2.0":
- version "5.2.0"
- resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30"
- integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==
-
-"@ampproject/remapping@^2.2.0", "@ampproject/remapping@^2.3.0":
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4"
- integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==
- dependencies:
- "@jridgewell/gen-mapping" "^0.3.5"
- "@jridgewell/trace-mapping" "^0.3.24"
-
-"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.27.1":
- version "7.27.1"
- resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be"
- integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==
- dependencies:
- "@babel/helper-validator-identifier" "^7.27.1"
- js-tokens "^4.0.0"
- picocolors "^1.1.1"
-
-"@babel/compat-data@^7.27.2":
- version "7.27.2"
- resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.27.2.tgz#4183f9e642fd84e74e3eea7ffa93a412e3b102c9"
- integrity sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ==
-
-"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9":
- version "7.27.1"
- resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.27.1.tgz#89de51e86bd12246003e3524704c49541b16c3e6"
- integrity sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==
- dependencies:
- "@ampproject/remapping" "^2.2.0"
- "@babel/code-frame" "^7.27.1"
- "@babel/generator" "^7.27.1"
- "@babel/helper-compilation-targets" "^7.27.1"
- "@babel/helper-module-transforms" "^7.27.1"
- "@babel/helpers" "^7.27.1"
- "@babel/parser" "^7.27.1"
- "@babel/template" "^7.27.1"
- "@babel/traverse" "^7.27.1"
- "@babel/types" "^7.27.1"
- convert-source-map "^2.0.0"
- debug "^4.1.0"
- gensync "^1.0.0-beta.2"
- json5 "^2.2.3"
- semver "^6.3.1"
-
-"@babel/generator@^7.27.1", "@babel/generator@^7.7.2":
- version "7.27.1"
- resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.27.1.tgz#862d4fad858f7208edd487c28b58144036b76230"
- integrity sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==
- dependencies:
- "@babel/parser" "^7.27.1"
- "@babel/types" "^7.27.1"
- "@jridgewell/gen-mapping" "^0.3.5"
- "@jridgewell/trace-mapping" "^0.3.25"
- jsesc "^3.0.2"
-
-"@babel/helper-compilation-targets@^7.27.1":
- version "7.27.2"
- resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d"
- integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==
- dependencies:
- "@babel/compat-data" "^7.27.2"
- "@babel/helper-validator-option" "^7.27.1"
- browserslist "^4.24.0"
- lru-cache "^5.1.1"
- semver "^6.3.1"
-
-"@babel/helper-module-imports@^7.27.1":
- version "7.27.1"
- resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204"
- integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==
- dependencies:
- "@babel/traverse" "^7.27.1"
- "@babel/types" "^7.27.1"
-
-"@babel/helper-module-transforms@^7.27.1":
- version "7.27.1"
- resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz#e1663b8b71d2de948da5c4fb2a20ca4f3ec27a6f"
- integrity sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==
- dependencies:
- "@babel/helper-module-imports" "^7.27.1"
- "@babel/helper-validator-identifier" "^7.27.1"
- "@babel/traverse" "^7.27.1"
-
-"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.27.1", "@babel/helper-plugin-utils@^7.8.0":
- version "7.27.1"
- resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c"
- integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==
-
-"@babel/helper-string-parser@^7.27.1":
- version "7.27.1"
- resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687"
- integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==
-
-"@babel/helper-validator-identifier@^7.27.1":
- version "7.27.1"
- resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8"
- integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==
-
-"@babel/helper-validator-option@^7.27.1":
- version "7.27.1"
- resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f"
- integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==
-
-"@babel/helpers@^7.27.1":
- version "7.27.1"
- resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.27.1.tgz#ffc27013038607cdba3288e692c3611c06a18aa4"
- integrity sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==
- dependencies:
- "@babel/template" "^7.27.1"
- "@babel/types" "^7.27.1"
-
-"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.27.1", "@babel/parser@^7.27.2":
- version "7.27.2"
- resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.27.2.tgz#577518bedb17a2ce4212afd052e01f7df0941127"
- integrity sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==
- dependencies:
- "@babel/types" "^7.27.1"
-
-"@babel/plugin-syntax-async-generators@^7.8.4":
- version "7.8.4"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d"
- integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==
- dependencies:
- "@babel/helper-plugin-utils" "^7.8.0"
-
-"@babel/plugin-syntax-bigint@^7.8.3":
- version "7.8.3"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea"
- integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==
- dependencies:
- "@babel/helper-plugin-utils" "^7.8.0"
-
-"@babel/plugin-syntax-class-properties@^7.12.13":
- version "7.12.13"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10"
- integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==
- dependencies:
- "@babel/helper-plugin-utils" "^7.12.13"
-
-"@babel/plugin-syntax-class-static-block@^7.14.5":
- version "7.14.5"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406"
- integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==
- dependencies:
- "@babel/helper-plugin-utils" "^7.14.5"
-
-"@babel/plugin-syntax-import-attributes@^7.24.7":
- version "7.27.1"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz#34c017d54496f9b11b61474e7ea3dfd5563ffe07"
- integrity sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==
- dependencies:
- "@babel/helper-plugin-utils" "^7.27.1"
-
-"@babel/plugin-syntax-import-meta@^7.10.4":
- version "7.10.4"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51"
- integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==
- dependencies:
- "@babel/helper-plugin-utils" "^7.10.4"
-
-"@babel/plugin-syntax-json-strings@^7.8.3":
- version "7.8.3"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a"
- integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==
- dependencies:
- "@babel/helper-plugin-utils" "^7.8.0"
-
-"@babel/plugin-syntax-jsx@^7.7.2":
- version "7.27.1"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz#2f9beb5eff30fa507c5532d107daac7b888fa34c"
- integrity sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==
- dependencies:
- "@babel/helper-plugin-utils" "^7.27.1"
-
-"@babel/plugin-syntax-logical-assignment-operators@^7.10.4":
- version "7.10.4"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699"
- integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==
- dependencies:
- "@babel/helper-plugin-utils" "^7.10.4"
-
-"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3":
- version "7.8.3"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9"
- integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==
- dependencies:
- "@babel/helper-plugin-utils" "^7.8.0"
-
-"@babel/plugin-syntax-numeric-separator@^7.10.4":
- version "7.10.4"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97"
- integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==
- dependencies:
- "@babel/helper-plugin-utils" "^7.10.4"
-
-"@babel/plugin-syntax-object-rest-spread@^7.8.3":
- version "7.8.3"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871"
- integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==
- dependencies:
- "@babel/helper-plugin-utils" "^7.8.0"
-
-"@babel/plugin-syntax-optional-catch-binding@^7.8.3":
- version "7.8.3"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1"
- integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==
- dependencies:
- "@babel/helper-plugin-utils" "^7.8.0"
-
-"@babel/plugin-syntax-optional-chaining@^7.8.3":
- version "7.8.3"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a"
- integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==
- dependencies:
- "@babel/helper-plugin-utils" "^7.8.0"
-
-"@babel/plugin-syntax-private-property-in-object@^7.14.5":
- version "7.14.5"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad"
- integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==
- dependencies:
- "@babel/helper-plugin-utils" "^7.14.5"
-
-"@babel/plugin-syntax-top-level-await@^7.14.5":
- version "7.14.5"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c"
- integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==
- dependencies:
- "@babel/helper-plugin-utils" "^7.14.5"
-
-"@babel/plugin-syntax-typescript@^7.7.2":
- version "7.27.1"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz#5147d29066a793450f220c63fa3a9431b7e6dd18"
- integrity sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==
- dependencies:
- "@babel/helper-plugin-utils" "^7.27.1"
-
-"@babel/runtime@^7.12.5":
- version "7.27.1"
- resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.1.tgz#9fce313d12c9a77507f264de74626e87fd0dc541"
- integrity sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==
-
-"@babel/template@^7.27.1", "@babel/template@^7.3.3":
- version "7.27.2"
- resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d"
- integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==
- dependencies:
- "@babel/code-frame" "^7.27.1"
- "@babel/parser" "^7.27.2"
- "@babel/types" "^7.27.1"
-
-"@babel/traverse@^7.27.1":
- version "7.27.1"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.27.1.tgz#4db772902b133bbddd1c4f7a7ee47761c1b9f291"
- integrity sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==
- dependencies:
- "@babel/code-frame" "^7.27.1"
- "@babel/generator" "^7.27.1"
- "@babel/parser" "^7.27.1"
- "@babel/template" "^7.27.1"
- "@babel/types" "^7.27.1"
- debug "^4.3.1"
- globals "^11.1.0"
-
-"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.27.1", "@babel/types@^7.3.3":
- version "7.27.1"
- resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.1.tgz#9defc53c16fc899e46941fc6901a9eea1c9d8560"
- integrity sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==
- dependencies:
- "@babel/helper-string-parser" "^7.27.1"
- "@babel/helper-validator-identifier" "^7.27.1"
-
-"@bcoe/v8-coverage@^0.2.3":
- version "0.2.3"
- resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
- integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
-
-"@emnapi/core@^1.4.0", "@emnapi/core@^1.4.3":
- version "1.4.3"
- resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.4.3.tgz#9ac52d2d5aea958f67e52c40a065f51de59b77d6"
- integrity sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==
- dependencies:
- "@emnapi/wasi-threads" "1.0.2"
- tslib "^2.4.0"
-
-"@emnapi/runtime@^1.4.0", "@emnapi/runtime@^1.4.3":
- version "1.4.3"
- resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.4.3.tgz#c0564665c80dc81c448adac23f9dfbed6c838f7d"
- integrity sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==
- dependencies:
- tslib "^2.4.0"
-
-"@emnapi/wasi-threads@1.0.2", "@emnapi/wasi-threads@^1.0.2":
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz#977f44f844eac7d6c138a415a123818c655f874c"
- integrity sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==
- dependencies:
- tslib "^2.4.0"
-
-"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.7.0":
- version "4.7.0"
- resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz#607084630c6c033992a082de6e6fbc1a8b52175a"
- integrity sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==
- dependencies:
- eslint-visitor-keys "^3.4.3"
-
-"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.1":
- version "4.12.1"
- resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0"
- integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==
-
-"@eslint/config-array@^0.20.0":
- version "0.20.0"
- resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.20.0.tgz#7a1232e82376712d3340012a2f561a2764d1988f"
- integrity sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==
- dependencies:
- "@eslint/object-schema" "^2.1.6"
- debug "^4.3.1"
- minimatch "^3.1.2"
-
-"@eslint/config-helpers@^0.2.1":
- version "0.2.2"
- resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.2.2.tgz#3779f76b894de3a8ec4763b79660e6d54d5b1010"
- integrity sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==
-
-"@eslint/core@^0.13.0":
- version "0.13.0"
- resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.13.0.tgz#bf02f209846d3bf996f9e8009db62df2739b458c"
- integrity sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==
- dependencies:
- "@types/json-schema" "^7.0.15"
-
-"@eslint/eslintrc@^3", "@eslint/eslintrc@^3.3.1":
- version "3.3.1"
- resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.1.tgz#e55f7f1dd400600dd066dbba349c4c0bac916964"
- integrity sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==
- dependencies:
- ajv "^6.12.4"
- debug "^4.3.2"
- espree "^10.0.1"
- globals "^14.0.0"
- ignore "^5.2.0"
- import-fresh "^3.2.1"
- js-yaml "^4.1.0"
- minimatch "^3.1.2"
- strip-json-comments "^3.1.1"
-
-"@eslint/js@9.26.0":
- version "9.26.0"
- resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.26.0.tgz#1e13126b67a3db15111d2dcc61f69a2acff70bd5"
- integrity sha512-I9XlJawFdSMvWjDt6wksMCrgns5ggLNfFwFvnShsleWruvXM514Qxk8V246efTw+eo9JABvVz+u3q2RiAowKxQ==
-
-"@eslint/object-schema@^2.1.6":
- version "2.1.6"
- resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.6.tgz#58369ab5b5b3ca117880c0f6c0b0f32f6950f24f"
- integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==
-
-"@eslint/plugin-kit@^0.2.8":
- version "0.2.8"
- resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz#47488d8f8171b5d4613e833313f3ce708e3525f8"
- integrity sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==
- dependencies:
- "@eslint/core" "^0.13.0"
- levn "^0.4.1"
-
-"@humanfs/core@^0.19.1":
- version "0.19.1"
- resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77"
- integrity sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==
-
-"@humanfs/node@^0.16.6":
- version "0.16.6"
- resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.6.tgz#ee2a10eaabd1131987bf0488fd9b820174cd765e"
- integrity sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==
- dependencies:
- "@humanfs/core" "^0.19.1"
- "@humanwhocodes/retry" "^0.3.0"
-
-"@humanwhocodes/module-importer@^1.0.1":
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c"
- integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==
-
-"@humanwhocodes/retry@^0.3.0":
- version "0.3.1"
- resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.1.tgz#c72a5c76a9fbaf3488e231b13dc52c0da7bab42a"
- integrity sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==
-
-"@humanwhocodes/retry@^0.4.2":
- version "0.4.3"
- resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba"
- integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==
-
-"@img/sharp-darwin-arm64@0.34.1":
- version "0.34.1"
- resolved "https://registry.yarnpkg.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.1.tgz#e79a4756bea9a06a7aadb4391ee53cb154a4968c"
- integrity sha512-pn44xgBtgpEbZsu+lWf2KNb6OAf70X68k+yk69Ic2Xz11zHR/w24/U49XT7AeRwJ0Px+mhALhU5LPci1Aymk7A==
- optionalDependencies:
- "@img/sharp-libvips-darwin-arm64" "1.1.0"
-
-"@img/sharp-darwin-x64@0.34.1":
- version "0.34.1"
- resolved "https://registry.yarnpkg.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.1.tgz#f1f1d386719f6933796415d84937502b7199a744"
- integrity sha512-VfuYgG2r8BpYiOUN+BfYeFo69nP/MIwAtSJ7/Zpxc5QF3KS22z8Pvg3FkrSFJBPNQ7mmcUcYQFBmEQp7eu1F8Q==
- optionalDependencies:
- "@img/sharp-libvips-darwin-x64" "1.1.0"
-
-"@img/sharp-libvips-darwin-arm64@1.1.0":
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.1.0.tgz#843f7c09c7245dc0d3cfec2b3c83bb08799a704f"
- integrity sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==
-
-"@img/sharp-libvips-darwin-x64@1.1.0":
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.1.0.tgz#1239c24426c06a8e833815562f78047a3bfbaaf8"
- integrity sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==
-
-"@img/sharp-libvips-linux-arm64@1.1.0":
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.1.0.tgz#20d276cefd903ee483f0441ba35961679c286315"
- integrity sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==
-
-"@img/sharp-libvips-linux-arm@1.1.0":
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.1.0.tgz#067c0b566eae8063738cf1b1db8f8a8573b5465c"
- integrity sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==
-
-"@img/sharp-libvips-linux-ppc64@1.1.0":
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.1.0.tgz#682334595f2ca00e0a07a675ba170af165162802"
- integrity sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==
-
-"@img/sharp-libvips-linux-s390x@1.1.0":
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.1.0.tgz#82fcd68444b3666384235279c145c2b28d8ee302"
- integrity sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==
-
-"@img/sharp-libvips-linux-x64@1.1.0":
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.1.0.tgz#65b2b908bf47156b0724fde9095676c83a18cf5a"
- integrity sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==
-
-"@img/sharp-libvips-linuxmusl-arm64@1.1.0":
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.1.0.tgz#72accf924e80b081c8db83b900b444a67c203f01"
- integrity sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==
-
-"@img/sharp-libvips-linuxmusl-x64@1.1.0":
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.1.0.tgz#1fa052737e203f46bf44192acd01f9faf11522d7"
- integrity sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==
-
-"@img/sharp-linux-arm64@0.34.1":
- version "0.34.1"
- resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.1.tgz#c36ef964499b8cfc2d2ed88fe68f27ce41522c80"
- integrity sha512-kX2c+vbvaXC6vly1RDf/IWNXxrlxLNpBVWkdpRq5Ka7OOKj6nr66etKy2IENf6FtOgklkg9ZdGpEu9kwdlcwOQ==
- optionalDependencies:
- "@img/sharp-libvips-linux-arm64" "1.1.0"
-
-"@img/sharp-linux-arm@0.34.1":
- version "0.34.1"
- resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.1.tgz#c96e38ff028d645912bb0aa132a7178b96997866"
- integrity sha512-anKiszvACti2sGy9CirTlNyk7BjjZPiML1jt2ZkTdcvpLU1YH6CXwRAZCA2UmRXnhiIftXQ7+Oh62Ji25W72jA==
- optionalDependencies:
- "@img/sharp-libvips-linux-arm" "1.1.0"
-
-"@img/sharp-linux-s390x@0.34.1":
- version "0.34.1"
- resolved "https://registry.yarnpkg.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.1.tgz#8ac58d9a49dcb08215e76c8d450717979b7815c3"
- integrity sha512-7s0KX2tI9mZI2buRipKIw2X1ufdTeaRgwmRabt5bi9chYfhur+/C1OXg3TKg/eag1W+6CCWLVmSauV1owmRPxA==
- optionalDependencies:
- "@img/sharp-libvips-linux-s390x" "1.1.0"
-
-"@img/sharp-linux-x64@0.34.1":
- version "0.34.1"
- resolved "https://registry.yarnpkg.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.1.tgz#3d8652efac635f0dba39d5e3b8b49515a2b2dee1"
- integrity sha512-wExv7SH9nmoBW3Wr2gvQopX1k8q2g5V5Iag8Zk6AVENsjwd+3adjwxtp3Dcu2QhOXr8W9NusBU6XcQUohBZ5MA==
- optionalDependencies:
- "@img/sharp-libvips-linux-x64" "1.1.0"
-
-"@img/sharp-linuxmusl-arm64@0.34.1":
- version "0.34.1"
- resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.1.tgz#b267e6a3e06f9e4d345cde471e5480c5c39e6969"
- integrity sha512-DfvyxzHxw4WGdPiTF0SOHnm11Xv4aQexvqhRDAoD00MzHekAj9a/jADXeXYCDFH/DzYruwHbXU7uz+H+nWmSOQ==
- optionalDependencies:
- "@img/sharp-libvips-linuxmusl-arm64" "1.1.0"
-
-"@img/sharp-linuxmusl-x64@0.34.1":
- version "0.34.1"
- resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.1.tgz#a8dee4b6227f348c4bbacaa6ac3dc584a1a80391"
- integrity sha512-pax/kTR407vNb9qaSIiWVnQplPcGU8LRIJpDT5o8PdAx5aAA7AS3X9PS8Isw1/WfqgQorPotjrZL3Pqh6C5EBg==
- optionalDependencies:
- "@img/sharp-libvips-linuxmusl-x64" "1.1.0"
-
-"@img/sharp-wasm32@0.34.1":
- version "0.34.1"
- resolved "https://registry.yarnpkg.com/@img/sharp-wasm32/-/sharp-wasm32-0.34.1.tgz#f7dfd66b6c231269042d3d8750c90f28b9ddcba1"
- integrity sha512-YDybQnYrLQfEpzGOQe7OKcyLUCML4YOXl428gOOzBgN6Gw0rv8dpsJ7PqTHxBnXnwXr8S1mYFSLSa727tpz0xg==
- dependencies:
- "@emnapi/runtime" "^1.4.0"
-
-"@img/sharp-win32-ia32@0.34.1":
- version "0.34.1"
- resolved "https://registry.yarnpkg.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.1.tgz#4bc293705df76a5f0a02df66ca3dc12e88f61332"
- integrity sha512-WKf/NAZITnonBf3U1LfdjoMgNO5JYRSlhovhRhMxXVdvWYveM4kM3L8m35onYIdh75cOMCo1BexgVQcCDzyoWw==
-
-"@img/sharp-win32-x64@0.34.1":
- version "0.34.1"
- resolved "https://registry.yarnpkg.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.1.tgz#8a7922fec949f037c204c79f6b83238d2482384b"
- integrity sha512-hw1iIAHpNE8q3uMIRCgGOeDoz9KtFNarFLQclLxr/LK1VBkj8nby18RjFvr6aP7USRYAjTZW6yisnBWMX571Tw==
-
-"@isaacs/fs-minipass@^4.0.0":
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz#2d59ae3ab4b38fb4270bfa23d30f8e2e86c7fe32"
- integrity sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==
- dependencies:
- minipass "^7.0.4"
-
-"@istanbuljs/load-nyc-config@^1.0.0":
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
- integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==
- dependencies:
- camelcase "^5.3.1"
- find-up "^4.1.0"
- get-package-type "^0.1.0"
- js-yaml "^3.13.1"
- resolve-from "^5.0.0"
-
-"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3":
- version "0.1.3"
- resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98"
- integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==
-
-"@jest/console@^29.7.0":
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc"
- integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==
- dependencies:
- "@jest/types" "^29.6.3"
- "@types/node" "*"
- chalk "^4.0.0"
- jest-message-util "^29.7.0"
- jest-util "^29.7.0"
- slash "^3.0.0"
-
-"@jest/core@^29.7.0":
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f"
- integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==
- dependencies:
- "@jest/console" "^29.7.0"
- "@jest/reporters" "^29.7.0"
- "@jest/test-result" "^29.7.0"
- "@jest/transform" "^29.7.0"
- "@jest/types" "^29.6.3"
- "@types/node" "*"
- ansi-escapes "^4.2.1"
- chalk "^4.0.0"
- ci-info "^3.2.0"
- exit "^0.1.2"
- graceful-fs "^4.2.9"
- jest-changed-files "^29.7.0"
- jest-config "^29.7.0"
- jest-haste-map "^29.7.0"
- jest-message-util "^29.7.0"
- jest-regex-util "^29.6.3"
- jest-resolve "^29.7.0"
- jest-resolve-dependencies "^29.7.0"
- jest-runner "^29.7.0"
- jest-runtime "^29.7.0"
- jest-snapshot "^29.7.0"
- jest-util "^29.7.0"
- jest-validate "^29.7.0"
- jest-watcher "^29.7.0"
- micromatch "^4.0.4"
- pretty-format "^29.7.0"
- slash "^3.0.0"
- strip-ansi "^6.0.0"
-
-"@jest/environment@^29.7.0":
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7"
- integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==
- dependencies:
- "@jest/fake-timers" "^29.7.0"
- "@jest/types" "^29.6.3"
- "@types/node" "*"
- jest-mock "^29.7.0"
-
-"@jest/expect-utils@^29.7.0":
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6"
- integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==
- dependencies:
- jest-get-type "^29.6.3"
-
-"@jest/expect@^29.7.0":
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2"
- integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==
- dependencies:
- expect "^29.7.0"
- jest-snapshot "^29.7.0"
-
-"@jest/fake-timers@^29.7.0":
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565"
- integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==
- dependencies:
- "@jest/types" "^29.6.3"
- "@sinonjs/fake-timers" "^10.0.2"
- "@types/node" "*"
- jest-message-util "^29.7.0"
- jest-mock "^29.7.0"
- jest-util "^29.7.0"
-
-"@jest/globals@^29.7.0":
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d"
- integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==
- dependencies:
- "@jest/environment" "^29.7.0"
- "@jest/expect" "^29.7.0"
- "@jest/types" "^29.6.3"
- jest-mock "^29.7.0"
-
-"@jest/reporters@^29.7.0":
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7"
- integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==
- dependencies:
- "@bcoe/v8-coverage" "^0.2.3"
- "@jest/console" "^29.7.0"
- "@jest/test-result" "^29.7.0"
- "@jest/transform" "^29.7.0"
- "@jest/types" "^29.6.3"
- "@jridgewell/trace-mapping" "^0.3.18"
- "@types/node" "*"
- chalk "^4.0.0"
- collect-v8-coverage "^1.0.0"
- exit "^0.1.2"
- glob "^7.1.3"
- graceful-fs "^4.2.9"
- istanbul-lib-coverage "^3.0.0"
- istanbul-lib-instrument "^6.0.0"
- istanbul-lib-report "^3.0.0"
- istanbul-lib-source-maps "^4.0.0"
- istanbul-reports "^3.1.3"
- jest-message-util "^29.7.0"
- jest-util "^29.7.0"
- jest-worker "^29.7.0"
- slash "^3.0.0"
- string-length "^4.0.1"
- strip-ansi "^6.0.0"
- v8-to-istanbul "^9.0.1"
-
-"@jest/schemas@^29.6.3":
- version "29.6.3"
- resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03"
- integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==
- dependencies:
- "@sinclair/typebox" "^0.27.8"
-
-"@jest/source-map@^29.6.3":
- version "29.6.3"
- resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4"
- integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==
- dependencies:
- "@jridgewell/trace-mapping" "^0.3.18"
- callsites "^3.0.0"
- graceful-fs "^4.2.9"
-
-"@jest/test-result@^29.7.0":
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c"
- integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==
- dependencies:
- "@jest/console" "^29.7.0"
- "@jest/types" "^29.6.3"
- "@types/istanbul-lib-coverage" "^2.0.0"
- collect-v8-coverage "^1.0.0"
-
-"@jest/test-sequencer@^29.7.0":
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce"
- integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==
- dependencies:
- "@jest/test-result" "^29.7.0"
- graceful-fs "^4.2.9"
- jest-haste-map "^29.7.0"
- slash "^3.0.0"
-
-"@jest/transform@^29.7.0":
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c"
- integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==
- dependencies:
- "@babel/core" "^7.11.6"
- "@jest/types" "^29.6.3"
- "@jridgewell/trace-mapping" "^0.3.18"
- babel-plugin-istanbul "^6.1.1"
- chalk "^4.0.0"
- convert-source-map "^2.0.0"
- fast-json-stable-stringify "^2.1.0"
- graceful-fs "^4.2.9"
- jest-haste-map "^29.7.0"
- jest-regex-util "^29.6.3"
- jest-util "^29.7.0"
- micromatch "^4.0.4"
- pirates "^4.0.4"
- slash "^3.0.0"
- write-file-atomic "^4.0.2"
-
-"@jest/types@^29.6.3":
- version "29.6.3"
- resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59"
- integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==
- dependencies:
- "@jest/schemas" "^29.6.3"
- "@types/istanbul-lib-coverage" "^2.0.0"
- "@types/istanbul-reports" "^3.0.0"
- "@types/node" "*"
- "@types/yargs" "^17.0.8"
- chalk "^4.0.0"
-
-"@jridgewell/gen-mapping@^0.3.5":
- version "0.3.8"
- resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142"
- integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==
- dependencies:
- "@jridgewell/set-array" "^1.2.1"
- "@jridgewell/sourcemap-codec" "^1.4.10"
- "@jridgewell/trace-mapping" "^0.3.24"
-
-"@jridgewell/resolve-uri@^3.1.0":
- version "3.1.2"
- resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6"
- integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==
-
-"@jridgewell/set-array@^1.2.1":
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280"
- integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==
-
-"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0":
- version "1.5.0"
- resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a"
- integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==
-
-"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25":
- version "0.3.25"
- resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0"
- integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==
- dependencies:
- "@jridgewell/resolve-uri" "^3.1.0"
- "@jridgewell/sourcemap-codec" "^1.4.14"
-
-"@modelcontextprotocol/sdk@^1.8.0":
- version "1.11.2"
- resolved "https://registry.yarnpkg.com/@modelcontextprotocol/sdk/-/sdk-1.11.2.tgz#d81784c140d1a9cc937f61af9f071d8b78befe30"
- integrity sha512-H9vwztj5OAqHg9GockCQC06k1natgcxWQSRpQcPJf6i5+MWBzfKkRtxGbjQf0X2ihii0ffLZCRGbYV2f2bjNCQ==
- dependencies:
- content-type "^1.0.5"
- cors "^2.8.5"
- cross-spawn "^7.0.3"
- eventsource "^3.0.2"
- express "^5.0.1"
- express-rate-limit "^7.5.0"
- pkce-challenge "^5.0.0"
- raw-body "^3.0.0"
- zod "^3.23.8"
- zod-to-json-schema "^3.24.1"
-
-"@napi-rs/wasm-runtime@^0.2.9":
- version "0.2.9"
- resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.9.tgz#7278122cf94f3b36d8170a8eee7d85356dfa6a96"
- integrity sha512-OKRBiajrrxB9ATokgEQoG87Z25c67pCpYcCwmXYX8PBftC9pBfN18gnm/fh1wurSLEKIAt+QRFLFCQISrb66Jg==
- dependencies:
- "@emnapi/core" "^1.4.0"
- "@emnapi/runtime" "^1.4.0"
- "@tybys/wasm-util" "^0.9.0"
-
-"@next/env@15.3.2":
- version "15.3.2"
- resolved "https://registry.yarnpkg.com/@next/env/-/env-15.3.2.tgz#7143eafa9b11cfdf3d3c7318b0facb9dfdb2948f"
- integrity sha512-xURk++7P7qR9JG1jJtLzPzf0qEvqCN0A/T3DXf8IPMKo9/6FfjxtEffRJIIew/bIL4T3C2jLLqBor8B/zVlx6g==
-
-"@next/eslint-plugin-next@15.3.2":
- version "15.3.2"
- resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-15.3.2.tgz#6a371b022d6de47f2bafc868c7c2220f4e6a2903"
- integrity sha512-ijVRTXBgnHT33aWnDtmlG+LJD+5vhc9AKTJPquGG5NKXjpKNjc62woIhFtrAcWdBobt8kqjCoaJ0q6sDQoX7aQ==
- dependencies:
- fast-glob "3.3.1"
-
-"@next/swc-darwin-arm64@15.3.2":
- version "15.3.2"
- resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.3.2.tgz#1a7b36bf3c439f899065c878a580bc57a3630ec7"
- integrity sha512-2DR6kY/OGcokbnCsjHpNeQblqCZ85/1j6njYSkzRdpLn5At7OkSdmk7WyAmB9G0k25+VgqVZ/u356OSoQZ3z0g==
-
-"@next/swc-darwin-x64@15.3.2":
- version "15.3.2"
- resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-15.3.2.tgz#3742026344f49128cf1b0f43814c67e880db7361"
- integrity sha512-ro/fdqaZWL6k1S/5CLv1I0DaZfDVJkWNaUU3un8Lg6m0YENWlDulmIWzV96Iou2wEYyEsZq51mwV8+XQXqMp3w==
-
-"@next/swc-linux-arm64-gnu@15.3.2":
- version "15.3.2"
- resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.3.2.tgz#fb29d45c034e3d2eef89b0e2801d62eb86155823"
- integrity sha512-covwwtZYhlbRWK2HlYX9835qXum4xYZ3E2Mra1mdQ+0ICGoMiw1+nVAn4d9Bo7R3JqSmK1grMq/va+0cdh7bJA==
-
-"@next/swc-linux-arm64-musl@15.3.2":
- version "15.3.2"
- resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.3.2.tgz#396784ef312666600ab1ae481e34cb1f6e3ae730"
- integrity sha512-KQkMEillvlW5Qk5mtGA/3Yz0/tzpNlSw6/3/ttsV1lNtMuOHcGii3zVeXZyi4EJmmLDKYcTcByV2wVsOhDt/zg==
-
-"@next/swc-linux-x64-gnu@15.3.2":
- version "15.3.2"
- resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.2.tgz#ac01fda376878e02bc6b57d1e88ab8ceae9f868e"
- integrity sha512-uRBo6THWei0chz+Y5j37qzx+BtoDRFIkDzZjlpCItBRXyMPIg079eIkOCl3aqr2tkxL4HFyJ4GHDes7W8HuAUg==
-
-"@next/swc-linux-x64-musl@15.3.2":
- version "15.3.2"
- resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.2.tgz#327a5023003bcb3ca436efc08733f091bba2b1e8"
- integrity sha512-+uxFlPuCNx/T9PdMClOqeE8USKzj8tVz37KflT3Kdbx/LOlZBRI2yxuIcmx1mPNK8DwSOMNCr4ureSet7eyC0w==
-
-"@next/swc-win32-arm64-msvc@15.3.2":
- version "15.3.2"
- resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.3.2.tgz#ce3a6588bd9c020960704011ab20bd0440026965"
- integrity sha512-LLTKmaI5cfD8dVzh5Vt7+OMo+AIOClEdIU/TSKbXXT2iScUTSxOGoBhfuv+FU8R9MLmrkIL1e2fBMkEEjYAtPQ==
-
-"@next/swc-win32-x64-msvc@15.3.2":
- version "15.3.2"
- resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.3.2.tgz#43cc36097ac27639e9024a5ceaa6e7727fa968c8"
- integrity sha512-aW5B8wOPioJ4mBdMDXkt5f3j8pUr9W8AnlX0Df35uRWNT1Y6RIybxjnSUe+PhM+M1bwgyY8PHLmXZC6zT1o5tA==
-
-"@nodelib/fs.scandir@2.1.5":
- version "2.1.5"
- resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
- integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==
- dependencies:
- "@nodelib/fs.stat" "2.0.5"
- run-parallel "^1.1.9"
-
-"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
- version "2.0.5"
- resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
- integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
-
-"@nodelib/fs.walk@^1.2.3":
- version "1.2.8"
- resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
- integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
- dependencies:
- "@nodelib/fs.scandir" "2.1.5"
- fastq "^1.6.0"
-
-"@nolyfill/is-core-module@1.0.39":
- version "1.0.39"
- resolved "https://registry.yarnpkg.com/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz#3dc35ba0f1e66b403c00b39344f870298ebb1c8e"
- integrity sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==
-
-"@rtsao/scc@^1.1.0":
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8"
- integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==
-
-"@rushstack/eslint-patch@^1.10.3":
- version "1.11.0"
- resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.11.0.tgz#75dce8e972f90bba488e2b0cc677fb233aa357ab"
- integrity sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==
-
-"@sinclair/typebox@^0.27.8":
- version "0.27.8"
- resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e"
- integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==
-
-"@sinonjs/commons@^3.0.0":
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd"
- integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==
- dependencies:
- type-detect "4.0.8"
-
-"@sinonjs/fake-timers@^10.0.2":
- version "10.3.0"
- resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66"
- integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==
- dependencies:
- "@sinonjs/commons" "^3.0.0"
-
-"@swc/counter@0.1.3":
- version "0.1.3"
- resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.3.tgz#cc7463bd02949611c6329596fccd2b0ec782b0e9"
- integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==
-
-"@swc/helpers@0.5.15":
- version "0.5.15"
- resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.15.tgz#79efab344c5819ecf83a43f3f9f811fc84b516d7"
- integrity sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==
- dependencies:
- tslib "^2.8.0"
-
-"@tailwindcss/node@4.1.6":
- version "4.1.6"
- resolved "https://registry.yarnpkg.com/@tailwindcss/node/-/node-4.1.6.tgz#693304dce91d88a1ea8e0d87cf5b64b5feb0fc6a"
- integrity sha512-ed6zQbgmKsjsVvodAS1q1Ld2BolEuxJOSyyNc+vhkjdmfNUDCmQnlXBfQkHrlzNmslxHsQU/bFmzcEbv4xXsLg==
- dependencies:
- "@ampproject/remapping" "^2.3.0"
- enhanced-resolve "^5.18.1"
- jiti "^2.4.2"
- lightningcss "1.29.2"
- magic-string "^0.30.17"
- source-map-js "^1.2.1"
- tailwindcss "4.1.6"
-
-"@tailwindcss/oxide-android-arm64@4.1.6":
- version "4.1.6"
- resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.6.tgz#b7632044a47250112f9ea9da4a4fdb5f7550b9f8"
- integrity sha512-VHwwPiwXtdIvOvqT/0/FLH/pizTVu78FOnI9jQo64kSAikFSZT7K4pjyzoDpSMaveJTGyAKvDjuhxJxKfmvjiQ==
-
-"@tailwindcss/oxide-darwin-arm64@4.1.6":
- version "4.1.6"
- resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.6.tgz#8d94e40fee9fb3214b1cf4f4d9341738a812871a"
- integrity sha512-weINOCcqv1HVBIGptNrk7c6lWgSFFiQMcCpKM4tnVi5x8OY2v1FrV76jwLukfT6pL1hyajc06tyVmZFYXoxvhQ==
-
-"@tailwindcss/oxide-darwin-x64@4.1.6":
- version "4.1.6"
- resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.6.tgz#281ab262cfde170dd4e977126e259b58eaab3bd3"
- integrity sha512-3FzekhHG0ww1zQjQ1lPoq0wPrAIVXAbUkWdWM8u5BnYFZgb9ja5ejBqyTgjpo5mfy0hFOoMnMuVDI+7CXhXZaQ==
-
-"@tailwindcss/oxide-freebsd-x64@4.1.6":
- version "4.1.6"
- resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.6.tgz#4d5b7e13ff8ab47aabf7d4613faf051cfd540398"
- integrity sha512-4m5F5lpkBZhVQJq53oe5XgJ+aFYWdrgkMwViHjRsES3KEu2m1udR21B1I77RUqie0ZYNscFzY1v9aDssMBZ/1w==
-
-"@tailwindcss/oxide-linux-arm-gnueabihf@4.1.6":
- version "4.1.6"
- resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.6.tgz#88dc4f20e6e75ded01aee85b398494adcaef85e8"
- integrity sha512-qU0rHnA9P/ZoaDKouU1oGPxPWzDKtIfX7eOGi5jOWJKdxieUJdVV+CxWZOpDWlYTd4N3sFQvcnVLJWJ1cLP5TA==
-
-"@tailwindcss/oxide-linux-arm64-gnu@4.1.6":
- version "4.1.6"
- resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.6.tgz#6b848009eec017a4feb1d7f763d37540b20eef16"
- integrity sha512-jXy3TSTrbfgyd3UxPQeXC3wm8DAgmigzar99Km9Sf6L2OFfn/k+u3VqmpgHQw5QNfCpPe43em6Q7V76Wx7ogIQ==
-
-"@tailwindcss/oxide-linux-arm64-musl@4.1.6":
- version "4.1.6"
- resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.6.tgz#5b5a27013fd801d471998fc371812fdf1156be24"
- integrity sha512-8kjivE5xW0qAQ9HX9reVFmZj3t+VmljDLVRJpVBEoTR+3bKMnvC7iLcoSGNIUJGOZy1mLVq7x/gerVg0T+IsYw==
-
-"@tailwindcss/oxide-linux-x64-gnu@4.1.6":
- version "4.1.6"
- resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.6.tgz#81e06ade4eef09141504bb35b8e4aa18349b7ced"
- integrity sha512-A4spQhwnWVpjWDLXnOW9PSinO2PTKJQNRmL/aIl2U/O+RARls8doDfs6R41+DAXK0ccacvRyDpR46aVQJJCoCg==
-
-"@tailwindcss/oxide-linux-x64-musl@4.1.6":
- version "4.1.6"
- resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.6.tgz#a01bb576581269e8c996b19c594d0b0d6673fdc3"
- integrity sha512-YRee+6ZqdzgiQAHVSLfl3RYmqeeaWVCk796MhXhLQu2kJu2COHBkqlqsqKYx3p8Hmk5pGCQd2jTAoMWWFeyG2A==
-
-"@tailwindcss/oxide-wasm32-wasi@4.1.6":
- version "4.1.6"
- resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.6.tgz#7e45eb7aafec0406477a05403689198a9f062b4d"
- integrity sha512-qAp4ooTYrBQ5pk5jgg54/U1rCJ/9FLYOkkQ/nTE+bVMseMfB6O7J8zb19YTpWuu4UdfRf5zzOrNKfl6T64MNrQ==
- dependencies:
- "@emnapi/core" "^1.4.3"
- "@emnapi/runtime" "^1.4.3"
- "@emnapi/wasi-threads" "^1.0.2"
- "@napi-rs/wasm-runtime" "^0.2.9"
- "@tybys/wasm-util" "^0.9.0"
- tslib "^2.8.0"
-
-"@tailwindcss/oxide-win32-arm64-msvc@4.1.6":
- version "4.1.6"
- resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.6.tgz#9b445635928a43b92ffb7b52bb063a549d7df980"
- integrity sha512-nqpDWk0Xr8ELO/nfRUDjk1pc9wDJ3ObeDdNMHLaymc4PJBWj11gdPCWZFKSK2AVKjJQC7J2EfmSmf47GN7OuLg==
-
-"@tailwindcss/oxide-win32-x64-msvc@4.1.6":
- version "4.1.6"
- resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.6.tgz#2d0405b733a5fcbe44554601a71f907142738ced"
- integrity sha512-5k9xF33xkfKpo9wCvYcegQ21VwIBU1/qEbYlVukfEIyQbEA47uK8AAwS7NVjNE3vHzcmxMYwd0l6L4pPjjm1rQ==
-
-"@tailwindcss/oxide@4.1.6":
- version "4.1.6"
- resolved "https://registry.yarnpkg.com/@tailwindcss/oxide/-/oxide-4.1.6.tgz#1ddabeb360385f04742c887e081352ab7469a668"
- integrity sha512-0bpEBQiGx+227fW4G0fLQ8vuvyy5rsB1YIYNapTq3aRsJ9taF3f5cCaovDjN5pUGKKzcpMrZst/mhNaKAPOHOA==
- dependencies:
- detect-libc "^2.0.4"
- tar "^7.4.3"
- optionalDependencies:
- "@tailwindcss/oxide-android-arm64" "4.1.6"
- "@tailwindcss/oxide-darwin-arm64" "4.1.6"
- "@tailwindcss/oxide-darwin-x64" "4.1.6"
- "@tailwindcss/oxide-freebsd-x64" "4.1.6"
- "@tailwindcss/oxide-linux-arm-gnueabihf" "4.1.6"
- "@tailwindcss/oxide-linux-arm64-gnu" "4.1.6"
- "@tailwindcss/oxide-linux-arm64-musl" "4.1.6"
- "@tailwindcss/oxide-linux-x64-gnu" "4.1.6"
- "@tailwindcss/oxide-linux-x64-musl" "4.1.6"
- "@tailwindcss/oxide-wasm32-wasi" "4.1.6"
- "@tailwindcss/oxide-win32-arm64-msvc" "4.1.6"
- "@tailwindcss/oxide-win32-x64-msvc" "4.1.6"
-
-"@tailwindcss/postcss@^4.1.6":
- version "4.1.6"
- resolved "https://registry.yarnpkg.com/@tailwindcss/postcss/-/postcss-4.1.6.tgz#c37ae6fc324ae4ec291976f477fac092357058b4"
- integrity sha512-ELq+gDMBuRXPJlpE3PEen+1MhnHAQQrh2zF0dI1NXOlEWfr2qWf2CQdr5jl9yANv8RErQaQ2l6nIFO9OSCVq/g==
- dependencies:
- "@alloc/quick-lru" "^5.2.0"
- "@tailwindcss/node" "4.1.6"
- "@tailwindcss/oxide" "4.1.6"
- postcss "^8.4.41"
- tailwindcss "4.1.6"
-
-"@tanstack/query-core@5.76.0":
- version "5.76.0"
- resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.76.0.tgz#3b4d5d34ce307ba0cf7d1a3e90d7adcdc6c46be0"
- integrity sha512-FN375hb8ctzfNAlex5gHI6+WDXTNpe0nbxp/d2YJtnP+IBM6OUm7zcaoCW6T63BawGOYZBbKC0iPvr41TteNVg==
-
-"@tanstack/react-query@^5.76.1":
- version "5.76.1"
- resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.76.1.tgz#ac8a19f99dfec1452a44fe22d46680c396c21152"
- integrity sha512-YxdLZVGN4QkT5YT1HKZQWiIlcgauIXEIsMOTSjvyD5wLYK8YVvKZUPAysMqossFJJfDpJW3pFn7WNZuPOqq+fw==
- dependencies:
- "@tanstack/query-core" "5.76.0"
-
-"@testing-library/dom@^10.4.0":
- version "10.4.0"
- resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-10.4.0.tgz#82a9d9462f11d240ecadbf406607c6ceeeff43a8"
- integrity sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==
- dependencies:
- "@babel/code-frame" "^7.10.4"
- "@babel/runtime" "^7.12.5"
- "@types/aria-query" "^5.0.1"
- aria-query "5.3.0"
- chalk "^4.1.0"
- dom-accessibility-api "^0.5.9"
- lz-string "^1.5.0"
- pretty-format "^27.0.2"
-
-"@testing-library/react@^16.3.0":
- version "16.3.0"
- resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-16.3.0.tgz#3a85bb9bdebf180cd76dba16454e242564d598a6"
- integrity sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==
- dependencies:
- "@babel/runtime" "^7.12.5"
-
-"@tybys/wasm-util@^0.9.0":
- version "0.9.0"
- resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.9.0.tgz#3e75eb00604c8d6db470bf18c37b7d984a0e3355"
- integrity sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==
- dependencies:
- tslib "^2.4.0"
-
-"@types/aria-query@^5.0.1":
- version "5.0.4"
- resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.4.tgz#1a31c3d378850d2778dabb6374d036dcba4ba708"
- integrity sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==
-
-"@types/babel__core@^7.1.14":
- version "7.20.5"
- resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017"
- integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==
- dependencies:
- "@babel/parser" "^7.20.7"
- "@babel/types" "^7.20.7"
- "@types/babel__generator" "*"
- "@types/babel__template" "*"
- "@types/babel__traverse" "*"
-
-"@types/babel__generator@*":
- version "7.27.0"
- resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.27.0.tgz#b5819294c51179957afaec341442f9341e4108a9"
- integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==
- dependencies:
- "@babel/types" "^7.0.0"
-
-"@types/babel__template@*":
- version "7.4.4"
- resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f"
- integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==
- dependencies:
- "@babel/parser" "^7.1.0"
- "@babel/types" "^7.0.0"
-
-"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6":
- version "7.20.7"
- resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.7.tgz#968cdc2366ec3da159f61166428ee40f370e56c2"
- integrity sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==
- dependencies:
- "@babel/types" "^7.20.7"
-
-"@types/estree@^1.0.6":
- version "1.0.7"
- resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.7.tgz#4158d3105276773d5b7695cd4834b1722e4f37a8"
- integrity sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==
-
-"@types/graceful-fs@^4.1.3":
- version "4.1.9"
- resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4"
- integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==
- dependencies:
- "@types/node" "*"
-
-"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1":
- version "2.0.6"
- resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7"
- integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==
-
-"@types/istanbul-lib-report@*":
- version "3.0.3"
- resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf"
- integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==
- dependencies:
- "@types/istanbul-lib-coverage" "*"
-
-"@types/istanbul-reports@^3.0.0":
- version "3.0.4"
- resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54"
- integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==
- dependencies:
- "@types/istanbul-lib-report" "*"
-
-"@types/json-schema@^7.0.15":
- version "7.0.15"
- resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
- integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
-
-"@types/json5@^0.0.29":
- version "0.0.29"
- resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
- integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
-
-"@types/node@*":
- version "22.15.18"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.18.tgz#2f8240f7e932f571c2d45f555ba0b6c3f7a75963"
- integrity sha512-v1DKRfUdyW+jJhZNEI1PYy29S2YRxMV5AOO/x/SjKmW0acCIOqmbj6Haf9eHAhsPmrhlHSxEhv/1WszcLWV4cg==
- dependencies:
- undici-types "~6.21.0"
-
-"@types/node@^20":
- version "20.17.47"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-20.17.47.tgz#f9cb375993fffdae609c8e17d2b3dd8d3c4bfa14"
- integrity sha512-3dLX0Upo1v7RvUimvxLeXqwrfyKxUINk0EAM83swP2mlSUcwV73sZy8XhNz8bcZ3VbsfQyC/y6jRdL5tgCNpDQ==
- dependencies:
- undici-types "~6.19.2"
-
-"@types/react-dom@^19":
- version "19.1.5"
- resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.1.5.tgz#cdfe2c663742887372f54804b16e8dbc26bd794a"
- integrity sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==
-
-"@types/react@^19":
- version "19.1.4"
- resolved "https://registry.yarnpkg.com/@types/react/-/react-19.1.4.tgz#4d125f014d6ac26b4759775698db118701e314fe"
- integrity sha512-EB1yiiYdvySuIITtD5lhW4yPyJ31RkJkkDw794LaQYrxCSaQV/47y5o1FMC4zF9ZyjUjzJMZwbovEnT5yHTW6g==
- dependencies:
- csstype "^3.0.2"
-
-"@types/stack-utils@^2.0.0":
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8"
- integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==
-
-"@types/yargs-parser@*":
- version "21.0.3"
- resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15"
- integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==
-
-"@types/yargs@^17.0.8":
- version "17.0.33"
- resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d"
- integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==
- dependencies:
- "@types/yargs-parser" "*"
-
-"@typescript-eslint/eslint-plugin@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0":
- version "8.32.1"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.1.tgz#9185b3eaa3b083d8318910e12d56c68b3c4f45b4"
- integrity sha512-6u6Plg9nP/J1GRpe/vcjjabo6Uc5YQPAMxsgQyGC/I0RuukiG1wIe3+Vtg3IrSCVJDmqK3j8adrtzXSENRtFgg==
- dependencies:
- "@eslint-community/regexpp" "^4.10.0"
- "@typescript-eslint/scope-manager" "8.32.1"
- "@typescript-eslint/type-utils" "8.32.1"
- "@typescript-eslint/utils" "8.32.1"
- "@typescript-eslint/visitor-keys" "8.32.1"
- graphemer "^1.4.0"
- ignore "^7.0.0"
- natural-compare "^1.4.0"
- ts-api-utils "^2.1.0"
-
-"@typescript-eslint/parser@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0":
- version "8.32.1"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.32.1.tgz#18b0e53315e0bc22b2619d398ae49a968370935e"
- integrity sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg==
- dependencies:
- "@typescript-eslint/scope-manager" "8.32.1"
- "@typescript-eslint/types" "8.32.1"
- "@typescript-eslint/typescript-estree" "8.32.1"
- "@typescript-eslint/visitor-keys" "8.32.1"
- debug "^4.3.4"
-
-"@typescript-eslint/scope-manager@8.32.1":
- version "8.32.1"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.32.1.tgz#9a6bf5fb2c5380e14fe9d38ccac6e4bbe17e8afc"
- integrity sha512-7IsIaIDeZn7kffk7qXC3o6Z4UblZJKV3UBpkvRNpr5NSyLji7tvTcvmnMNYuYLyh26mN8W723xpo3i4MlD33vA==
- dependencies:
- "@typescript-eslint/types" "8.32.1"
- "@typescript-eslint/visitor-keys" "8.32.1"
-
-"@typescript-eslint/type-utils@8.32.1":
- version "8.32.1"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.32.1.tgz#b9292a45f69ecdb7db74d1696e57d1a89514d21e"
- integrity sha512-mv9YpQGA8iIsl5KyUPi+FGLm7+bA4fgXaeRcFKRDRwDMu4iwrSHeDPipwueNXhdIIZltwCJv+NkxftECbIZWfA==
- dependencies:
- "@typescript-eslint/typescript-estree" "8.32.1"
- "@typescript-eslint/utils" "8.32.1"
- debug "^4.3.4"
- ts-api-utils "^2.1.0"
-
-"@typescript-eslint/types@8.32.1":
- version "8.32.1"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.32.1.tgz#b19fe4ac0dc08317bae0ce9ec1168123576c1d4b"
- integrity sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg==
-
-"@typescript-eslint/typescript-estree@8.32.1":
- version "8.32.1"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.32.1.tgz#9023720ca4ecf4f59c275a05b5fed69b1276face"
- integrity sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg==
- dependencies:
- "@typescript-eslint/types" "8.32.1"
- "@typescript-eslint/visitor-keys" "8.32.1"
- debug "^4.3.4"
- fast-glob "^3.3.2"
- is-glob "^4.0.3"
- minimatch "^9.0.4"
- semver "^7.6.0"
- ts-api-utils "^2.1.0"
-
-"@typescript-eslint/utils@8.32.1":
- version "8.32.1"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.32.1.tgz#4d6d5d29b9e519e9a85e9a74e9f7bdb58abe9704"
- integrity sha512-DsSFNIgLSrc89gpq1LJB7Hm1YpuhK086DRDJSNrewcGvYloWW1vZLHBTIvarKZDcAORIy/uWNx8Gad+4oMpkSA==
- dependencies:
- "@eslint-community/eslint-utils" "^4.7.0"
- "@typescript-eslint/scope-manager" "8.32.1"
- "@typescript-eslint/types" "8.32.1"
- "@typescript-eslint/typescript-estree" "8.32.1"
-
-"@typescript-eslint/visitor-keys@8.32.1":
- version "8.32.1"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.32.1.tgz#4321395cc55c2eb46036cbbb03e101994d11ddca"
- integrity sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w==
- dependencies:
- "@typescript-eslint/types" "8.32.1"
- eslint-visitor-keys "^4.2.0"
-
-"@unrs/resolver-binding-darwin-arm64@1.7.2":
- version "1.7.2"
- resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.7.2.tgz#12eed2bd9865d1f55bb79d76072330b6032441d7"
- integrity sha512-vxtBno4xvowwNmO/ASL0Y45TpHqmNkAaDtz4Jqb+clmcVSSl8XCG/PNFFkGsXXXS6AMjP+ja/TtNCFFa1QwLRg==
-
-"@unrs/resolver-binding-darwin-x64@1.7.2":
- version "1.7.2"
- resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.7.2.tgz#97e0212a85c56e156a272628ec55da7aff992161"
- integrity sha512-qhVa8ozu92C23Hsmv0BF4+5Dyyd5STT1FolV4whNgbY6mj3kA0qsrGPe35zNR3wAN7eFict3s4Rc2dDTPBTuFQ==
-
-"@unrs/resolver-binding-freebsd-x64@1.7.2":
- version "1.7.2"
- resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.7.2.tgz#07594a9d1d83e84b52908800459273ea00caf595"
- integrity sha512-zKKdm2uMXqLFX6Ac7K5ElnnG5VIXbDlFWzg4WJ8CGUedJryM5A3cTgHuGMw1+P5ziV8CRhnSEgOnurTI4vpHpg==
-
-"@unrs/resolver-binding-linux-arm-gnueabihf@1.7.2":
- version "1.7.2"
- resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.7.2.tgz#9ef6031bb1136ee7862a6f94a2a53c395d2b6fae"
- integrity sha512-8N1z1TbPnHH+iDS/42GJ0bMPLiGK+cUqOhNbMKtWJ4oFGzqSJk/zoXFzcQkgtI63qMcUI7wW1tq2usZQSb2jxw==
-
-"@unrs/resolver-binding-linux-arm-musleabihf@1.7.2":
- version "1.7.2"
- resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.7.2.tgz#24910379ab39da1b15d65b1a06b4bfb4c293ca0c"
- integrity sha512-tjYzI9LcAXR9MYd9rO45m1s0B/6bJNuZ6jeOxo1pq1K6OBuRMMmfyvJYval3s9FPPGmrldYA3mi4gWDlWuTFGA==
-
-"@unrs/resolver-binding-linux-arm64-gnu@1.7.2":
- version "1.7.2"
- resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.7.2.tgz#49b6a8fb8f42f7530f51bc2e60fc582daed31ffb"
- integrity sha512-jon9M7DKRLGZ9VYSkFMflvNqu9hDtOCEnO2QAryFWgT6o6AXU8du56V7YqnaLKr6rAbZBWYsYpikF226v423QA==
-
-"@unrs/resolver-binding-linux-arm64-musl@1.7.2":
- version "1.7.2"
- resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.7.2.tgz#3a9707a6afda534f30c8de8a5de6c193b1b6d164"
- integrity sha512-c8Cg4/h+kQ63pL43wBNaVMmOjXI/X62wQmru51qjfTvI7kmCy5uHTJvK/9LrF0G8Jdx8r34d019P1DVJmhXQpA==
-
-"@unrs/resolver-binding-linux-ppc64-gnu@1.7.2":
- version "1.7.2"
- resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.7.2.tgz#659831ff2bfe8157d806b69b6efe142265bf9f0f"
- integrity sha512-A+lcwRFyrjeJmv3JJvhz5NbcCkLQL6Mk16kHTNm6/aGNc4FwPHPE4DR9DwuCvCnVHvF5IAd9U4VIs/VvVir5lg==
-
-"@unrs/resolver-binding-linux-riscv64-gnu@1.7.2":
- version "1.7.2"
- resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.7.2.tgz#e75abebd53cdddb3d635f6efb7a5ef6e96695717"
- integrity sha512-hQQ4TJQrSQW8JlPm7tRpXN8OCNP9ez7PajJNjRD1ZTHQAy685OYqPrKjfaMw/8LiHCt8AZ74rfUVHP9vn0N69Q==
-
-"@unrs/resolver-binding-linux-riscv64-musl@1.7.2":
- version "1.7.2"
- resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.7.2.tgz#e99b5316ee612b180aff5a7211717f3fc8c3e54e"
- integrity sha512-NoAGbiqrxtY8kVooZ24i70CjLDlUFI7nDj3I9y54U94p+3kPxwd2L692YsdLa+cqQ0VoqMWoehDFp21PKRUoIQ==
-
-"@unrs/resolver-binding-linux-s390x-gnu@1.7.2":
- version "1.7.2"
- resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.7.2.tgz#36646d5f60246f0eae650fc7bcd79b3cbf7dcff1"
- integrity sha512-KaZByo8xuQZbUhhreBTW+yUnOIHUsv04P8lKjQ5otiGoSJ17ISGYArc+4vKdLEpGaLbemGzr4ZeUbYQQsLWFjA==
-
-"@unrs/resolver-binding-linux-x64-gnu@1.7.2":
- version "1.7.2"
- resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.7.2.tgz#e720adc2979702c62f4040de05c854f186268c27"
- integrity sha512-dEidzJDubxxhUCBJ/SHSMJD/9q7JkyfBMT77Px1npl4xpg9t0POLvnWywSk66BgZS/b2Hy9Y1yFaoMTFJUe9yg==
-
-"@unrs/resolver-binding-linux-x64-musl@1.7.2":
- version "1.7.2"
- resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.7.2.tgz#684e576557d20deb4ac8ea056dcbe79739ca2870"
- integrity sha512-RvP+Ux3wDjmnZDT4XWFfNBRVG0fMsc+yVzNFUqOflnDfZ9OYujv6nkh+GOr+watwrW4wdp6ASfG/e7bkDradsw==
-
-"@unrs/resolver-binding-wasm32-wasi@1.7.2":
- version "1.7.2"
- resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.7.2.tgz#5b138ce8d471f5d0c8d6bfab525c53b80ca734e0"
- integrity sha512-y797JBmO9IsvXVRCKDXOxjyAE4+CcZpla2GSoBQ33TVb3ILXuFnMrbR/QQZoauBYeOFuu4w3ifWLw52sdHGz6g==
- dependencies:
- "@napi-rs/wasm-runtime" "^0.2.9"
-
-"@unrs/resolver-binding-win32-arm64-msvc@1.7.2":
- version "1.7.2"
- resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.7.2.tgz#bd772db4e8a02c31161cf1dfa33852eb7ef22df6"
- integrity sha512-gtYTh4/VREVSLA+gHrfbWxaMO/00y+34htY7XpioBTy56YN2eBjkPrY1ML1Zys89X3RJDKVaogzwxlM1qU7egg==
-
-"@unrs/resolver-binding-win32-ia32-msvc@1.7.2":
- version "1.7.2"
- resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.7.2.tgz#a6955ccdc43e809a158c4fe2d54931d34c3f7b51"
- integrity sha512-Ywv20XHvHTDRQs12jd3MY8X5C8KLjDbg/jyaal/QLKx3fAShhJyD4blEANInsjxW3P7isHx1Blt56iUDDJO3jg==
-
-"@unrs/resolver-binding-win32-x64-msvc@1.7.2":
- version "1.7.2"
- resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.7.2.tgz#7fd81d89e34a711d398ca87f6d5842735d49721e"
- integrity sha512-friS8NEQfHaDbkThxopGk+LuE5v3iY0StruifjQEt7SLbA46OnfgMO15sOTkbpJkol6RB+1l1TYPXh0sCddpvA==
-
-accepts@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895"
- integrity sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==
- dependencies:
- mime-types "^3.0.0"
- negotiator "^1.0.0"
-
-acorn-jsx@^5.3.2:
- version "5.3.2"
- resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
- integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
-
-acorn@^8.14.0:
- version "8.14.1"
- resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.1.tgz#721d5dc10f7d5b5609a891773d47731796935dfb"
- integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==
-
-ajv@^6.12.4:
- version "6.12.6"
- resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
- integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
- dependencies:
- fast-deep-equal "^3.1.1"
- fast-json-stable-stringify "^2.0.0"
- json-schema-traverse "^0.4.1"
- uri-js "^4.2.2"
-
-ansi-escapes@^4.2.1:
- version "4.3.2"
- resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e"
- integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==
- dependencies:
- type-fest "^0.21.3"
-
-ansi-regex@^5.0.1:
- version "5.0.1"
- resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
- integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
-
-ansi-styles@^4.0.0, ansi-styles@^4.1.0:
- version "4.3.0"
- resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
- integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
- dependencies:
- color-convert "^2.0.1"
-
-ansi-styles@^5.0.0:
- version "5.2.0"
- resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
- integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
-
-anymatch@^3.0.3:
- version "3.1.3"
- resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"
- integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==
- dependencies:
- normalize-path "^3.0.0"
- picomatch "^2.0.4"
-
-argparse@^1.0.7:
- version "1.0.10"
- resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
- integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
- dependencies:
- sprintf-js "~1.0.2"
-
-argparse@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
- integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
-
-aria-query@5.3.0:
- version "5.3.0"
- resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e"
- integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==
- dependencies:
- dequal "^2.0.3"
-
-aria-query@^5.3.2:
- version "5.3.2"
- resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.2.tgz#93f81a43480e33a338f19163a3d10a50c01dcd59"
- integrity sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==
-
-array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz#384d12a37295aec3769ab022ad323a18a51ccf8b"
- integrity sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==
- dependencies:
- call-bound "^1.0.3"
- is-array-buffer "^3.0.5"
-
-array-includes@^3.1.6, array-includes@^3.1.8:
- version "3.1.8"
- resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.8.tgz#5e370cbe172fdd5dd6530c1d4aadda25281ba97d"
- integrity sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==
- dependencies:
- call-bind "^1.0.7"
- define-properties "^1.2.1"
- es-abstract "^1.23.2"
- es-object-atoms "^1.0.0"
- get-intrinsic "^1.2.4"
- is-string "^1.0.7"
-
-array.prototype.findlast@^1.2.5:
- version "1.2.5"
- resolved "https://registry.yarnpkg.com/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz#3e4fbcb30a15a7f5bf64cf2faae22d139c2e4904"
- integrity sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==
- dependencies:
- call-bind "^1.0.7"
- define-properties "^1.2.1"
- es-abstract "^1.23.2"
- es-errors "^1.3.0"
- es-object-atoms "^1.0.0"
- es-shim-unscopables "^1.0.2"
-
-array.prototype.findlastindex@^1.2.5:
- version "1.2.6"
- resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz#cfa1065c81dcb64e34557c9b81d012f6a421c564"
- integrity sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==
- dependencies:
- call-bind "^1.0.8"
- call-bound "^1.0.4"
- define-properties "^1.2.1"
- es-abstract "^1.23.9"
- es-errors "^1.3.0"
- es-object-atoms "^1.1.1"
- es-shim-unscopables "^1.1.0"
-
-array.prototype.flat@^1.3.1, array.prototype.flat@^1.3.2:
- version "1.3.3"
- resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz#534aaf9e6e8dd79fb6b9a9917f839ef1ec63afe5"
- integrity sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==
- dependencies:
- call-bind "^1.0.8"
- define-properties "^1.2.1"
- es-abstract "^1.23.5"
- es-shim-unscopables "^1.0.2"
-
-array.prototype.flatmap@^1.3.2, array.prototype.flatmap@^1.3.3:
- version "1.3.3"
- resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz#712cc792ae70370ae40586264629e33aab5dd38b"
- integrity sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==
- dependencies:
- call-bind "^1.0.8"
- define-properties "^1.2.1"
- es-abstract "^1.23.5"
- es-shim-unscopables "^1.0.2"
-
-array.prototype.tosorted@^1.1.4:
- version "1.1.4"
- resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz#fe954678ff53034e717ea3352a03f0b0b86f7ffc"
- integrity sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==
- dependencies:
- call-bind "^1.0.7"
- define-properties "^1.2.1"
- es-abstract "^1.23.3"
- es-errors "^1.3.0"
- es-shim-unscopables "^1.0.2"
-
-arraybuffer.prototype.slice@^1.0.4:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz#9d760d84dbdd06d0cbf92c8849615a1a7ab3183c"
- integrity sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==
- dependencies:
- array-buffer-byte-length "^1.0.1"
- call-bind "^1.0.8"
- define-properties "^1.2.1"
- es-abstract "^1.23.5"
- es-errors "^1.3.0"
- get-intrinsic "^1.2.6"
- is-array-buffer "^3.0.4"
-
-ast-types-flow@^0.0.8:
- version "0.0.8"
- resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.8.tgz#0a85e1c92695769ac13a428bb653e7538bea27d6"
- integrity sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==
-
-async-function@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/async-function/-/async-function-1.0.0.tgz#509c9fca60eaf85034c6829838188e4e4c8ffb2b"
- integrity sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==
-
-available-typed-arrays@^1.0.7:
- version "1.0.7"
- resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846"
- integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==
- dependencies:
- possible-typed-array-names "^1.0.0"
-
-axe-core@^4.10.0:
- version "4.10.3"
- resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.10.3.tgz#04145965ac7894faddbac30861e5d8f11bfd14fc"
- integrity sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==
-
-axobject-query@^4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-4.1.0.tgz#28768c76d0e3cff21bc62a9e2d0b6ac30042a1ee"
- integrity sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==
-
-babel-jest@^29.7.0:
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5"
- integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==
- dependencies:
- "@jest/transform" "^29.7.0"
- "@types/babel__core" "^7.1.14"
- babel-plugin-istanbul "^6.1.1"
- babel-preset-jest "^29.6.3"
- chalk "^4.0.0"
- graceful-fs "^4.2.9"
- slash "^3.0.0"
-
-babel-plugin-istanbul@^6.1.1:
- version "6.1.1"
- resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73"
- integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==
- dependencies:
- "@babel/helper-plugin-utils" "^7.0.0"
- "@istanbuljs/load-nyc-config" "^1.0.0"
- "@istanbuljs/schema" "^0.1.2"
- istanbul-lib-instrument "^5.0.4"
- test-exclude "^6.0.0"
-
-babel-plugin-jest-hoist@^29.6.3:
- version "29.6.3"
- resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626"
- integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==
- dependencies:
- "@babel/template" "^7.3.3"
- "@babel/types" "^7.3.3"
- "@types/babel__core" "^7.1.14"
- "@types/babel__traverse" "^7.0.6"
-
-babel-preset-current-node-syntax@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz#9a929eafece419612ef4ae4f60b1862ebad8ef30"
- integrity sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==
- dependencies:
- "@babel/plugin-syntax-async-generators" "^7.8.4"
- "@babel/plugin-syntax-bigint" "^7.8.3"
- "@babel/plugin-syntax-class-properties" "^7.12.13"
- "@babel/plugin-syntax-class-static-block" "^7.14.5"
- "@babel/plugin-syntax-import-attributes" "^7.24.7"
- "@babel/plugin-syntax-import-meta" "^7.10.4"
- "@babel/plugin-syntax-json-strings" "^7.8.3"
- "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
- "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3"
- "@babel/plugin-syntax-numeric-separator" "^7.10.4"
- "@babel/plugin-syntax-object-rest-spread" "^7.8.3"
- "@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
- "@babel/plugin-syntax-optional-chaining" "^7.8.3"
- "@babel/plugin-syntax-private-property-in-object" "^7.14.5"
- "@babel/plugin-syntax-top-level-await" "^7.14.5"
-
-babel-preset-jest@^29.6.3:
- version "29.6.3"
- resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c"
- integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==
- dependencies:
- babel-plugin-jest-hoist "^29.6.3"
- babel-preset-current-node-syntax "^1.0.0"
-
-balanced-match@^1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
- integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
-
-body-parser@^2.2.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-2.2.0.tgz#f7a9656de305249a715b549b7b8fd1ab9dfddcfa"
- integrity sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==
- dependencies:
- bytes "^3.1.2"
- content-type "^1.0.5"
- debug "^4.4.0"
- http-errors "^2.0.0"
- iconv-lite "^0.6.3"
- on-finished "^2.4.1"
- qs "^6.14.0"
- raw-body "^3.0.0"
- type-is "^2.0.0"
-
-brace-expansion@^1.1.7:
- version "1.1.11"
- resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
- integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
- dependencies:
- balanced-match "^1.0.0"
- concat-map "0.0.1"
-
-brace-expansion@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
- integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
- dependencies:
- balanced-match "^1.0.0"
-
-braces@^3.0.3:
- version "3.0.3"
- resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
- integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
- dependencies:
- fill-range "^7.1.1"
-
-browserslist@^4.24.0:
- version "4.24.5"
- resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.5.tgz#aa0f5b8560fe81fde84c6dcb38f759bafba0e11b"
- integrity sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==
- dependencies:
- caniuse-lite "^1.0.30001716"
- electron-to-chromium "^1.5.149"
- node-releases "^2.0.19"
- update-browserslist-db "^1.1.3"
-
-bser@2.1.1:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05"
- integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==
- dependencies:
- node-int64 "^0.4.0"
-
-buffer-from@^1.0.0:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
- integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
-
-busboy@1.6.0:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893"
- integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==
- dependencies:
- streamsearch "^1.1.0"
-
-bytes@3.1.2, bytes@^3.1.2:
- version "3.1.2"
- resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
- integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
-
-call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6"
- integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==
- dependencies:
- es-errors "^1.3.0"
- function-bind "^1.1.2"
-
-call-bind@^1.0.7, call-bind@^1.0.8:
- version "1.0.8"
- resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c"
- integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==
- dependencies:
- call-bind-apply-helpers "^1.0.0"
- es-define-property "^1.0.0"
- get-intrinsic "^1.2.4"
- set-function-length "^1.2.2"
-
-call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a"
- integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==
- dependencies:
- call-bind-apply-helpers "^1.0.2"
- get-intrinsic "^1.3.0"
-
-callsites@^3.0.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
- integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
-
-camelcase@^5.3.1:
- version "5.3.1"
- resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
- integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
-
-camelcase@^6.2.0:
- version "6.3.0"
- resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
- integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
-
-caniuse-lite@^1.0.30001579, caniuse-lite@^1.0.30001716:
- version "1.0.30001718"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz#dae13a9c80d517c30c6197515a96131c194d8f82"
- integrity sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==
-
-chalk@^4.0.0, chalk@^4.1.0:
- version "4.1.2"
- resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
- integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
- dependencies:
- ansi-styles "^4.1.0"
- supports-color "^7.1.0"
-
-char-regex@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf"
- integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==
-
-chownr@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/chownr/-/chownr-3.0.0.tgz#9855e64ecd240a9cc4267ce8a4aa5d24a1da15e4"
- integrity sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==
-
-ci-info@^3.2.0:
- version "3.9.0"
- resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4"
- integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==
-
-cjs-module-lexer@^1.0.0:
- version "1.4.3"
- resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz#0f79731eb8cfe1ec72acd4066efac9d61991b00d"
- integrity sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==
-
-class-variance-authority@^0.7.1:
- version "0.7.1"
- resolved "https://registry.yarnpkg.com/class-variance-authority/-/class-variance-authority-0.7.1.tgz#4008a798a0e4553a781a57ac5177c9fb5d043787"
- integrity sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==
- dependencies:
- clsx "^2.1.1"
-
-client-only@0.0.1:
- version "0.0.1"
- resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1"
- integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
-
-cliui@^8.0.1:
- version "8.0.1"
- resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa"
- integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==
- dependencies:
- string-width "^4.2.0"
- strip-ansi "^6.0.1"
- wrap-ansi "^7.0.0"
-
-clsx@^2.1.1:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999"
- integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
-
-co@^4.6.0:
- version "4.6.0"
- resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
- integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==
-
-collect-v8-coverage@^1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9"
- integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==
-
-color-convert@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
- integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
- dependencies:
- color-name "~1.1.4"
-
-color-name@^1.0.0, color-name@~1.1.4:
- version "1.1.4"
- resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
- integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
-
-color-string@^1.9.0:
- version "1.9.1"
- resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4"
- integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==
- dependencies:
- color-name "^1.0.0"
- simple-swizzle "^0.2.2"
-
-color@^4.2.3:
- version "4.2.3"
- resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a"
- integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==
- dependencies:
- color-convert "^2.0.1"
- color-string "^1.9.0"
-
-concat-map@0.0.1:
- version "0.0.1"
- resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
- integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
-
-content-disposition@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-1.0.0.tgz#844426cb398f934caefcbb172200126bc7ceace2"
- integrity sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==
- dependencies:
- safe-buffer "5.2.1"
-
-content-type@^1.0.5:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
- integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
-
-convert-source-map@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
- integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
-
-cookie-signature@^1.2.1:
- version "1.2.2"
- resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793"
- integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==
-
-cookie@^0.7.1:
- version "0.7.2"
- resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7"
- integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==
-
-cors@^2.8.5:
- version "2.8.5"
- resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
- integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
- dependencies:
- object-assign "^4"
- vary "^1"
-
-create-jest@^29.7.0:
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320"
- integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==
- dependencies:
- "@jest/types" "^29.6.3"
- chalk "^4.0.0"
- exit "^0.1.2"
- graceful-fs "^4.2.9"
- jest-config "^29.7.0"
- jest-util "^29.7.0"
- prompts "^2.0.1"
-
-cross-spawn@^7.0.3, cross-spawn@^7.0.6:
- version "7.0.6"
- resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
- integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
- dependencies:
- path-key "^3.1.0"
- shebang-command "^2.0.0"
- which "^2.0.1"
-
-csstype@^3.0.2:
- version "3.1.3"
- resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
- integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
-
-damerau-levenshtein@^1.0.8:
- version "1.0.8"
- resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7"
- integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==
-
-data-view-buffer@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.2.tgz#211a03ba95ecaf7798a8c7198d79536211f88570"
- integrity sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==
- dependencies:
- call-bound "^1.0.3"
- es-errors "^1.3.0"
- is-data-view "^1.0.2"
-
-data-view-byte-length@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz#9e80f7ca52453ce3e93d25a35318767ea7704735"
- integrity sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==
- dependencies:
- call-bound "^1.0.3"
- es-errors "^1.3.0"
- is-data-view "^1.0.2"
-
-data-view-byte-offset@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz#068307f9b71ab76dbbe10291389e020856606191"
- integrity sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==
- dependencies:
- call-bound "^1.0.2"
- es-errors "^1.3.0"
- is-data-view "^1.0.1"
-
-debug@^3.2.7:
- version "3.2.7"
- resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
- integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
- dependencies:
- ms "^2.1.1"
-
-debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5, debug@^4.4.0:
- version "4.4.1"
- resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b"
- integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==
- dependencies:
- ms "^2.1.3"
-
-dedent@^1.0.0:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.6.0.tgz#79d52d6389b1ffa67d2bcef59ba51847a9d503b2"
- integrity sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==
-
-deep-is@^0.1.3:
- version "0.1.4"
- resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
- integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
-
-deepmerge@^4.2.2:
- version "4.3.1"
- resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a"
- integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==
-
-define-data-property@^1.0.1, define-data-property@^1.1.4:
- version "1.1.4"
- resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e"
- integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==
- dependencies:
- es-define-property "^1.0.0"
- es-errors "^1.3.0"
- gopd "^1.0.1"
-
-define-properties@^1.1.3, define-properties@^1.2.1:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c"
- integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==
- dependencies:
- define-data-property "^1.0.1"
- has-property-descriptors "^1.0.0"
- object-keys "^1.1.1"
-
-depd@2.0.0, depd@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
- integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
-
-dequal@^2.0.3:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be"
- integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==
-
-detect-libc@^2.0.3, detect-libc@^2.0.4:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.4.tgz#f04715b8ba815e53b4d8109655b6508a6865a7e8"
- integrity sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==
-
-detect-newline@^3.0.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
- integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==
-
-diff-sequences@^29.6.3:
- version "29.6.3"
- resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921"
- integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==
-
-doctrine@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d"
- integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==
- dependencies:
- esutils "^2.0.2"
-
-dom-accessibility-api@^0.5.9:
- version "0.5.16"
- resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453"
- integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==
-
-dunder-proto@^1.0.0, dunder-proto@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a"
- integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==
- dependencies:
- call-bind-apply-helpers "^1.0.1"
- es-errors "^1.3.0"
- gopd "^1.2.0"
-
-ee-first@1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
- integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
-
-electron-to-chromium@^1.5.149:
- version "1.5.153"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.153.tgz#edf1e4d2c00bb88bc642ac01dec2b836d4cfedfe"
- integrity sha512-4bwluTFwjXZ0/ei1qDpHDGzVveuBfx4wiZ9VQ8j/30+T2JxSF2TfZ00d1X+wNMeDyUdZXgLkJFbarJdAMtd+/w==
-
-emittery@^0.13.1:
- version "0.13.1"
- resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad"
- integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==
-
-emoji-regex@^8.0.0:
- version "8.0.0"
- resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
- integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
-
-emoji-regex@^9.2.2:
- version "9.2.2"
- resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
- integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
-
-encodeurl@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58"
- integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==
-
-enhanced-resolve@^5.18.1:
- version "5.18.1"
- resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz#728ab082f8b7b6836de51f1637aab5d3b9568faf"
- integrity sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==
- dependencies:
- graceful-fs "^4.2.4"
- tapable "^2.2.0"
-
-error-ex@^1.3.1:
- version "1.3.2"
- resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
- integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==
- dependencies:
- is-arrayish "^0.2.1"
-
-es-abstract@^1.17.5, es-abstract@^1.23.2, es-abstract@^1.23.3, es-abstract@^1.23.5, es-abstract@^1.23.6, es-abstract@^1.23.9:
- version "1.23.9"
- resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.9.tgz#5b45994b7de78dada5c1bebf1379646b32b9d606"
- integrity sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==
- dependencies:
- array-buffer-byte-length "^1.0.2"
- arraybuffer.prototype.slice "^1.0.4"
- available-typed-arrays "^1.0.7"
- call-bind "^1.0.8"
- call-bound "^1.0.3"
- data-view-buffer "^1.0.2"
- data-view-byte-length "^1.0.2"
- data-view-byte-offset "^1.0.1"
- es-define-property "^1.0.1"
- es-errors "^1.3.0"
- es-object-atoms "^1.0.0"
- es-set-tostringtag "^2.1.0"
- es-to-primitive "^1.3.0"
- function.prototype.name "^1.1.8"
- get-intrinsic "^1.2.7"
- get-proto "^1.0.0"
- get-symbol-description "^1.1.0"
- globalthis "^1.0.4"
- gopd "^1.2.0"
- has-property-descriptors "^1.0.2"
- has-proto "^1.2.0"
- has-symbols "^1.1.0"
- hasown "^2.0.2"
- internal-slot "^1.1.0"
- is-array-buffer "^3.0.5"
- is-callable "^1.2.7"
- is-data-view "^1.0.2"
- is-regex "^1.2.1"
- is-shared-array-buffer "^1.0.4"
- is-string "^1.1.1"
- is-typed-array "^1.1.15"
- is-weakref "^1.1.0"
- math-intrinsics "^1.1.0"
- object-inspect "^1.13.3"
- object-keys "^1.1.1"
- object.assign "^4.1.7"
- own-keys "^1.0.1"
- regexp.prototype.flags "^1.5.3"
- safe-array-concat "^1.1.3"
- safe-push-apply "^1.0.0"
- safe-regex-test "^1.1.0"
- set-proto "^1.0.0"
- string.prototype.trim "^1.2.10"
- string.prototype.trimend "^1.0.9"
- string.prototype.trimstart "^1.0.8"
- typed-array-buffer "^1.0.3"
- typed-array-byte-length "^1.0.3"
- typed-array-byte-offset "^1.0.4"
- typed-array-length "^1.0.7"
- unbox-primitive "^1.1.0"
- which-typed-array "^1.1.18"
-
-es-define-property@^1.0.0, es-define-property@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa"
- integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==
-
-es-errors@^1.3.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
- integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
-
-es-iterator-helpers@^1.2.1:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz#d1dd0f58129054c0ad922e6a9a1e65eef435fe75"
- integrity sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==
- dependencies:
- call-bind "^1.0.8"
- call-bound "^1.0.3"
- define-properties "^1.2.1"
- es-abstract "^1.23.6"
- es-errors "^1.3.0"
- es-set-tostringtag "^2.0.3"
- function-bind "^1.1.2"
- get-intrinsic "^1.2.6"
- globalthis "^1.0.4"
- gopd "^1.2.0"
- has-property-descriptors "^1.0.2"
- has-proto "^1.2.0"
- has-symbols "^1.1.0"
- internal-slot "^1.1.0"
- iterator.prototype "^1.1.4"
- safe-array-concat "^1.1.3"
-
-es-object-atoms@^1.0.0, es-object-atoms@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1"
- integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==
- dependencies:
- es-errors "^1.3.0"
-
-es-set-tostringtag@^2.0.3, es-set-tostringtag@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d"
- integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==
- dependencies:
- es-errors "^1.3.0"
- get-intrinsic "^1.2.6"
- has-tostringtag "^1.0.2"
- hasown "^2.0.2"
-
-es-shim-unscopables@^1.0.2, es-shim-unscopables@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz#438df35520dac5d105f3943d927549ea3b00f4b5"
- integrity sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==
- dependencies:
- hasown "^2.0.2"
-
-es-to-primitive@^1.3.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.3.0.tgz#96c89c82cc49fd8794a24835ba3e1ff87f214e18"
- integrity sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==
- dependencies:
- is-callable "^1.2.7"
- is-date-object "^1.0.5"
- is-symbol "^1.0.4"
-
-escalade@^3.1.1, escalade@^3.2.0:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5"
- integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==
-
-escape-html@^1.0.3:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
- integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
-
-escape-string-regexp@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344"
- integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==
-
-escape-string-regexp@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
- integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
-
-eslint-config-next@15.3.2:
- version "15.3.2"
- resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-15.3.2.tgz#ed2bc6607f54555bbeaf23a032b4e430e453b63c"
- integrity sha512-FerU4DYccO4FgeYFFglz0SnaKRe1ejXQrDb8kWUkTAg036YWi+jUsgg4sIGNCDhAsDITsZaL4MzBWKB6f4G1Dg==
- dependencies:
- "@next/eslint-plugin-next" "15.3.2"
- "@rushstack/eslint-patch" "^1.10.3"
- "@typescript-eslint/eslint-plugin" "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0"
- "@typescript-eslint/parser" "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0"
- eslint-import-resolver-node "^0.3.6"
- eslint-import-resolver-typescript "^3.5.2"
- eslint-plugin-import "^2.31.0"
- eslint-plugin-jsx-a11y "^6.10.0"
- eslint-plugin-react "^7.37.0"
- eslint-plugin-react-hooks "^5.0.0"
-
-eslint-import-resolver-node@^0.3.6, eslint-import-resolver-node@^0.3.9:
- version "0.3.9"
- resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac"
- integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==
- dependencies:
- debug "^3.2.7"
- is-core-module "^2.13.0"
- resolve "^1.22.4"
-
-eslint-import-resolver-typescript@^3.5.2:
- version "3.10.1"
- resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz#23dac32efa86a88e2b8232eb244ac499ad636db2"
- integrity sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==
- dependencies:
- "@nolyfill/is-core-module" "1.0.39"
- debug "^4.4.0"
- get-tsconfig "^4.10.0"
- is-bun-module "^2.0.0"
- stable-hash "^0.0.5"
- tinyglobby "^0.2.13"
- unrs-resolver "^1.6.2"
-
-eslint-module-utils@^2.12.0:
- version "2.12.0"
- resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz#fe4cfb948d61f49203d7b08871982b65b9af0b0b"
- integrity sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==
- dependencies:
- debug "^3.2.7"
-
-eslint-plugin-import@^2.31.0:
- version "2.31.0"
- resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz#310ce7e720ca1d9c0bb3f69adfd1c6bdd7d9e0e7"
- integrity sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==
- dependencies:
- "@rtsao/scc" "^1.1.0"
- array-includes "^3.1.8"
- array.prototype.findlastindex "^1.2.5"
- array.prototype.flat "^1.3.2"
- array.prototype.flatmap "^1.3.2"
- debug "^3.2.7"
- doctrine "^2.1.0"
- eslint-import-resolver-node "^0.3.9"
- eslint-module-utils "^2.12.0"
- hasown "^2.0.2"
- is-core-module "^2.15.1"
- is-glob "^4.0.3"
- minimatch "^3.1.2"
- object.fromentries "^2.0.8"
- object.groupby "^1.0.3"
- object.values "^1.2.0"
- semver "^6.3.1"
- string.prototype.trimend "^1.0.8"
- tsconfig-paths "^3.15.0"
-
-eslint-plugin-jsx-a11y@^6.10.0:
- version "6.10.2"
- resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz#d2812bb23bf1ab4665f1718ea442e8372e638483"
- integrity sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==
- dependencies:
- aria-query "^5.3.2"
- array-includes "^3.1.8"
- array.prototype.flatmap "^1.3.2"
- ast-types-flow "^0.0.8"
- axe-core "^4.10.0"
- axobject-query "^4.1.0"
- damerau-levenshtein "^1.0.8"
- emoji-regex "^9.2.2"
- hasown "^2.0.2"
- jsx-ast-utils "^3.3.5"
- language-tags "^1.0.9"
- minimatch "^3.1.2"
- object.fromentries "^2.0.8"
- safe-regex-test "^1.0.3"
- string.prototype.includes "^2.0.1"
-
-eslint-plugin-react-hooks@^5.0.0:
- version "5.2.0"
- resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz#1be0080901e6ac31ce7971beed3d3ec0a423d9e3"
- integrity sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==
-
-eslint-plugin-react@^7.37.0:
- version "7.37.5"
- resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz#2975511472bdda1b272b34d779335c9b0e877065"
- integrity sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==
- dependencies:
- array-includes "^3.1.8"
- array.prototype.findlast "^1.2.5"
- array.prototype.flatmap "^1.3.3"
- array.prototype.tosorted "^1.1.4"
- doctrine "^2.1.0"
- es-iterator-helpers "^1.2.1"
- estraverse "^5.3.0"
- hasown "^2.0.2"
- jsx-ast-utils "^2.4.1 || ^3.0.0"
- minimatch "^3.1.2"
- object.entries "^1.1.9"
- object.fromentries "^2.0.8"
- object.values "^1.2.1"
- prop-types "^15.8.1"
- resolve "^2.0.0-next.5"
- semver "^6.3.1"
- string.prototype.matchall "^4.0.12"
- string.prototype.repeat "^1.0.0"
-
-eslint-scope@^8.3.0:
- version "8.3.0"
- resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.3.0.tgz#10cd3a918ffdd722f5f3f7b5b83db9b23c87340d"
- integrity sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==
- dependencies:
- esrecurse "^4.3.0"
- estraverse "^5.2.0"
-
-eslint-visitor-keys@^3.4.3:
- version "3.4.3"
- resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
- integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
-
-eslint-visitor-keys@^4.2.0:
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45"
- integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==
-
-eslint@^9:
- version "9.26.0"
- resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.26.0.tgz#978fe029adc2aceed28ab437bca876e83461c3b4"
- integrity sha512-Hx0MOjPh6uK9oq9nVsATZKE/Wlbai7KFjfCuw9UHaguDW3x+HF0O5nIi3ud39TWgrTjTO5nHxmL3R1eANinWHQ==
- dependencies:
- "@eslint-community/eslint-utils" "^4.2.0"
- "@eslint-community/regexpp" "^4.12.1"
- "@eslint/config-array" "^0.20.0"
- "@eslint/config-helpers" "^0.2.1"
- "@eslint/core" "^0.13.0"
- "@eslint/eslintrc" "^3.3.1"
- "@eslint/js" "9.26.0"
- "@eslint/plugin-kit" "^0.2.8"
- "@humanfs/node" "^0.16.6"
- "@humanwhocodes/module-importer" "^1.0.1"
- "@humanwhocodes/retry" "^0.4.2"
- "@modelcontextprotocol/sdk" "^1.8.0"
- "@types/estree" "^1.0.6"
- "@types/json-schema" "^7.0.15"
- ajv "^6.12.4"
- chalk "^4.0.0"
- cross-spawn "^7.0.6"
- debug "^4.3.2"
- escape-string-regexp "^4.0.0"
- eslint-scope "^8.3.0"
- eslint-visitor-keys "^4.2.0"
- espree "^10.3.0"
- esquery "^1.5.0"
- esutils "^2.0.2"
- fast-deep-equal "^3.1.3"
- file-entry-cache "^8.0.0"
- find-up "^5.0.0"
- glob-parent "^6.0.2"
- ignore "^5.2.0"
- imurmurhash "^0.1.4"
- is-glob "^4.0.0"
- json-stable-stringify-without-jsonify "^1.0.1"
- lodash.merge "^4.6.2"
- minimatch "^3.1.2"
- natural-compare "^1.4.0"
- optionator "^0.9.3"
- zod "^3.24.2"
-
-espree@^10.0.1, espree@^10.3.0:
- version "10.3.0"
- resolved "https://registry.yarnpkg.com/espree/-/espree-10.3.0.tgz#29267cf5b0cb98735b65e64ba07e0ed49d1eed8a"
- integrity sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==
- dependencies:
- acorn "^8.14.0"
- acorn-jsx "^5.3.2"
- eslint-visitor-keys "^4.2.0"
-
-esprima@^4.0.0:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
- integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
-
-esquery@^1.5.0:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7"
- integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==
- dependencies:
- estraverse "^5.1.0"
-
-esrecurse@^4.3.0:
- version "4.3.0"
- resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
- integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
- dependencies:
- estraverse "^5.2.0"
-
-estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0:
- version "5.3.0"
- resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123"
- integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
-
-esutils@^2.0.2:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
- integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
-
-etag@^1.8.1:
- version "1.8.1"
- resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
- integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
-
-eventsource-parser@^3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-3.0.1.tgz#5e358dba9a55ba64ca90da883c4ca35bd82467bd"
- integrity sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==
-
-eventsource@^3.0.2:
- version "3.0.7"
- resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-3.0.7.tgz#1157622e2f5377bb6aef2114372728ba0c156989"
- integrity sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==
- dependencies:
- eventsource-parser "^3.0.1"
-
-execa@^5.0.0:
- version "5.1.1"
- resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
- integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==
- dependencies:
- cross-spawn "^7.0.3"
- get-stream "^6.0.0"
- human-signals "^2.1.0"
- is-stream "^2.0.0"
- merge-stream "^2.0.0"
- npm-run-path "^4.0.1"
- onetime "^5.1.2"
- signal-exit "^3.0.3"
- strip-final-newline "^2.0.0"
-
-exit@^0.1.2:
- version "0.1.2"
- resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
- integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==
-
-expect@^29.7.0:
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc"
- integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==
- dependencies:
- "@jest/expect-utils" "^29.7.0"
- jest-get-type "^29.6.3"
- jest-matcher-utils "^29.7.0"
- jest-message-util "^29.7.0"
- jest-util "^29.7.0"
-
-express-rate-limit@^7.5.0:
- version "7.5.0"
- resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-7.5.0.tgz#6a67990a724b4fbbc69119419feef50c51e8b28f"
- integrity sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==
-
-express@^5.0.1:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/express/-/express-5.1.0.tgz#d31beaf715a0016f0d53f47d3b4d7acf28c75cc9"
- integrity sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==
- dependencies:
- accepts "^2.0.0"
- body-parser "^2.2.0"
- content-disposition "^1.0.0"
- content-type "^1.0.5"
- cookie "^0.7.1"
- cookie-signature "^1.2.1"
- debug "^4.4.0"
- encodeurl "^2.0.0"
- escape-html "^1.0.3"
- etag "^1.8.1"
- finalhandler "^2.1.0"
- fresh "^2.0.0"
- http-errors "^2.0.0"
- merge-descriptors "^2.0.0"
- mime-types "^3.0.0"
- on-finished "^2.4.1"
- once "^1.4.0"
- parseurl "^1.3.3"
- proxy-addr "^2.0.7"
- qs "^6.14.0"
- range-parser "^1.2.1"
- router "^2.2.0"
- send "^1.1.0"
- serve-static "^2.2.0"
- statuses "^2.0.1"
- type-is "^2.0.1"
- vary "^1.1.2"
-
-fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
- version "3.1.3"
- resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
- integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
-
-fast-glob@3.3.1:
- version "3.3.1"
- resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4"
- integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==
- dependencies:
- "@nodelib/fs.stat" "^2.0.2"
- "@nodelib/fs.walk" "^1.2.3"
- glob-parent "^5.1.2"
- merge2 "^1.3.0"
- micromatch "^4.0.4"
-
-fast-glob@^3.3.2:
- version "3.3.3"
- resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818"
- integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==
- dependencies:
- "@nodelib/fs.stat" "^2.0.2"
- "@nodelib/fs.walk" "^1.2.3"
- glob-parent "^5.1.2"
- merge2 "^1.3.0"
- micromatch "^4.0.8"
-
-fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
- integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
-
-fast-levenshtein@^2.0.6:
- version "2.0.6"
- resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
- integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
-
-fastq@^1.6.0:
- version "1.19.1"
- resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5"
- integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==
- dependencies:
- reusify "^1.0.4"
-
-fb-watchman@^2.0.0:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c"
- integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==
- dependencies:
- bser "2.1.1"
-
-fdir@^6.4.4:
- version "6.4.4"
- resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.4.4.tgz#1cfcf86f875a883e19a8fab53622cfe992e8d2f9"
- integrity sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==
-
-file-entry-cache@^8.0.0:
- version "8.0.0"
- resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f"
- integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==
- dependencies:
- flat-cache "^4.0.0"
-
-fill-range@^7.1.1:
- version "7.1.1"
- resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
- integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
- dependencies:
- to-regex-range "^5.0.1"
-
-finalhandler@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-2.1.0.tgz#72306373aa89d05a8242ed569ed86a1bff7c561f"
- integrity sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==
- dependencies:
- debug "^4.4.0"
- encodeurl "^2.0.0"
- escape-html "^1.0.3"
- on-finished "^2.4.1"
- parseurl "^1.3.3"
- statuses "^2.0.1"
-
-find-up@^4.0.0, find-up@^4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
- integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
- dependencies:
- locate-path "^5.0.0"
- path-exists "^4.0.0"
-
-find-up@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
- integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
- dependencies:
- locate-path "^6.0.0"
- path-exists "^4.0.0"
-
-flat-cache@^4.0.0:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c"
- integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==
- dependencies:
- flatted "^3.2.9"
- keyv "^4.5.4"
-
-flatted@^3.2.9:
- version "3.3.3"
- resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358"
- integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==
-
-for-each@^0.3.3, for-each@^0.3.5:
- version "0.3.5"
- resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47"
- integrity sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==
- dependencies:
- is-callable "^1.2.7"
-
-forwarded@0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
- integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
-
-fresh@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/fresh/-/fresh-2.0.0.tgz#8dd7df6a1b3a1b3a5cf186c05a5dd267622635a4"
- integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==
-
-fs.realpath@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
- integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
-
-fsevents@^2.3.2:
- version "2.3.3"
- resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
- integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
-
-function-bind@^1.1.2:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
- integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
-
-function.prototype.name@^1.1.6, function.prototype.name@^1.1.8:
- version "1.1.8"
- resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.8.tgz#e68e1df7b259a5c949eeef95cdbde53edffabb78"
- integrity sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==
- dependencies:
- call-bind "^1.0.8"
- call-bound "^1.0.3"
- define-properties "^1.2.1"
- functions-have-names "^1.2.3"
- hasown "^2.0.2"
- is-callable "^1.2.7"
-
-functions-have-names@^1.2.3:
- version "1.2.3"
- resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
- integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
-
-gensync@^1.0.0-beta.2:
- version "1.0.0-beta.2"
- resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
- integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
-
-get-caller-file@^2.0.5:
- version "2.0.5"
- resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
- integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
-
-get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7, get-intrinsic@^1.3.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01"
- integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==
- dependencies:
- call-bind-apply-helpers "^1.0.2"
- es-define-property "^1.0.1"
- es-errors "^1.3.0"
- es-object-atoms "^1.1.1"
- function-bind "^1.1.2"
- get-proto "^1.0.1"
- gopd "^1.2.0"
- has-symbols "^1.1.0"
- hasown "^2.0.2"
- math-intrinsics "^1.1.0"
-
-get-package-type@^0.1.0:
- version "0.1.0"
- resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a"
- integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==
-
-get-proto@^1.0.0, get-proto@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1"
- integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==
- dependencies:
- dunder-proto "^1.0.1"
- es-object-atoms "^1.0.0"
-
-get-stream@^6.0.0:
- version "6.0.1"
- resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
- integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
-
-get-symbol-description@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz#7bdd54e0befe8ffc9f3b4e203220d9f1e881b6ee"
- integrity sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==
- dependencies:
- call-bound "^1.0.3"
- es-errors "^1.3.0"
- get-intrinsic "^1.2.6"
-
-get-tsconfig@^4.10.0:
- version "4.10.0"
- resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.10.0.tgz#403a682b373a823612475a4c2928c7326fc0f6bb"
- integrity sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==
- dependencies:
- resolve-pkg-maps "^1.0.0"
-
-glob-parent@^5.1.2:
- version "5.1.2"
- resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
- integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
- dependencies:
- is-glob "^4.0.1"
-
-glob-parent@^6.0.2:
- version "6.0.2"
- resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3"
- integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
- dependencies:
- is-glob "^4.0.3"
-
-glob@^7.1.3, glob@^7.1.4:
- version "7.2.3"
- resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
- integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
- dependencies:
- fs.realpath "^1.0.0"
- inflight "^1.0.4"
- inherits "2"
- minimatch "^3.1.1"
- once "^1.3.0"
- path-is-absolute "^1.0.0"
-
-globals@^11.1.0:
- version "11.12.0"
- resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
- integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
-
-globals@^14.0.0:
- version "14.0.0"
- resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e"
- integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==
-
-globalthis@^1.0.4:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236"
- integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==
- dependencies:
- define-properties "^1.2.1"
- gopd "^1.0.1"
-
-gopd@^1.0.1, gopd@^1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1"
- integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
-
-graceful-fs@^4.2.4, graceful-fs@^4.2.9:
- version "4.2.11"
- resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
- integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
-
-graphemer@^1.4.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
- integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
-
-has-bigints@^1.0.2:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.1.0.tgz#28607e965ac967e03cd2a2c70a2636a1edad49fe"
- integrity sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==
-
-has-flag@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
- integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
-
-has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854"
- integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==
- dependencies:
- es-define-property "^1.0.0"
-
-has-proto@^1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.2.0.tgz#5de5a6eabd95fdffd9818b43055e8065e39fe9d5"
- integrity sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==
- dependencies:
- dunder-proto "^1.0.0"
-
-has-symbols@^1.0.3, has-symbols@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338"
- integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==
-
-has-tostringtag@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc"
- integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==
- dependencies:
- has-symbols "^1.0.3"
-
-hasown@^2.0.2:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
- integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
- dependencies:
- function-bind "^1.1.2"
-
-html-escaper@^2.0.0:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
- integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
-
-http-errors@2.0.0, http-errors@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3"
- integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==
- dependencies:
- depd "2.0.0"
- inherits "2.0.4"
- setprototypeof "1.2.0"
- statuses "2.0.1"
- toidentifier "1.0.1"
-
-human-signals@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
- integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
-
-iconv-lite@0.6.3, iconv-lite@^0.6.3:
- version "0.6.3"
- resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
- integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
- dependencies:
- safer-buffer ">= 2.1.2 < 3.0.0"
-
-ignore@^5.2.0:
- version "5.3.2"
- resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5"
- integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==
-
-ignore@^7.0.0:
- version "7.0.4"
- resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.4.tgz#a12c70d0f2607c5bf508fb65a40c75f037d7a078"
- integrity sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==
-
-import-fresh@^3.2.1:
- version "3.3.1"
- resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf"
- integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==
- dependencies:
- parent-module "^1.0.0"
- resolve-from "^4.0.0"
-
-import-local@^3.0.2:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260"
- integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==
- dependencies:
- pkg-dir "^4.2.0"
- resolve-cwd "^3.0.0"
-
-imurmurhash@^0.1.4:
- version "0.1.4"
- resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
- integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==
-
-inflight@^1.0.4:
- version "1.0.6"
- resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
- integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==
- dependencies:
- once "^1.3.0"
- wrappy "1"
-
-inherits@2, inherits@2.0.4:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
- integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
-
-internal-slot@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.1.0.tgz#1eac91762947d2f7056bc838d93e13b2e9604961"
- integrity sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==
- dependencies:
- es-errors "^1.3.0"
- hasown "^2.0.2"
- side-channel "^1.1.0"
-
-ipaddr.js@1.9.1:
- version "1.9.1"
- resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
- integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
-
-is-array-buffer@^3.0.4, is-array-buffer@^3.0.5:
- version "3.0.5"
- resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz#65742e1e687bd2cc666253068fd8707fe4d44280"
- integrity sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==
- dependencies:
- call-bind "^1.0.8"
- call-bound "^1.0.3"
- get-intrinsic "^1.2.6"
-
-is-arrayish@^0.2.1:
- version "0.2.1"
- resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
- integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==
-
-is-arrayish@^0.3.1:
- version "0.3.2"
- resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03"
- integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==
-
-is-async-function@^2.0.0:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.1.1.tgz#3e69018c8e04e73b738793d020bfe884b9fd3523"
- integrity sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==
- dependencies:
- async-function "^1.0.0"
- call-bound "^1.0.3"
- get-proto "^1.0.1"
- has-tostringtag "^1.0.2"
- safe-regex-test "^1.1.0"
-
-is-bigint@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.1.0.tgz#dda7a3445df57a42583db4228682eba7c4170672"
- integrity sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==
- dependencies:
- has-bigints "^1.0.2"
-
-is-boolean-object@^1.2.1:
- version "1.2.2"
- resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.2.2.tgz#7067f47709809a393c71ff5bb3e135d8a9215d9e"
- integrity sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==
- dependencies:
- call-bound "^1.0.3"
- has-tostringtag "^1.0.2"
-
-is-bun-module@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/is-bun-module/-/is-bun-module-2.0.0.tgz#4d7859a87c0fcac950c95e666730e745eae8bddd"
- integrity sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==
- dependencies:
- semver "^7.7.1"
-
-is-callable@^1.2.7:
- version "1.2.7"
- resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055"
- integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==
-
-is-core-module@^2.13.0, is-core-module@^2.15.1, is-core-module@^2.16.0:
- version "2.16.1"
- resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4"
- integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==
- dependencies:
- hasown "^2.0.2"
-
-is-data-view@^1.0.1, is-data-view@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.2.tgz#bae0a41b9688986c2188dda6657e56b8f9e63b8e"
- integrity sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==
- dependencies:
- call-bound "^1.0.2"
- get-intrinsic "^1.2.6"
- is-typed-array "^1.1.13"
-
-is-date-object@^1.0.5, is-date-object@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.1.0.tgz#ad85541996fc7aa8b2729701d27b7319f95d82f7"
- integrity sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==
- dependencies:
- call-bound "^1.0.2"
- has-tostringtag "^1.0.2"
-
-is-extglob@^2.1.1:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
- integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
-
-is-finalizationregistry@^1.1.0:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz#eefdcdc6c94ddd0674d9c85887bf93f944a97c90"
- integrity sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==
- dependencies:
- call-bound "^1.0.3"
-
-is-fullwidth-code-point@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
- integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
-
-is-generator-fn@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118"
- integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==
-
-is-generator-function@^1.0.10:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.1.0.tgz#bf3eeda931201394f57b5dba2800f91a238309ca"
- integrity sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==
- dependencies:
- call-bound "^1.0.3"
- get-proto "^1.0.0"
- has-tostringtag "^1.0.2"
- safe-regex-test "^1.1.0"
-
-is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3:
- version "4.0.3"
- resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
- integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
- dependencies:
- is-extglob "^2.1.1"
-
-is-map@^2.0.3:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e"
- integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==
-
-is-number-object@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.1.1.tgz#144b21e95a1bc148205dcc2814a9134ec41b2541"
- integrity sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==
- dependencies:
- call-bound "^1.0.3"
- has-tostringtag "^1.0.2"
-
-is-number@^7.0.0:
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
- integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
-
-is-promise@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3"
- integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==
-
-is-regex@^1.2.1:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22"
- integrity sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==
- dependencies:
- call-bound "^1.0.2"
- gopd "^1.2.0"
- has-tostringtag "^1.0.2"
- hasown "^2.0.2"
-
-is-set@^2.0.3:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d"
- integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==
-
-is-shared-array-buffer@^1.0.4:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz#9b67844bd9b7f246ba0708c3a93e34269c774f6f"
- integrity sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==
- dependencies:
- call-bound "^1.0.3"
-
-is-stream@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077"
- integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==
-
-is-string@^1.0.7, is-string@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.1.1.tgz#92ea3f3d5c5b6e039ca8677e5ac8d07ea773cbb9"
- integrity sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==
- dependencies:
- call-bound "^1.0.3"
- has-tostringtag "^1.0.2"
-
-is-symbol@^1.0.4, is-symbol@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.1.1.tgz#f47761279f532e2b05a7024a7506dbbedacd0634"
- integrity sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==
- dependencies:
- call-bound "^1.0.2"
- has-symbols "^1.1.0"
- safe-regex-test "^1.1.0"
-
-is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15:
- version "1.1.15"
- resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.15.tgz#4bfb4a45b61cee83a5a46fba778e4e8d59c0ce0b"
- integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==
- dependencies:
- which-typed-array "^1.1.16"
-
-is-weakmap@^2.0.2:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd"
- integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==
-
-is-weakref@^1.0.2, is-weakref@^1.1.0:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.1.1.tgz#eea430182be8d64174bd96bffbc46f21bf3f9293"
- integrity sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==
- dependencies:
- call-bound "^1.0.3"
-
-is-weakset@^2.0.3:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.4.tgz#c9f5deb0bc1906c6d6f1027f284ddf459249daca"
- integrity sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==
- dependencies:
- call-bound "^1.0.3"
- get-intrinsic "^1.2.6"
-
-isarray@^2.0.5:
- version "2.0.5"
- resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
- integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
-
-isexe@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
- integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
-
-istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0:
- version "3.2.2"
- resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756"
- integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==
-
-istanbul-lib-instrument@^5.0.4:
- version "5.2.1"
- resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d"
- integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==
- dependencies:
- "@babel/core" "^7.12.3"
- "@babel/parser" "^7.14.7"
- "@istanbuljs/schema" "^0.1.2"
- istanbul-lib-coverage "^3.2.0"
- semver "^6.3.0"
-
-istanbul-lib-instrument@^6.0.0:
- version "6.0.3"
- resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765"
- integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==
- dependencies:
- "@babel/core" "^7.23.9"
- "@babel/parser" "^7.23.9"
- "@istanbuljs/schema" "^0.1.3"
- istanbul-lib-coverage "^3.2.0"
- semver "^7.5.4"
-
-istanbul-lib-report@^3.0.0:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d"
- integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==
- dependencies:
- istanbul-lib-coverage "^3.0.0"
- make-dir "^4.0.0"
- supports-color "^7.1.0"
-
-istanbul-lib-source-maps@^4.0.0:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551"
- integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==
- dependencies:
- debug "^4.1.1"
- istanbul-lib-coverage "^3.0.0"
- source-map "^0.6.1"
-
-istanbul-reports@^3.1.3:
- version "3.1.7"
- resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b"
- integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==
- dependencies:
- html-escaper "^2.0.0"
- istanbul-lib-report "^3.0.0"
-
-iterator.prototype@^1.1.4:
- version "1.1.5"
- resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.5.tgz#12c959a29de32de0aa3bbbb801f4d777066dae39"
- integrity sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==
- dependencies:
- define-data-property "^1.1.4"
- es-object-atoms "^1.0.0"
- get-intrinsic "^1.2.6"
- get-proto "^1.0.0"
- has-symbols "^1.1.0"
- set-function-name "^2.0.2"
-
-jest-changed-files@^29.7.0:
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a"
- integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==
- dependencies:
- execa "^5.0.0"
- jest-util "^29.7.0"
- p-limit "^3.1.0"
-
-jest-circus@^29.7.0:
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a"
- integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==
- dependencies:
- "@jest/environment" "^29.7.0"
- "@jest/expect" "^29.7.0"
- "@jest/test-result" "^29.7.0"
- "@jest/types" "^29.6.3"
- "@types/node" "*"
- chalk "^4.0.0"
- co "^4.6.0"
- dedent "^1.0.0"
- is-generator-fn "^2.0.0"
- jest-each "^29.7.0"
- jest-matcher-utils "^29.7.0"
- jest-message-util "^29.7.0"
- jest-runtime "^29.7.0"
- jest-snapshot "^29.7.0"
- jest-util "^29.7.0"
- p-limit "^3.1.0"
- pretty-format "^29.7.0"
- pure-rand "^6.0.0"
- slash "^3.0.0"
- stack-utils "^2.0.3"
-
-jest-cli@^29.7.0:
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995"
- integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==
- dependencies:
- "@jest/core" "^29.7.0"
- "@jest/test-result" "^29.7.0"
- "@jest/types" "^29.6.3"
- chalk "^4.0.0"
- create-jest "^29.7.0"
- exit "^0.1.2"
- import-local "^3.0.2"
- jest-config "^29.7.0"
- jest-util "^29.7.0"
- jest-validate "^29.7.0"
- yargs "^17.3.1"
-
-jest-config@^29.7.0:
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f"
- integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==
- dependencies:
- "@babel/core" "^7.11.6"
- "@jest/test-sequencer" "^29.7.0"
- "@jest/types" "^29.6.3"
- babel-jest "^29.7.0"
- chalk "^4.0.0"
- ci-info "^3.2.0"
- deepmerge "^4.2.2"
- glob "^7.1.3"
- graceful-fs "^4.2.9"
- jest-circus "^29.7.0"
- jest-environment-node "^29.7.0"
- jest-get-type "^29.6.3"
- jest-regex-util "^29.6.3"
- jest-resolve "^29.7.0"
- jest-runner "^29.7.0"
- jest-util "^29.7.0"
- jest-validate "^29.7.0"
- micromatch "^4.0.4"
- parse-json "^5.2.0"
- pretty-format "^29.7.0"
- slash "^3.0.0"
- strip-json-comments "^3.1.1"
-
-jest-diff@^29.7.0:
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a"
- integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==
- dependencies:
- chalk "^4.0.0"
- diff-sequences "^29.6.3"
- jest-get-type "^29.6.3"
- pretty-format "^29.7.0"
-
-jest-docblock@^29.7.0:
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a"
- integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==
- dependencies:
- detect-newline "^3.0.0"
-
-jest-each@^29.7.0:
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1"
- integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==
- dependencies:
- "@jest/types" "^29.6.3"
- chalk "^4.0.0"
- jest-get-type "^29.6.3"
- jest-util "^29.7.0"
- pretty-format "^29.7.0"
-
-jest-environment-node@^29.7.0:
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376"
- integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==
- dependencies:
- "@jest/environment" "^29.7.0"
- "@jest/fake-timers" "^29.7.0"
- "@jest/types" "^29.6.3"
- "@types/node" "*"
- jest-mock "^29.7.0"
- jest-util "^29.7.0"
-
-jest-get-type@^29.6.3:
- version "29.6.3"
- resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1"
- integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==
-
-jest-haste-map@^29.7.0:
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104"
- integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==
- dependencies:
- "@jest/types" "^29.6.3"
- "@types/graceful-fs" "^4.1.3"
- "@types/node" "*"
- anymatch "^3.0.3"
- fb-watchman "^2.0.0"
- graceful-fs "^4.2.9"
- jest-regex-util "^29.6.3"
- jest-util "^29.7.0"
- jest-worker "^29.7.0"
- micromatch "^4.0.4"
- walker "^1.0.8"
- optionalDependencies:
- fsevents "^2.3.2"
-
-jest-leak-detector@^29.7.0:
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728"
- integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==
- dependencies:
- jest-get-type "^29.6.3"
- pretty-format "^29.7.0"
-
-jest-matcher-utils@^29.7.0:
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12"
- integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==
- dependencies:
- chalk "^4.0.0"
- jest-diff "^29.7.0"
- jest-get-type "^29.6.3"
- pretty-format "^29.7.0"
-
-jest-message-util@^29.7.0:
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3"
- integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==
- dependencies:
- "@babel/code-frame" "^7.12.13"
- "@jest/types" "^29.6.3"
- "@types/stack-utils" "^2.0.0"
- chalk "^4.0.0"
- graceful-fs "^4.2.9"
- micromatch "^4.0.4"
- pretty-format "^29.7.0"
- slash "^3.0.0"
- stack-utils "^2.0.3"
-
-jest-mock@^29.7.0:
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347"
- integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==
- dependencies:
- "@jest/types" "^29.6.3"
- "@types/node" "*"
- jest-util "^29.7.0"
-
-jest-pnp-resolver@^1.2.2:
- version "1.2.3"
- resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e"
- integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==
-
-jest-regex-util@^29.6.3:
- version "29.6.3"
- resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52"
- integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==
-
-jest-resolve-dependencies@^29.7.0:
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428"
- integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==
- dependencies:
- jest-regex-util "^29.6.3"
- jest-snapshot "^29.7.0"
-
-jest-resolve@^29.7.0:
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30"
- integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==
- dependencies:
- chalk "^4.0.0"
- graceful-fs "^4.2.9"
- jest-haste-map "^29.7.0"
- jest-pnp-resolver "^1.2.2"
- jest-util "^29.7.0"
- jest-validate "^29.7.0"
- resolve "^1.20.0"
- resolve.exports "^2.0.0"
- slash "^3.0.0"
-
-jest-runner@^29.7.0:
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e"
- integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==
- dependencies:
- "@jest/console" "^29.7.0"
- "@jest/environment" "^29.7.0"
- "@jest/test-result" "^29.7.0"
- "@jest/transform" "^29.7.0"
- "@jest/types" "^29.6.3"
- "@types/node" "*"
- chalk "^4.0.0"
- emittery "^0.13.1"
- graceful-fs "^4.2.9"
- jest-docblock "^29.7.0"
- jest-environment-node "^29.7.0"
- jest-haste-map "^29.7.0"
- jest-leak-detector "^29.7.0"
- jest-message-util "^29.7.0"
- jest-resolve "^29.7.0"
- jest-runtime "^29.7.0"
- jest-util "^29.7.0"
- jest-watcher "^29.7.0"
- jest-worker "^29.7.0"
- p-limit "^3.1.0"
- source-map-support "0.5.13"
-
-jest-runtime@^29.7.0:
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817"
- integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==
- dependencies:
- "@jest/environment" "^29.7.0"
- "@jest/fake-timers" "^29.7.0"
- "@jest/globals" "^29.7.0"
- "@jest/source-map" "^29.6.3"
- "@jest/test-result" "^29.7.0"
- "@jest/transform" "^29.7.0"
- "@jest/types" "^29.6.3"
- "@types/node" "*"
- chalk "^4.0.0"
- cjs-module-lexer "^1.0.0"
- collect-v8-coverage "^1.0.0"
- glob "^7.1.3"
- graceful-fs "^4.2.9"
- jest-haste-map "^29.7.0"
- jest-message-util "^29.7.0"
- jest-mock "^29.7.0"
- jest-regex-util "^29.6.3"
- jest-resolve "^29.7.0"
- jest-snapshot "^29.7.0"
- jest-util "^29.7.0"
- slash "^3.0.0"
- strip-bom "^4.0.0"
-
-jest-snapshot@^29.7.0:
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5"
- integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==
- dependencies:
- "@babel/core" "^7.11.6"
- "@babel/generator" "^7.7.2"
- "@babel/plugin-syntax-jsx" "^7.7.2"
- "@babel/plugin-syntax-typescript" "^7.7.2"
- "@babel/types" "^7.3.3"
- "@jest/expect-utils" "^29.7.0"
- "@jest/transform" "^29.7.0"
- "@jest/types" "^29.6.3"
- babel-preset-current-node-syntax "^1.0.0"
- chalk "^4.0.0"
- expect "^29.7.0"
- graceful-fs "^4.2.9"
- jest-diff "^29.7.0"
- jest-get-type "^29.6.3"
- jest-matcher-utils "^29.7.0"
- jest-message-util "^29.7.0"
- jest-util "^29.7.0"
- natural-compare "^1.4.0"
- pretty-format "^29.7.0"
- semver "^7.5.3"
-
-jest-util@^29.7.0:
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc"
- integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==
- dependencies:
- "@jest/types" "^29.6.3"
- "@types/node" "*"
- chalk "^4.0.0"
- ci-info "^3.2.0"
- graceful-fs "^4.2.9"
- picomatch "^2.2.3"
-
-jest-validate@^29.7.0:
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c"
- integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==
- dependencies:
- "@jest/types" "^29.6.3"
- camelcase "^6.2.0"
- chalk "^4.0.0"
- jest-get-type "^29.6.3"
- leven "^3.1.0"
- pretty-format "^29.7.0"
-
-jest-watcher@^29.7.0:
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2"
- integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==
- dependencies:
- "@jest/test-result" "^29.7.0"
- "@jest/types" "^29.6.3"
- "@types/node" "*"
- ansi-escapes "^4.2.1"
- chalk "^4.0.0"
- emittery "^0.13.1"
- jest-util "^29.7.0"
- string-length "^4.0.1"
-
-jest-worker@^29.7.0:
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a"
- integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==
- dependencies:
- "@types/node" "*"
- jest-util "^29.7.0"
- merge-stream "^2.0.0"
- supports-color "^8.0.0"
-
-jest@^29.7.0:
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613"
- integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==
- dependencies:
- "@jest/core" "^29.7.0"
- "@jest/types" "^29.6.3"
- import-local "^3.0.2"
- jest-cli "^29.7.0"
-
-jiti@^2.4.2:
- version "2.4.2"
- resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.4.2.tgz#d19b7732ebb6116b06e2038da74a55366faef560"
- integrity sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==
-
-"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
- integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
-
-js-yaml@^3.13.1:
- version "3.14.1"
- resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537"
- integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==
- dependencies:
- argparse "^1.0.7"
- esprima "^4.0.0"
-
-js-yaml@^4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
- integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
- dependencies:
- argparse "^2.0.1"
-
-jsesc@^3.0.2:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d"
- integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==
-
-json-buffer@3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
- integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
-
-json-parse-even-better-errors@^2.3.0:
- version "2.3.1"
- resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
- integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
-
-json-schema-traverse@^0.4.1:
- version "0.4.1"
- resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
- integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
-
-json-stable-stringify-without-jsonify@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
- integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
-
-json5@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593"
- integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==
- dependencies:
- minimist "^1.2.0"
-
-json5@^2.2.3:
- version "2.2.3"
- resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
- integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
-
-"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.5:
- version "3.3.5"
- resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a"
- integrity sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==
- dependencies:
- array-includes "^3.1.6"
- array.prototype.flat "^1.3.1"
- object.assign "^4.1.4"
- object.values "^1.1.6"
-
-keyv@^4.5.4:
- version "4.5.4"
- resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
- integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==
- dependencies:
- json-buffer "3.0.1"
-
-kleur@^3.0.3:
- version "3.0.3"
- resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
- integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
-
-language-subtag-registry@^0.3.20:
- version "0.3.23"
- resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz#23529e04d9e3b74679d70142df3fd2eb6ec572e7"
- integrity sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==
-
-language-tags@^1.0.9:
- version "1.0.9"
- resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.9.tgz#1ffdcd0ec0fafb4b1be7f8b11f306ad0f9c08777"
- integrity sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==
- dependencies:
- language-subtag-registry "^0.3.20"
-
-leven@^3.1.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
- integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==
-
-levn@^0.4.1:
- version "0.4.1"
- resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
- integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==
- dependencies:
- prelude-ls "^1.2.1"
- type-check "~0.4.0"
-
-lightningcss-darwin-arm64@1.29.2:
- version "1.29.2"
- resolved "https://registry.yarnpkg.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.29.2.tgz#6ceff38b01134af48e859394e1ca21e5d49faae6"
- integrity sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==
-
-lightningcss-darwin-x64@1.29.2:
- version "1.29.2"
- resolved "https://registry.yarnpkg.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.29.2.tgz#891b6f9e57682d794223c33463ca66d3af3fb038"
- integrity sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==
-
-lightningcss-freebsd-x64@1.29.2:
- version "1.29.2"
- resolved "https://registry.yarnpkg.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.29.2.tgz#8a95f9ab73b2b2b0beefe1599fafa8b058938495"
- integrity sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg==
-
-lightningcss-linux-arm-gnueabihf@1.29.2:
- version "1.29.2"
- resolved "https://registry.yarnpkg.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.29.2.tgz#5c60bbf92b39d7ed51e363f7b98a7111bf5914a1"
- integrity sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg==
-
-lightningcss-linux-arm64-gnu@1.29.2:
- version "1.29.2"
- resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.29.2.tgz#e73d7608c4cce034c3654e5e8b53be74846224de"
- integrity sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==
-
-lightningcss-linux-arm64-musl@1.29.2:
- version "1.29.2"
- resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.29.2.tgz#a95a18d5a909831c092e0a8d2de4b9ac1a8db151"
- integrity sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==
-
-lightningcss-linux-x64-gnu@1.29.2:
- version "1.29.2"
- resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.29.2.tgz#551ca07e565394928642edee92acc042e546cb78"
- integrity sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==
-
-lightningcss-linux-x64-musl@1.29.2:
- version "1.29.2"
- resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.29.2.tgz#2fd164554340831bce50285b57101817850dd258"
- integrity sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==
-
-lightningcss-win32-arm64-msvc@1.29.2:
- version "1.29.2"
- resolved "https://registry.yarnpkg.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.29.2.tgz#da43ea49fafc5d2de38e016f1a8539d5eed98318"
- integrity sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==
-
-lightningcss-win32-x64-msvc@1.29.2:
- version "1.29.2"
- resolved "https://registry.yarnpkg.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.29.2.tgz#ddefaa099a39b725b2f5bbdcb9fc718435cc9797"
- integrity sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==
-
-lightningcss@1.29.2:
- version "1.29.2"
- resolved "https://registry.yarnpkg.com/lightningcss/-/lightningcss-1.29.2.tgz#f5f0fd6e63292a232697e6fe709da5b47624def3"
- integrity sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==
- dependencies:
- detect-libc "^2.0.3"
- optionalDependencies:
- lightningcss-darwin-arm64 "1.29.2"
- lightningcss-darwin-x64 "1.29.2"
- lightningcss-freebsd-x64 "1.29.2"
- lightningcss-linux-arm-gnueabihf "1.29.2"
- lightningcss-linux-arm64-gnu "1.29.2"
- lightningcss-linux-arm64-musl "1.29.2"
- lightningcss-linux-x64-gnu "1.29.2"
- lightningcss-linux-x64-musl "1.29.2"
- lightningcss-win32-arm64-msvc "1.29.2"
- lightningcss-win32-x64-msvc "1.29.2"
-
-lines-and-columns@^1.1.6:
- version "1.2.4"
- resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
- integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
-
-locate-path@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
- integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==
- dependencies:
- p-locate "^4.1.0"
-
-locate-path@^6.0.0:
- version "6.0.0"
- resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
- integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
- dependencies:
- p-locate "^5.0.0"
-
-lodash.merge@^4.6.2:
- version "4.6.2"
- resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
- integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
-
-loose-envify@^1.4.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
- integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
- dependencies:
- js-tokens "^3.0.0 || ^4.0.0"
-
-lru-cache@^5.1.1:
- version "5.1.1"
- resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
- integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==
- dependencies:
- yallist "^3.0.2"
-
-lucide-react@^0.510.0:
- version "0.510.0"
- resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.510.0.tgz#933b19d893b30ac5cb355e4b91eeefc13088caa3"
- integrity sha512-p8SQRAMVh7NhsAIETokSqDrc5CHnDLbV29mMnzaXx+Vc/hnqQzwI2r0FMWCcoTXnbw2KEjy48xwpGdEL+ck06Q==
-
-lz-string@^1.5.0:
- version "1.5.0"
- resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941"
- integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==
-
-magic-string@^0.30.17:
- version "0.30.17"
- resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453"
- integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==
- dependencies:
- "@jridgewell/sourcemap-codec" "^1.5.0"
-
-make-dir@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e"
- integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==
- dependencies:
- semver "^7.5.3"
-
-makeerror@1.0.12:
- version "1.0.12"
- resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a"
- integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==
- dependencies:
- tmpl "1.0.5"
-
-math-intrinsics@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9"
- integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==
-
-media-typer@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-1.1.0.tgz#6ab74b8f2d3320f2064b2a87a38e7931ff3a5561"
- integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==
-
-merge-descriptors@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz#ea922f660635a2249ee565e0449f951e6b603808"
- integrity sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==
-
-merge-stream@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
- integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
-
-merge2@^1.3.0:
- version "1.4.1"
- resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
- integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
-
-micromatch@^4.0.4, micromatch@^4.0.8:
- version "4.0.8"
- resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202"
- integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
- dependencies:
- braces "^3.0.3"
- picomatch "^2.3.1"
-
-mime-db@^1.54.0:
- version "1.54.0"
- resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5"
- integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==
-
-mime-types@^3.0.0, mime-types@^3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-3.0.1.tgz#b1d94d6997a9b32fd69ebaed0db73de8acb519ce"
- integrity sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==
- dependencies:
- mime-db "^1.54.0"
-
-mimic-fn@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
- integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
-
-minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2:
- version "3.1.2"
- resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
- integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
- dependencies:
- brace-expansion "^1.1.7"
-
-minimatch@^9.0.4:
- version "9.0.5"
- resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5"
- integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==
- dependencies:
- brace-expansion "^2.0.1"
-
-minimist@^1.2.0, minimist@^1.2.6:
- version "1.2.8"
- resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
- integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
-
-minipass@^7.0.4, minipass@^7.1.2:
- version "7.1.2"
- resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707"
- integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
-
-minizlib@^3.0.1:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-3.0.2.tgz#f33d638eb279f664439aa38dc5f91607468cb574"
- integrity sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==
- dependencies:
- minipass "^7.1.2"
-
-mkdirp@^3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50"
- integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==
-
-ms@^2.1.1, ms@^2.1.3:
- version "2.1.3"
- resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
- integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
-
-nanoid@^3.3.6, nanoid@^3.3.8:
- version "3.3.11"
- resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b"
- integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==
-
-napi-postinstall@^0.2.2:
- version "0.2.4"
- resolved "https://registry.yarnpkg.com/napi-postinstall/-/napi-postinstall-0.2.4.tgz#419697d0288cb524623e422f919624f22a5e4028"
- integrity sha512-ZEzHJwBhZ8qQSbknHqYcdtQVr8zUgGyM/q6h6qAyhtyVMNrSgDhrC4disf03dYW0e+czXyLnZINnCTEkWy0eJg==
-
-natural-compare@^1.4.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
- integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
-
-negotiator@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a"
- integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==
-
-next@15.3.2:
- version "15.3.2"
- resolved "https://registry.yarnpkg.com/next/-/next-15.3.2.tgz#97510629e38a058dd154782a5c2ec9c9ab94d0d8"
- integrity sha512-CA3BatMyHkxZ48sgOCLdVHjFU36N7TF1HhqAHLFOkV6buwZnvMI84Cug8xD56B9mCuKrqXnLn94417GrZ/jjCQ==
- dependencies:
- "@next/env" "15.3.2"
- "@swc/counter" "0.1.3"
- "@swc/helpers" "0.5.15"
- busboy "1.6.0"
- caniuse-lite "^1.0.30001579"
- postcss "8.4.31"
- styled-jsx "5.1.6"
- optionalDependencies:
- "@next/swc-darwin-arm64" "15.3.2"
- "@next/swc-darwin-x64" "15.3.2"
- "@next/swc-linux-arm64-gnu" "15.3.2"
- "@next/swc-linux-arm64-musl" "15.3.2"
- "@next/swc-linux-x64-gnu" "15.3.2"
- "@next/swc-linux-x64-musl" "15.3.2"
- "@next/swc-win32-arm64-msvc" "15.3.2"
- "@next/swc-win32-x64-msvc" "15.3.2"
- sharp "^0.34.1"
-
-node-int64@^0.4.0:
- version "0.4.0"
- resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
- integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==
-
-node-releases@^2.0.19:
- version "2.0.19"
- resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314"
- integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==
-
-normalize-path@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
- integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
-
-npm-run-path@^4.0.1:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
- integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==
- dependencies:
- path-key "^3.0.0"
-
-object-assign@^4, object-assign@^4.1.1:
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
- integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
-
-object-inspect@^1.13.3:
- version "1.13.4"
- resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213"
- integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==
-
-object-keys@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
- integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
-
-object.assign@^4.1.4, object.assign@^4.1.7:
- version "4.1.7"
- resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.7.tgz#8c14ca1a424c6a561b0bb2a22f66f5049a945d3d"
- integrity sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==
- dependencies:
- call-bind "^1.0.8"
- call-bound "^1.0.3"
- define-properties "^1.2.1"
- es-object-atoms "^1.0.0"
- has-symbols "^1.1.0"
- object-keys "^1.1.1"
-
-object.entries@^1.1.9:
- version "1.1.9"
- resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.9.tgz#e4770a6a1444afb61bd39f984018b5bede25f8b3"
- integrity sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==
- dependencies:
- call-bind "^1.0.8"
- call-bound "^1.0.4"
- define-properties "^1.2.1"
- es-object-atoms "^1.1.1"
-
-object.fromentries@^2.0.8:
- version "2.0.8"
- resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65"
- integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==
- dependencies:
- call-bind "^1.0.7"
- define-properties "^1.2.1"
- es-abstract "^1.23.2"
- es-object-atoms "^1.0.0"
-
-object.groupby@^1.0.3:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.3.tgz#9b125c36238129f6f7b61954a1e7176148d5002e"
- integrity sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==
- dependencies:
- call-bind "^1.0.7"
- define-properties "^1.2.1"
- es-abstract "^1.23.2"
-
-object.values@^1.1.6, object.values@^1.2.0, object.values@^1.2.1:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.1.tgz#deed520a50809ff7f75a7cfd4bc64c7a038c6216"
- integrity sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==
- dependencies:
- call-bind "^1.0.8"
- call-bound "^1.0.3"
- define-properties "^1.2.1"
- es-object-atoms "^1.0.0"
-
-on-finished@^2.4.1:
- version "2.4.1"
- resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
- integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
- dependencies:
- ee-first "1.1.1"
-
-once@^1.3.0, once@^1.4.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
- integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
- dependencies:
- wrappy "1"
-
-onetime@^5.1.2:
- version "5.1.2"
- resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
- integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==
- dependencies:
- mimic-fn "^2.1.0"
-
-optionator@^0.9.3:
- version "0.9.4"
- resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734"
- integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==
- dependencies:
- deep-is "^0.1.3"
- fast-levenshtein "^2.0.6"
- levn "^0.4.1"
- prelude-ls "^1.2.1"
- type-check "^0.4.0"
- word-wrap "^1.2.5"
-
-own-keys@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/own-keys/-/own-keys-1.0.1.tgz#e4006910a2bf913585289676eebd6f390cf51358"
- integrity sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==
- dependencies:
- get-intrinsic "^1.2.6"
- object-keys "^1.1.1"
- safe-push-apply "^1.0.0"
-
-p-limit@^2.2.0:
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
- integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
- dependencies:
- p-try "^2.0.0"
-
-p-limit@^3.0.2, p-limit@^3.1.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
- integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
- dependencies:
- yocto-queue "^0.1.0"
-
-p-locate@^4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
- integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==
- dependencies:
- p-limit "^2.2.0"
-
-p-locate@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
- integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
- dependencies:
- p-limit "^3.0.2"
-
-p-try@^2.0.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
- integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
-
-parent-module@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
- integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
- dependencies:
- callsites "^3.0.0"
-
-parse-json@^5.2.0:
- version "5.2.0"
- resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
- integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==
- dependencies:
- "@babel/code-frame" "^7.0.0"
- error-ex "^1.3.1"
- json-parse-even-better-errors "^2.3.0"
- lines-and-columns "^1.1.6"
-
-parseurl@^1.3.3:
- version "1.3.3"
- resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
- integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
-
-path-exists@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
- integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
-
-path-is-absolute@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
- integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
-
-path-key@^3.0.0, path-key@^3.1.0:
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
- integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
-
-path-parse@^1.0.7:
- version "1.0.7"
- resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
- integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
-
-path-to-regexp@^8.0.0:
- version "8.2.0"
- resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.2.0.tgz#73990cc29e57a3ff2a0d914095156df5db79e8b4"
- integrity sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==
-
-picocolors@^1.0.0, picocolors@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
- integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
-
-picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1:
- version "2.3.1"
- resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
- integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
-
-picomatch@^4.0.2:
- version "4.0.2"
- resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab"
- integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==
-
-pirates@^4.0.4:
- version "4.0.7"
- resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22"
- integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==
-
-pkce-challenge@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/pkce-challenge/-/pkce-challenge-5.0.0.tgz#c3a405cb49e272094a38e890a2b51da0228c4d97"
- integrity sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==
-
-pkg-dir@^4.2.0:
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
- integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==
- dependencies:
- find-up "^4.0.0"
-
-possible-typed-array-names@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae"
- integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==
-
-postcss@8.4.31:
- version "8.4.31"
- resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d"
- integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==
- dependencies:
- nanoid "^3.3.6"
- picocolors "^1.0.0"
- source-map-js "^1.0.2"
-
-postcss@^8.4.41, postcss@^8.5.3:
- version "8.5.3"
- resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.3.tgz#1463b6f1c7fb16fe258736cba29a2de35237eafb"
- integrity sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==
- dependencies:
- nanoid "^3.3.8"
- picocolors "^1.1.1"
- source-map-js "^1.2.1"
-
-prelude-ls@^1.2.1:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
- integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
-
-pretty-format@^27.0.2:
- version "27.5.1"
- resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e"
- integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==
- dependencies:
- ansi-regex "^5.0.1"
- ansi-styles "^5.0.0"
- react-is "^17.0.1"
-
-pretty-format@^29.7.0:
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812"
- integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==
- dependencies:
- "@jest/schemas" "^29.6.3"
- ansi-styles "^5.0.0"
- react-is "^18.0.0"
-
-prompts@^2.0.1:
- version "2.4.2"
- resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069"
- integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==
- dependencies:
- kleur "^3.0.3"
- sisteransi "^1.0.5"
-
-prop-types@^15.8.1:
- version "15.8.1"
- resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
- integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
- dependencies:
- loose-envify "^1.4.0"
- object-assign "^4.1.1"
- react-is "^16.13.1"
-
-proxy-addr@^2.0.7:
- version "2.0.7"
- resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
- integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==
- dependencies:
- forwarded "0.2.0"
- ipaddr.js "1.9.1"
-
-punycode@^2.1.0:
- version "2.3.1"
- resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
- integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
-
-pure-rand@^6.0.0:
- version "6.1.0"
- resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2"
- integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==
-
-qs@^6.14.0:
- version "6.14.0"
- resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930"
- integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==
- dependencies:
- side-channel "^1.1.0"
-
-queue-microtask@^1.2.2:
- version "1.2.3"
- resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
- integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
-
-range-parser@^1.2.1:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
- integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
-
-raw-body@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-3.0.0.tgz#25b3476f07a51600619dae3fe82ddc28a36e5e0f"
- integrity sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==
- dependencies:
- bytes "3.1.2"
- http-errors "2.0.0"
- iconv-lite "0.6.3"
- unpipe "1.0.0"
-
-react-hook-form@^7.56.3:
- version "7.56.3"
- resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.56.3.tgz#c459f59fead379db30113951152e13db386cc015"
- integrity sha512-IK18V6GVbab4TAo1/cz3kqajxbDPGofdF0w7VHdCo0Nt8PrPlOZcuuDq9YYIV1BtjcX78x0XsldbQRQnQXWXmw==
-
-react-is@^16.13.1:
- version "16.13.1"
- resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
- integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
-
-react-is@^17.0.1:
- version "17.0.2"
- resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
- integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
-
-react-is@^18.0.0:
- version "18.3.1"
- resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e"
- integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
-
-react@^19.0.0:
- version "19.1.0"
- resolved "https://registry.yarnpkg.com/react/-/react-19.1.0.tgz#926864b6c48da7627f004795d6cce50e90793b75"
- integrity sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==
-
-reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9:
- version "1.0.10"
- resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9"
- integrity sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==
- dependencies:
- call-bind "^1.0.8"
- define-properties "^1.2.1"
- es-abstract "^1.23.9"
- es-errors "^1.3.0"
- es-object-atoms "^1.0.0"
- get-intrinsic "^1.2.7"
- get-proto "^1.0.1"
- which-builtin-type "^1.2.1"
-
-regexp.prototype.flags@^1.5.3:
- version "1.5.4"
- resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz#1ad6c62d44a259007e55b3970e00f746efbcaa19"
- integrity sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==
- dependencies:
- call-bind "^1.0.8"
- define-properties "^1.2.1"
- es-errors "^1.3.0"
- get-proto "^1.0.1"
- gopd "^1.2.0"
- set-function-name "^2.0.2"
-
-require-directory@^2.1.1:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
- integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
-
-resolve-cwd@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"
- integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==
- dependencies:
- resolve-from "^5.0.0"
-
-resolve-from@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
- integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
-
-resolve-from@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
- integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
-
-resolve-pkg-maps@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f"
- integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==
-
-resolve.exports@^2.0.0:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.3.tgz#41955e6f1b4013b7586f873749a635dea07ebe3f"
- integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==
-
-resolve@^1.20.0, resolve@^1.22.4:
- version "1.22.10"
- resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39"
- integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==
- dependencies:
- is-core-module "^2.16.0"
- path-parse "^1.0.7"
- supports-preserve-symlinks-flag "^1.0.0"
-
-resolve@^2.0.0-next.5:
- version "2.0.0-next.5"
- resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.5.tgz#6b0ec3107e671e52b68cd068ef327173b90dc03c"
- integrity sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==
- dependencies:
- is-core-module "^2.13.0"
- path-parse "^1.0.7"
- supports-preserve-symlinks-flag "^1.0.0"
-
-reusify@^1.0.4:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f"
- integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==
-
-router@^2.2.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/router/-/router-2.2.0.tgz#019be620b711c87641167cc79b99090f00b146ef"
- integrity sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==
- dependencies:
- debug "^4.4.0"
- depd "^2.0.0"
- is-promise "^4.0.0"
- parseurl "^1.3.3"
- path-to-regexp "^8.0.0"
-
-run-parallel@^1.1.9:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
- integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==
- dependencies:
- queue-microtask "^1.2.2"
-
-safe-array-concat@^1.1.3:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz#c9e54ec4f603b0bbb8e7e5007a5ee7aecd1538c3"
- integrity sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==
- dependencies:
- call-bind "^1.0.8"
- call-bound "^1.0.2"
- get-intrinsic "^1.2.6"
- has-symbols "^1.1.0"
- isarray "^2.0.5"
-
-safe-buffer@5.2.1:
- version "5.2.1"
- resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
- integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
-
-safe-push-apply@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/safe-push-apply/-/safe-push-apply-1.0.0.tgz#01850e981c1602d398c85081f360e4e6d03d27f5"
- integrity sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==
- dependencies:
- es-errors "^1.3.0"
- isarray "^2.0.5"
-
-safe-regex-test@^1.0.3, safe-regex-test@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz#7f87dfb67a3150782eaaf18583ff5d1711ac10c1"
- integrity sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==
- dependencies:
- call-bound "^1.0.2"
- es-errors "^1.3.0"
- is-regex "^1.2.1"
-
-"safer-buffer@>= 2.1.2 < 3.0.0":
- version "2.1.2"
- resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
- integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
-
-semver@^6.3.0, semver@^6.3.1:
- version "6.3.1"
- resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
- integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
-
-semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.7.1:
- version "7.7.2"
- resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58"
- integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==
-
-send@^1.1.0, send@^1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/send/-/send-1.2.0.tgz#32a7554fb777b831dfa828370f773a3808d37212"
- integrity sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==
- dependencies:
- debug "^4.3.5"
- encodeurl "^2.0.0"
- escape-html "^1.0.3"
- etag "^1.8.1"
- fresh "^2.0.0"
- http-errors "^2.0.0"
- mime-types "^3.0.1"
- ms "^2.1.3"
- on-finished "^2.4.1"
- range-parser "^1.2.1"
- statuses "^2.0.1"
-
-serve-static@^2.2.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-2.2.0.tgz#9c02564ee259bdd2251b82d659a2e7e1938d66f9"
- integrity sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==
- dependencies:
- encodeurl "^2.0.0"
- escape-html "^1.0.3"
- parseurl "^1.3.3"
- send "^1.2.0"
-
-set-function-length@^1.2.2:
- version "1.2.2"
- resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449"
- integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==
- dependencies:
- define-data-property "^1.1.4"
- es-errors "^1.3.0"
- function-bind "^1.1.2"
- get-intrinsic "^1.2.4"
- gopd "^1.0.1"
- has-property-descriptors "^1.0.2"
-
-set-function-name@^2.0.2:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985"
- integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==
- dependencies:
- define-data-property "^1.1.4"
- es-errors "^1.3.0"
- functions-have-names "^1.2.3"
- has-property-descriptors "^1.0.2"
-
-set-proto@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/set-proto/-/set-proto-1.0.0.tgz#0760dbcff30b2d7e801fd6e19983e56da337565e"
- integrity sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==
- dependencies:
- dunder-proto "^1.0.1"
- es-errors "^1.3.0"
- es-object-atoms "^1.0.0"
-
-setprototypeof@1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
- integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
-
-sharp@^0.34.1:
- version "0.34.1"
- resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.34.1.tgz#e5922894b0cc7ddf159eeabc6d5668e4e8b11d61"
- integrity sha512-1j0w61+eVxu7DawFJtnfYcvSv6qPFvfTaqzTQ2BLknVhHTwGS8sc63ZBF4rzkWMBVKybo4S5OBtDdZahh2A1xg==
- dependencies:
- color "^4.2.3"
- detect-libc "^2.0.3"
- semver "^7.7.1"
- optionalDependencies:
- "@img/sharp-darwin-arm64" "0.34.1"
- "@img/sharp-darwin-x64" "0.34.1"
- "@img/sharp-libvips-darwin-arm64" "1.1.0"
- "@img/sharp-libvips-darwin-x64" "1.1.0"
- "@img/sharp-libvips-linux-arm" "1.1.0"
- "@img/sharp-libvips-linux-arm64" "1.1.0"
- "@img/sharp-libvips-linux-ppc64" "1.1.0"
- "@img/sharp-libvips-linux-s390x" "1.1.0"
- "@img/sharp-libvips-linux-x64" "1.1.0"
- "@img/sharp-libvips-linuxmusl-arm64" "1.1.0"
- "@img/sharp-libvips-linuxmusl-x64" "1.1.0"
- "@img/sharp-linux-arm" "0.34.1"
- "@img/sharp-linux-arm64" "0.34.1"
- "@img/sharp-linux-s390x" "0.34.1"
- "@img/sharp-linux-x64" "0.34.1"
- "@img/sharp-linuxmusl-arm64" "0.34.1"
- "@img/sharp-linuxmusl-x64" "0.34.1"
- "@img/sharp-wasm32" "0.34.1"
- "@img/sharp-win32-ia32" "0.34.1"
- "@img/sharp-win32-x64" "0.34.1"
-
-shebang-command@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
- integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
- dependencies:
- shebang-regex "^3.0.0"
-
-shebang-regex@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
- integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
-
-side-channel-list@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad"
- integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==
- dependencies:
- es-errors "^1.3.0"
- object-inspect "^1.13.3"
-
-side-channel-map@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42"
- integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==
- dependencies:
- call-bound "^1.0.2"
- es-errors "^1.3.0"
- get-intrinsic "^1.2.5"
- object-inspect "^1.13.3"
-
-side-channel-weakmap@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea"
- integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==
- dependencies:
- call-bound "^1.0.2"
- es-errors "^1.3.0"
- get-intrinsic "^1.2.5"
- object-inspect "^1.13.3"
- side-channel-map "^1.0.1"
-
-side-channel@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9"
- integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==
- dependencies:
- es-errors "^1.3.0"
- object-inspect "^1.13.3"
- side-channel-list "^1.0.0"
- side-channel-map "^1.0.1"
- side-channel-weakmap "^1.0.2"
-
-signal-exit@^3.0.3, signal-exit@^3.0.7:
- version "3.0.7"
- resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
- integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
-
-simple-swizzle@^0.2.2:
- version "0.2.2"
- resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a"
- integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==
- dependencies:
- is-arrayish "^0.3.1"
-
-sisteransi@^1.0.5:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
- integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==
-
-slash@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
- integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
-
-source-map-js@^1.0.2, source-map-js@^1.2.1:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
- integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
-
-source-map-support@0.5.13:
- version "0.5.13"
- resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932"
- integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==
- dependencies:
- buffer-from "^1.0.0"
- source-map "^0.6.0"
-
-source-map@^0.6.0, source-map@^0.6.1:
- version "0.6.1"
- resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
- integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
-
-sprintf-js@~1.0.2:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
- integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==
-
-stable-hash@^0.0.5:
- version "0.0.5"
- resolved "https://registry.yarnpkg.com/stable-hash/-/stable-hash-0.0.5.tgz#94e8837aaeac5b4d0f631d2972adef2924b40269"
- integrity sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==
-
-stack-utils@^2.0.3:
- version "2.0.6"
- resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f"
- integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==
- dependencies:
- escape-string-regexp "^2.0.0"
-
-statuses@2.0.1, statuses@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
- integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
-
-streamsearch@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
- integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
-
-string-length@^4.0.1:
- version "4.0.2"
- resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a"
- integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==
- dependencies:
- char-regex "^1.0.2"
- strip-ansi "^6.0.0"
-
-string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
- version "4.2.3"
- resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
- integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
- dependencies:
- emoji-regex "^8.0.0"
- is-fullwidth-code-point "^3.0.0"
- strip-ansi "^6.0.1"
-
-string.prototype.includes@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz#eceef21283640761a81dbe16d6c7171a4edf7d92"
- integrity sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==
- dependencies:
- call-bind "^1.0.7"
- define-properties "^1.2.1"
- es-abstract "^1.23.3"
-
-string.prototype.matchall@^4.0.12:
- version "4.0.12"
- resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz#6c88740e49ad4956b1332a911e949583a275d4c0"
- integrity sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==
- dependencies:
- call-bind "^1.0.8"
- call-bound "^1.0.3"
- define-properties "^1.2.1"
- es-abstract "^1.23.6"
- es-errors "^1.3.0"
- es-object-atoms "^1.0.0"
- get-intrinsic "^1.2.6"
- gopd "^1.2.0"
- has-symbols "^1.1.0"
- internal-slot "^1.1.0"
- regexp.prototype.flags "^1.5.3"
- set-function-name "^2.0.2"
- side-channel "^1.1.0"
-
-string.prototype.repeat@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz#e90872ee0308b29435aa26275f6e1b762daee01a"
- integrity sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==
- dependencies:
- define-properties "^1.1.3"
- es-abstract "^1.17.5"
-
-string.prototype.trim@^1.2.10:
- version "1.2.10"
- resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz#40b2dd5ee94c959b4dcfb1d65ce72e90da480c81"
- integrity sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==
- dependencies:
- call-bind "^1.0.8"
- call-bound "^1.0.2"
- define-data-property "^1.1.4"
- define-properties "^1.2.1"
- es-abstract "^1.23.5"
- es-object-atoms "^1.0.0"
- has-property-descriptors "^1.0.2"
-
-string.prototype.trimend@^1.0.8, string.prototype.trimend@^1.0.9:
- version "1.0.9"
- resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz#62e2731272cd285041b36596054e9f66569b6942"
- integrity sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==
- dependencies:
- call-bind "^1.0.8"
- call-bound "^1.0.2"
- define-properties "^1.2.1"
- es-object-atoms "^1.0.0"
-
-string.prototype.trimstart@^1.0.8:
- version "1.0.8"
- resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde"
- integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==
- dependencies:
- call-bind "^1.0.7"
- define-properties "^1.2.1"
- es-object-atoms "^1.0.0"
-
-strip-ansi@^6.0.0, strip-ansi@^6.0.1:
- version "6.0.1"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
- integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
- dependencies:
- ansi-regex "^5.0.1"
-
-strip-bom@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
- integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==
-
-strip-bom@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878"
- integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==
-
-strip-final-newline@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
- integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
-
-strip-json-comments@^3.1.1:
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
- integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
-
-styled-jsx@5.1.6:
- version "5.1.6"
- resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.1.6.tgz#83b90c077e6c6a80f7f5e8781d0f311b2fe41499"
- integrity sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==
- dependencies:
- client-only "0.0.1"
-
-supports-color@^7.1.0:
- version "7.2.0"
- resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
- integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
- dependencies:
- has-flag "^4.0.0"
-
-supports-color@^8.0.0:
- version "8.1.1"
- resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
- integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
- dependencies:
- has-flag "^4.0.0"
-
-supports-preserve-symlinks-flag@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
- integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
-
-tailwind-merge@^3.3.0:
- version "3.3.0"
- resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-3.3.0.tgz#d9bb4f298801ef6232ced4f97e830fc79bc3d3e7"
- integrity sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ==
-
-tailwindcss@4.1.6, tailwindcss@^4.1.6:
- version "4.1.6"
- resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-4.1.6.tgz#ebe62de22b7d8a8c1f76bd3a07fc37c3fcc36503"
- integrity sha512-j0cGLTreM6u4OWzBeLBpycK0WIh8w7kSwcUsQZoGLHZ7xDTdM69lN64AgoIEEwFi0tnhs4wSykUa5YWxAzgFYg==
-
-tapable@^2.2.0:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
- integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==
-
-tar@^7.4.3:
- version "7.4.3"
- resolved "https://registry.yarnpkg.com/tar/-/tar-7.4.3.tgz#88bbe9286a3fcd900e94592cda7a22b192e80571"
- integrity sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==
- dependencies:
- "@isaacs/fs-minipass" "^4.0.0"
- chownr "^3.0.0"
- minipass "^7.1.2"
- minizlib "^3.0.1"
- mkdirp "^3.0.1"
- yallist "^5.0.0"
-
-test-exclude@^6.0.0:
- version "6.0.0"
- resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e"
- integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==
- dependencies:
- "@istanbuljs/schema" "^0.1.2"
- glob "^7.1.4"
- minimatch "^3.0.4"
-
-tinyglobby@^0.2.13:
- version "0.2.13"
- resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.13.tgz#a0e46515ce6cbcd65331537e57484af5a7b2ff7e"
- integrity sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==
- dependencies:
- fdir "^6.4.4"
- picomatch "^4.0.2"
-
-tmpl@1.0.5:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
- integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==
-
-to-regex-range@^5.0.1:
- version "5.0.1"
- resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
- integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
- dependencies:
- is-number "^7.0.0"
-
-toidentifier@1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
- integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
-
-ts-api-utils@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.1.0.tgz#595f7094e46eed364c13fd23e75f9513d29baf91"
- integrity sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==
-
-tsconfig-paths@^3.15.0:
- version "3.15.0"
- resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4"
- integrity sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==
- dependencies:
- "@types/json5" "^0.0.29"
- json5 "^1.0.2"
- minimist "^1.2.6"
- strip-bom "^3.0.0"
-
-tslib@^2.4.0, tslib@^2.8.0:
- version "2.8.1"
- resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
- integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
-
-tw-animate-css@^1.2.9:
- version "1.2.9"
- resolved "https://registry.yarnpkg.com/tw-animate-css/-/tw-animate-css-1.2.9.tgz#b25d5fb31fd3c3ec6d91c1d1842c0d7c0fdbd999"
- integrity sha512-9O4k1at9pMQff9EAcCEuy1UNO43JmaPQvq+0lwza9Y0BQ6LB38NiMj+qHqjoQf40355MX+gs6wtlR6H9WsSXFg==
-
-type-check@^0.4.0, type-check@~0.4.0:
- version "0.4.0"
- resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
- integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==
- dependencies:
- prelude-ls "^1.2.1"
-
-type-detect@4.0.8:
- version "4.0.8"
- resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
- integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
-
-type-fest@^0.21.3:
- version "0.21.3"
- resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37"
- integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==
-
-type-is@^2.0.0, type-is@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/type-is/-/type-is-2.0.1.tgz#64f6cf03f92fce4015c2b224793f6bdd4b068c97"
- integrity sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==
- dependencies:
- content-type "^1.0.5"
- media-typer "^1.1.0"
- mime-types "^3.0.0"
-
-typed-array-buffer@^1.0.3:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz#a72395450a4869ec033fd549371b47af3a2ee536"
- integrity sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==
- dependencies:
- call-bound "^1.0.3"
- es-errors "^1.3.0"
- is-typed-array "^1.1.14"
-
-typed-array-byte-length@^1.0.3:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz#8407a04f7d78684f3d252aa1a143d2b77b4160ce"
- integrity sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==
- dependencies:
- call-bind "^1.0.8"
- for-each "^0.3.3"
- gopd "^1.2.0"
- has-proto "^1.2.0"
- is-typed-array "^1.1.14"
-
-typed-array-byte-offset@^1.0.4:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz#ae3698b8ec91a8ab945016108aef00d5bff12355"
- integrity sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==
- dependencies:
- available-typed-arrays "^1.0.7"
- call-bind "^1.0.8"
- for-each "^0.3.3"
- gopd "^1.2.0"
- has-proto "^1.2.0"
- is-typed-array "^1.1.15"
- reflect.getprototypeof "^1.0.9"
-
-typed-array-length@^1.0.7:
- version "1.0.7"
- resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.7.tgz#ee4deff984b64be1e118b0de8c9c877d5ce73d3d"
- integrity sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==
- dependencies:
- call-bind "^1.0.7"
- for-each "^0.3.3"
- gopd "^1.0.1"
- is-typed-array "^1.1.13"
- possible-typed-array-names "^1.0.0"
- reflect.getprototypeof "^1.0.6"
-
-typescript@^5:
- version "5.8.3"
- resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e"
- integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==
-
-unbox-primitive@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz#8d9d2c9edeea8460c7f35033a88867944934d1e2"
- integrity sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==
- dependencies:
- call-bound "^1.0.3"
- has-bigints "^1.0.2"
- has-symbols "^1.1.0"
- which-boxed-primitive "^1.1.1"
-
-undici-types@~6.19.2:
- version "6.19.8"
- resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02"
- integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==
-
-undici-types@~6.21.0:
- version "6.21.0"
- resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb"
- integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==
-
-unpipe@1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
- integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
-
-unrs-resolver@^1.6.2:
- version "1.7.2"
- resolved "https://registry.yarnpkg.com/unrs-resolver/-/unrs-resolver-1.7.2.tgz#a6844bcb9006020b58e718c5522a4f4552632b6b"
- integrity sha512-BBKpaylOW8KbHsu378Zky/dGh4ckT/4NW/0SHRABdqRLcQJ2dAOjDo9g97p04sWflm0kqPqpUatxReNV/dqI5A==
- dependencies:
- napi-postinstall "^0.2.2"
- optionalDependencies:
- "@unrs/resolver-binding-darwin-arm64" "1.7.2"
- "@unrs/resolver-binding-darwin-x64" "1.7.2"
- "@unrs/resolver-binding-freebsd-x64" "1.7.2"
- "@unrs/resolver-binding-linux-arm-gnueabihf" "1.7.2"
- "@unrs/resolver-binding-linux-arm-musleabihf" "1.7.2"
- "@unrs/resolver-binding-linux-arm64-gnu" "1.7.2"
- "@unrs/resolver-binding-linux-arm64-musl" "1.7.2"
- "@unrs/resolver-binding-linux-ppc64-gnu" "1.7.2"
- "@unrs/resolver-binding-linux-riscv64-gnu" "1.7.2"
- "@unrs/resolver-binding-linux-riscv64-musl" "1.7.2"
- "@unrs/resolver-binding-linux-s390x-gnu" "1.7.2"
- "@unrs/resolver-binding-linux-x64-gnu" "1.7.2"
- "@unrs/resolver-binding-linux-x64-musl" "1.7.2"
- "@unrs/resolver-binding-wasm32-wasi" "1.7.2"
- "@unrs/resolver-binding-win32-arm64-msvc" "1.7.2"
- "@unrs/resolver-binding-win32-ia32-msvc" "1.7.2"
- "@unrs/resolver-binding-win32-x64-msvc" "1.7.2"
-
-update-browserslist-db@^1.1.3:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420"
- integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==
- dependencies:
- escalade "^3.2.0"
- picocolors "^1.1.1"
-
-uri-js@^4.2.2:
- version "4.4.1"
- resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
- integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
- dependencies:
- punycode "^2.1.0"
-
-v8-to-istanbul@^9.0.1:
- version "9.3.0"
- resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175"
- integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==
- dependencies:
- "@jridgewell/trace-mapping" "^0.3.12"
- "@types/istanbul-lib-coverage" "^2.0.1"
- convert-source-map "^2.0.0"
-
-vary@^1, vary@^1.1.2:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
- integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
-
-walker@^1.0.8:
- version "1.0.8"
- resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f"
- integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==
- dependencies:
- makeerror "1.0.12"
-
-which-boxed-primitive@^1.1.0, which-boxed-primitive@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz#d76ec27df7fa165f18d5808374a5fe23c29b176e"
- integrity sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==
- dependencies:
- is-bigint "^1.1.0"
- is-boolean-object "^1.2.1"
- is-number-object "^1.1.1"
- is-string "^1.1.1"
- is-symbol "^1.1.1"
-
-which-builtin-type@^1.2.1:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.2.1.tgz#89183da1b4907ab089a6b02029cc5d8d6574270e"
- integrity sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==
- dependencies:
- call-bound "^1.0.2"
- function.prototype.name "^1.1.6"
- has-tostringtag "^1.0.2"
- is-async-function "^2.0.0"
- is-date-object "^1.1.0"
- is-finalizationregistry "^1.1.0"
- is-generator-function "^1.0.10"
- is-regex "^1.2.1"
- is-weakref "^1.0.2"
- isarray "^2.0.5"
- which-boxed-primitive "^1.1.0"
- which-collection "^1.0.2"
- which-typed-array "^1.1.16"
-
-which-collection@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0"
- integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==
- dependencies:
- is-map "^2.0.3"
- is-set "^2.0.3"
- is-weakmap "^2.0.2"
- is-weakset "^2.0.3"
-
-which-typed-array@^1.1.16, which-typed-array@^1.1.18:
- version "1.1.19"
- resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.19.tgz#df03842e870b6b88e117524a4b364b6fc689f956"
- integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==
- dependencies:
- available-typed-arrays "^1.0.7"
- call-bind "^1.0.8"
- call-bound "^1.0.4"
- for-each "^0.3.5"
- get-proto "^1.0.1"
- gopd "^1.2.0"
- has-tostringtag "^1.0.2"
-
-which@^2.0.1:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
- integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
- dependencies:
- isexe "^2.0.0"
-
-word-wrap@^1.2.5:
- version "1.2.5"
- resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
- integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
-
-wrap-ansi@^7.0.0:
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
- integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
- dependencies:
- ansi-styles "^4.0.0"
- string-width "^4.1.0"
- strip-ansi "^6.0.0"
-
-wrappy@1:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
- integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
-
-write-file-atomic@^4.0.2:
- version "4.0.2"
- resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd"
- integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==
- dependencies:
- imurmurhash "^0.1.4"
- signal-exit "^3.0.7"
-
-y18n@^5.0.5:
- version "5.0.8"
- resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
- integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
-
-yallist@^3.0.2:
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
- integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
-
-yallist@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/yallist/-/yallist-5.0.0.tgz#00e2de443639ed0d78fd87de0d27469fbcffb533"
- integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==
-
-yargs-parser@^21.1.1:
- version "21.1.1"
- resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
- integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
-
-yargs@^17.3.1:
- version "17.7.2"
- resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"
- integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==
- dependencies:
- cliui "^8.0.1"
- escalade "^3.1.1"
- get-caller-file "^2.0.5"
- require-directory "^2.1.1"
- string-width "^4.2.3"
- y18n "^5.0.5"
- yargs-parser "^21.1.1"
-
-yocto-queue@^0.1.0:
- version "0.1.0"
- resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
- integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
-
-zod-to-json-schema@^3.24.1:
- version "3.24.5"
- resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz#d1095440b147fb7c2093812a53c54df8d5df50a3"
- integrity sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==
-
-zod@^3.23.8, zod@^3.24.2, zod@^3.24.4:
- version "3.24.4"
- resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.4.tgz#e2e2cca5faaa012d76e527d0d36622e0a90c315f"
- integrity sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==
-
-zustand@^5.0.4:
- version "5.0.4"
- resolved "https://registry.yarnpkg.com/zustand/-/zustand-5.0.4.tgz#33af161f1e337854ccd8b711ef9e92545d6ae53f"
- integrity sha512-39VFTN5InDtMd28ZhjLyuTnlytDr9HfwO512Ai4I8ZABCoyAj4F1+sr7sD1jP/+p7k77Iko0Pb5NhgBFDCX0kQ==