From 1c851a14e89829bc13fb94fc7d0bc2ba1c1db7a7 Mon Sep 17 00:00:00 2001 From: Oussama El Benney Date: Tue, 21 May 2024 16:42:19 +0100 Subject: [PATCH 01/20] auth using token/jwt login + logout --- src/app/auth/login/page.jsx | 40 +--------------- src/app/auth/verif/page.jsx | 6 +-- src/app/layout.js | 6 ++- src/app/lib/dal.js | 12 +++-- src/app/lib/fetchRequest.js | 8 ++-- src/app/lib/isAuthenticated.js | 14 ++++-- src/app/lib/isAuthenticatedSSR.js | 21 +++++++++ src/app/lib/session.js | 3 +- src/app/ui/Header.jsx | 76 +++++++++++++++++++++++++++++++ src/app/ui/LogoutButton.js | 19 ++++++++ src/middleware.js | 23 ++++------ 11 files changed, 157 insertions(+), 71 deletions(-) create mode 100644 src/app/lib/isAuthenticatedSSR.js create mode 100644 src/app/ui/Header.jsx create mode 100644 src/app/ui/LogoutButton.js diff --git a/src/app/auth/login/page.jsx b/src/app/auth/login/page.jsx index 33b3f09..29be228 100644 --- a/src/app/auth/login/page.jsx +++ b/src/app/auth/login/page.jsx @@ -18,10 +18,6 @@ const LoginPage = () => { // Add your form submission logic here }; - const secretKey = process.env.NEXT_PUBLIC_SESSION_SECRET; - console.log(secretKey); - - // send login request to the server const login = async (event) => { event.preventDefault(); try { @@ -37,43 +33,9 @@ const LoginPage = () => { setMessages(data.error); } else { setMessages('Login successful'); - // Set the cookie - const expiresAt = new Date(new Date().getTime() + 60 * 60 * 1000); // Set cookie to expire in 1 hour - // const session = json strigify it - Cookies.set('session', data.token, { - expires: expiresAt, - secure: true, - sameSite: 'Lax', - }); - // Redirect to the dashboard - // window.location.href = '/auth/verif'; - } - } catch (error) { - setMessages('An error occurred'); - } - }; - const loginn = async (event) => { - event.preventDefault(); - try { - const response = await fetch('http://localhost:8000/login/', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ username, password }), - }); - const data = await response.json(); - if (data.error) { - setMessages(data.error); - } else { - setMessages('Login successful'); - // Set the cookie - const expiresAt = new Date(new Date().getTime() + 60 * 60 * 1000); // Set cookie to expire in 1 hour - // const session = json strigify it await createSession(data); - // Redirect to the dashboard - // window.location.href = '/auth/verif'; + window.location.href = '/auth/verif'; } } catch (error) { setMessages('An error occurred'); diff --git a/src/app/auth/verif/page.jsx b/src/app/auth/verif/page.jsx index cfea4df..79eec0f 100644 --- a/src/app/auth/verif/page.jsx +++ b/src/app/auth/verif/page.jsx @@ -20,18 +20,18 @@ const Verif = () => { } }; const isAuth = async () => { - isAuthenticated(); + await isAuthenticated(); } return ( - <> +
user is redirected to this page after successful login
{/*test fetchExampleData */} - +
); } export default Verif; \ No newline at end of file diff --git a/src/app/layout.js b/src/app/layout.js index 9aef1df..3cf1c50 100644 --- a/src/app/layout.js +++ b/src/app/layout.js @@ -1,5 +1,6 @@ import { Inter } from "next/font/google"; import "./globals.css"; +import Header from "@/app/ui/Header"; const inter = Inter({ subsets: ["latin"] }); @@ -11,7 +12,10 @@ export const metadata = { export default function RootLayout({ children }) { return ( - {children} + +
+ {children} + ); } diff --git a/src/app/lib/dal.js b/src/app/lib/dal.js index 74c6293..67dfeaf 100644 --- a/src/app/lib/dal.js +++ b/src/app/lib/dal.js @@ -1,16 +1,18 @@ -import 'server-only' +// import 'server-only' +"use client"; import Cookies from 'js-cookie'; import { decrypt } from '@/app/lib/session' import {redirect} from "next/navigation"; -export const verifySession = cache(async () => { +export const verifySession = async () => { const cookie = Cookies.get('session').value const session = await decrypt(cookie) + console.log('session from dal', session) if (!session.token) { - redirect('/login') + redirect('/auth/login') } - return { isAuth: true, userName: session.username } -}) \ No newline at end of file + return { isAuth: true, sessionData: session.sessionData } +} \ No newline at end of file diff --git a/src/app/lib/fetchRequest.js b/src/app/lib/fetchRequest.js index 3afb1c3..b04097d 100644 --- a/src/app/lib/fetchRequest.js +++ b/src/app/lib/fetchRequest.js @@ -4,13 +4,13 @@ import Cookies from 'js-cookie'; import {decrypt} from "@/app/lib/session"; const fetchRequest = async (url, options = {}) => { - const token = Cookies.get('session'); - console.log('token', token) - const jwtDecrypted = decrypt(token); + const jwtCookie = Cookies.get('session'); + console.log('jwtCookie', jwtCookie) + const jwtDecrypted = await decrypt(jwtCookie); console.log('jwtDecrypted', jwtDecrypted) const headers = { ...options.headers, - 'Authorization': `Token ${token}`, + 'Authorization': `Token ${jwtDecrypted.sessionData.token}`, 'Content-Type': 'application/json', }; diff --git a/src/app/lib/isAuthenticated.js b/src/app/lib/isAuthenticated.js index 0a880ac..7c77a5e 100644 --- a/src/app/lib/isAuthenticated.js +++ b/src/app/lib/isAuthenticated.js @@ -1,16 +1,20 @@ //verify if the user is authenticated import Cookies from "js-cookie"; +import {decrypt} from "@/app/lib/session"; -export default function isAuthenticated() { +export default async function isAuthenticated() { if (!Cookies.get('session')) { console.log('user is not authenticated'); // redirect to auth/login page - window.location.href = '/auth/login'; - + // window.location.href = '/auth/login'; return false; } - else console.log('user is authenticated'); - return true; + + console.log('user is authenticated'); + const session = Cookies.get('session') + const cookieDecoded = await decrypt(session) + return { isAuth: true, sessionData: cookieDecoded.sessionData } + } diff --git a/src/app/lib/isAuthenticatedSSR.js b/src/app/lib/isAuthenticatedSSR.js new file mode 100644 index 0000000..09a8fd7 --- /dev/null +++ b/src/app/lib/isAuthenticatedSSR.js @@ -0,0 +1,21 @@ +//verify if the user is authenticated + +import { cookies } from 'next/headers' + +import {decrypt} from "@/app/lib/session"; + +export default async function isAuthenticatedSSR() { + if (!cookies().get('session')) { + console.log('user is not authenticated'); + // redirect to auth/login page + // window.location.href = '/auth/login'; + return false; + } + + console.log('user is authenticated'); + const cookie = cookies().get('session')?.value + const session = await decrypt(cookie) + return { isAuth: true, sessionData: session.sessionData } + +} + diff --git a/src/app/lib/session.js b/src/app/lib/session.js index 850ed74..1eb509a 100644 --- a/src/app/lib/session.js +++ b/src/app/lib/session.js @@ -2,7 +2,8 @@ import Cookies from 'js-cookie'; import { SignJWT, jwtVerify } from 'jose' -const secretKey = "password1234" +const secretKey = process.env.NEXT_PUBLIC_SESSION_SECRET; +console.log(secretKey); const encodedKey = new TextEncoder().encode(secretKey) export async function encrypt(payload) { diff --git a/src/app/ui/Header.jsx b/src/app/ui/Header.jsx new file mode 100644 index 0000000..e85b42e --- /dev/null +++ b/src/app/ui/Header.jsx @@ -0,0 +1,76 @@ +// 'use client'; + +// import { useState, useEffect } from 'react'; +import Link from 'next/link'; +// import isAuthenticated from "@/app/lib/isAuthenticated"; +import isAuthenticatedSSR from "@/app/lib/isAuthenticatedSSR"; +import LogoutButton from "@/app/ui/LogoutButton"; +// import fetchRequest from "@/app/lib/fetchRequest"; +// import Cookies from "js-cookie"; + +const Header = async () => { + + const {isAuth, sessionData} = await isAuthenticatedSSR() + console.log('isAuth', isAuth) + console.log('sessionData', sessionData) + // const [loggedInData, setLoggedInData] = useState(); + // + // + // useEffect(() => { + // isAuthenticated().then((data) => { + // console.log('data from headerv1', data) + // if (data.isAuth) { + // setLoggedInData(data.sessionData) + // console.log('data from header', loggedInData) + // } + // }) + // }, []); + + + // const logout = async () => { + // console.log('logout') + // await fetchRequest('http://localhost:8000/logout', {method: 'GET' }); + // Cookies.remove('session'); + // setLoggedInData(null); + // // reload the page + // window.location.href = '/'; + // } + return ( +
+
+ +

TeamBook

+ +
+ +
+ ); +}; + +export default Header; \ No newline at end of file diff --git a/src/app/ui/LogoutButton.js b/src/app/ui/LogoutButton.js new file mode 100644 index 0000000..b0aa3fd --- /dev/null +++ b/src/app/ui/LogoutButton.js @@ -0,0 +1,19 @@ +'use client'; + +import Cookies from 'js-cookie'; + +const LogoutButton = () => { + const logout = async () => { + await fetch('http://localhost:8000/logout', { method: 'GET' }); + Cookies.remove('session'); + window.location.href = '/'; + }; + + return ( + + ); +}; + +export default LogoutButton; diff --git a/src/middleware.js b/src/middleware.js index 1308f82..62024a0 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -1,12 +1,10 @@ -"use client"; - import { NextResponse } from 'next/server' import { decrypt } from '@/app/lib/session' -import Cookies from 'js-cookie'; +import { cookies } from 'next/headers' // 1. Specify protected and public routes -const protectedRoutes = ['/dashboard'] -const publicRoutes = ['/login', '/signup', '/'] +const protectedRoutes = ['/dashboard', '/auth/verif'] +const publicRoutes = ['/auth/login', '/auth/signup'] export default async function middleware(req) { // 2. Check if the current route is protected or public @@ -15,22 +13,21 @@ export default async function middleware(req) { const isPublicRoute = publicRoutes.includes(path) // 3. Decrypt the session from the cookie - const cookie = Cookies.get('session'); - console.log('cookie', cookie) + const cookie = cookies().get('session')?.value const session = await decrypt(cookie) - + console.log('session dfdf', session) // 5. Redirect to /login if the user is not authenticated - if (isProtectedRoute && !session?.token) { - return NextResponse.redirect(new URL('/login', req.nextUrl)) + if (isProtectedRoute && !session?.sessionData.token) { + return NextResponse.redirect(new URL('/auth/login', req.nextUrl)) } // 6. Redirect to /dashboard if the user is authenticated if ( isPublicRoute && - session?.token && - !req.nextUrl.pathname.startsWith('/auth') + session?.sessionData.token && + !req.nextUrl.pathname.startsWith('/auth/verif') ) { - return NextResponse.redirect(new URL('/auth', req.nextUrl)) + return NextResponse.redirect(new URL('/auth/verif', req.nextUrl)) } return NextResponse.next() -- GitLab From 7ff7ae2ec30b73a0aac738a935f93f3b8ae82817 Mon Sep 17 00:00:00 2001 From: Baligh ZOGHLAMI Date: Wed, 22 May 2024 13:20:13 +0100 Subject: [PATCH 02/20] add forgot password & change password UI --- next.config.mjs | 6 +- src/app/auth/change-password/page.jsx | 120 +++++++++++++++++++++ src/app/auth/forgot-password/page.jsx | 98 ++++++++++++++++++ src/app/auth/login/page.jsx | 12 +-- src/app/globals.css | 40 ++----- src/app/lib/constants.js | 1 + src/app/lib/fetchRequest.js | 17 +-- src/app/page.js | 143 +------------------------- src/components/Loader/Loader.css | 46 +++++++++ src/components/Loader/Loader.jsx | 13 +++ 10 files changed, 312 insertions(+), 184 deletions(-) create mode 100644 src/app/auth/change-password/page.jsx create mode 100644 src/app/auth/forgot-password/page.jsx create mode 100644 src/app/lib/constants.js create mode 100644 src/components/Loader/Loader.css create mode 100644 src/components/Loader/Loader.jsx diff --git a/next.config.mjs b/next.config.mjs index 4678774..c9a3c0c 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,4 +1,8 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {}; +const nextConfig = { + images: { + domains: ['https://res.cloudinary.com'], + } +}; export default nextConfig; diff --git a/src/app/auth/change-password/page.jsx b/src/app/auth/change-password/page.jsx new file mode 100644 index 0000000..be4882e --- /dev/null +++ b/src/app/auth/change-password/page.jsx @@ -0,0 +1,120 @@ +"use client" +import fetchRequest from '@/app/lib/fetchRequest' +import Image from 'next/image' +import Link from 'next/link' +import React, { useEffect, useState } from 'react' +import { useSearchParams } from 'next/navigation' +import Loader from '@/components/Loader/Loader' +import { passwordRegex } from '@/app/lib/constants' + +const ChangePassword = () => { + const [isSuccess, setIsSuccess] = useState(false) + const [formErrors, setFormErrors] = useState([]) + const [password, setPassword] = useState("") + const [confirmPassword, setConfirmPassword] = useState("") + const [isLoading, setIsLoading] = useState(false) + const params = useSearchParams(); + const handleChangePassword = async (event) => { + event.preventDefault() + setIsLoading(true) + const { isSuccess, data, errors } = await fetchRequest(`/password_reset/confirm/?token=${params.get("token")}`, { + method: "POST", + body: JSON.stringify({ + password, + token: params.get("token") + }) + }) + if (isSuccess) { + setIsSuccess(true) + } + else { + console.log(errors) + setIsLoading(false) + if (errors.type === "Validation Error") { + if (errors.detail.token) { + setFormErrors(["Le lien que vous avez utilisé pour réinitialiser votre mot de passe est invalide"]) + } + else { + setFormErrors(["Le Mot de passe est invalide"]) + } + } else if (errors?.detail?.startsWith("The OTP password entered is not valid")) { + setFormErrors(["Le lien que vous avez utilisé pour réinitialiser votre mot de passe est déja utilisé"]) + } + else { + console.log("Internal Server Error") + } + } + } + const isEmptyFields = !password && !confirmPassword + useEffect(() => { + var currentErrors = [] + if (password !== confirmPassword) { + currentErrors.push("Les mots de passe ne sont pas identiques.") + } + if (password.length < 8) { + currentErrors.push("Le mot de passe doit comporter au moins 8 caractères.") + } + if (!passwordRegex.test(password)) { + currentErrors.push("Le mot de passe doit contenir au moins une lettre, un chiffre et un caractère spécial.") + } + setFormErrors(currentErrors) + }, [password, confirmPassword]) + return ( +
+
+
+
+ teamwill +
+ {(!isSuccess) &&
+

Change your password. +

+
+
    +
  • Le mot de passe doit contenir au moins 8 caractères
  • +
  • Le mot de passe doit contenir au moins un chiffre
  • +
  • Le mot de passe doit contenir au moins un caractère spécial
  • +
+
+
+
+ + setPassword(event.target.value)} type="password" name="new_password1" id="new_password1" + className="rounded-md px-3 w-full duration-150 delay-75 focus:ring ring-offset-1 ring-sushi-200 border h-10 border-neutral-300 outline-none" /> +
+
+ + setConfirmPassword(event.target.value)} type="password" name="new_password2" id="new_password2" + className="rounded-md px-3 duration-150 delay-75 w-full focus:ring ring-offset-1 ring-sushi-200 border h-10 border-neutral-300 outline-none" /> +
+
    0 && !isEmptyFields ? "bg-red-100 border border-red-300" : ""} min-h-10 px-3 text-xs py-3 rounded relative mt-9 mb-6 list-inside list-disc`} role="alert"> + {!isEmptyFields && formErrors.map((error, index) => { + return
  • {error}
  • + })} +
+
+ +
+
+
} + {(isSuccess) && ( +
+

The password has been changed!

+ log in again? +
+ )} +
+
+
+ ) +} + +export default ChangePassword \ No newline at end of file diff --git a/src/app/auth/forgot-password/page.jsx b/src/app/auth/forgot-password/page.jsx new file mode 100644 index 0000000..3895740 --- /dev/null +++ b/src/app/auth/forgot-password/page.jsx @@ -0,0 +1,98 @@ +'use client' +import fetchRequest from '@/app/lib/fetchRequest' +import Loader from '@/components/Loader/Loader' +import Image from 'next/image' +import Link from 'next/link' +import React, { useState } from 'react' + +const ForgotPassword = () => { + const [email, setEmail] = useState("") + const [isSuccess, setIsSuccess] = useState(false) + const [isLoading, setIsLoading] = useState(false) + const [requestErrors, setRequestErrors] = useState([]) + const handleForgetPassword = async (event) => { + event.preventDefault() + setIsLoading(true) + const { isSuccess, errors, data } = await fetchRequest("/password_reset/", { + body: JSON.stringify({ email }), + method: "POST" + }) + if (isSuccess) { + setIsSuccess(true) + } + else { + setIsLoading(false) + if (errors.type === "Validation Error") { + // setRequestErrors(Object.keys(errors.detail).reduce((prev, current) => { + // return [...prev, ...errors.detail[current]] + // }, [])) + setRequestErrors(["Nous n'avons pas pu trouver de compte associé à cet e-mail. Veuillez essayer une autre adresse e-mail."]) + } else { + console.log("Internal server error") + } + } + } + return ( +
+
+
+ teamwill + {(!isSuccess) &&
+
+

Forgot your password!! No Problem + Reset it here +

+
+
+
+
+
+ setEmail(e.target.value)} autocomplete="off" id="email" name="email" type="text" + className="peer placeholder-transparent h-10 w-full border-b-2 border-gray-300 text-gray-900 focus:outline-none focus:borer-rose-600" + placeholder="Email address" /> + +
+ + + {(requestErrors.length !== 0) && } +
+ Back to log + in +
+ +
+
+
+
} + {(isSuccess) &&
+ + + +
+

Email Sent!

+

Check your email and open the link we sent to continue

+
+
} +
+
+
+ ) +} + +export default ForgotPassword \ No newline at end of file diff --git a/src/app/auth/login/page.jsx b/src/app/auth/login/page.jsx index 33b3f09..d9dd76c 100644 --- a/src/app/auth/login/page.jsx +++ b/src/app/auth/login/page.jsx @@ -3,7 +3,7 @@ import { useState } from 'react'; import Link from 'next/link'; import Cookies from 'js-cookie'; -import {createSession} from "@/app/lib/session"; +import { createSession } from "@/app/lib/session"; const LoginPage = () => { const [username, setUsername] = useState(''); @@ -91,7 +91,7 @@ const LoginPage = () => {
{
{ />
- -

Mot de passe oublié?

+ +

Mot de passe oublié?

{messages && ( @@ -124,7 +124,7 @@ const LoginPage = () => { )} diff --git a/src/app/globals.css b/src/app/globals.css index 6c139df..b6ba7d3 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -2,29 +2,6 @@ @tailwind components; @tailwind utilities; -:root { - --foreground-rgb: 0, 0, 0; - --background-start-rgb: 214, 219, 220; - --background-end-rgb: 255, 255, 255; -} - -@media (prefers-color-scheme: dark) { - :root { - --foreground-rgb: 255, 255, 255; - --background-start-rgb: 0, 0, 0; - --background-end-rgb: 0, 0, 0; - } -} - -body { - color: rgb(var(--foreground-rgb)); - background: linear-gradient( - to bottom, - transparent, - rgb(var(--background-end-rgb)) - ) - rgb(var(--background-start-rgb)); -} @layer utilities { .text-balance { @@ -34,18 +11,23 @@ body { /* Customize the scrollbar */ ::-webkit-scrollbar { - width: 6px; /* Set the width of the scrollbar */ + width: 6px; + /* Set the width of the scrollbar */ } ::-webkit-scrollbar-thumb { - background-color: rgba(0, 0, 0, 0.3); /* Color of the thumb */ - border-radius: 3px; /* Rounded corners of the thumb */ + background-color: rgba(0, 0, 0, 0.3); + /* Color of the thumb */ + border-radius: 3px; + /* Rounded corners of the thumb */ } ::-webkit-scrollbar-thumb:hover { - background-color: rgba(0, 0, 0, 0.5); /* Color of the thumb on hover */ + background-color: rgba(0, 0, 0, 0.5); + /* Color of the thumb on hover */ } ::-webkit-scrollbar-track { - background-color: rgba(0, 0, 0, 0.1); /* Color of the track */ -} + background-color: rgba(0, 0, 0, 0.1); + /* Color of the track */ +} \ No newline at end of file diff --git a/src/app/lib/constants.js b/src/app/lib/constants.js new file mode 100644 index 0000000..01b3d8f --- /dev/null +++ b/src/app/lib/constants.js @@ -0,0 +1 @@ +export const passwordRegex = /^(?=.*[a-zA-Z])(?=.*\d)(?=.*[^a-zA-Z\d\s]).*$/; \ No newline at end of file diff --git a/src/app/lib/fetchRequest.js b/src/app/lib/fetchRequest.js index 3afb1c3..722c921 100644 --- a/src/app/lib/fetchRequest.js +++ b/src/app/lib/fetchRequest.js @@ -1,8 +1,8 @@ //custom fetch tha have the token in header import Cookies from 'js-cookie'; -import {decrypt} from "@/app/lib/session"; - +import { decrypt } from "@/app/lib/session"; +const BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000" const fetchRequest = async (url, options = {}) => { const token = Cookies.get('session'); console.log('token', token) @@ -14,16 +14,21 @@ const fetchRequest = async (url, options = {}) => { 'Content-Type': 'application/json', }; - const response = await fetch(url, { + const response = await fetch(`${BASE_URL}${url}`, { ...options, headers, }); if (!response.ok) { - throw new Error('Network response was not ok'); + try { + const errorData = await response.json(); + return { isSuccess: false, errors: errorData, data: null } + } catch (error) { + return { isSuccess: false, errors: error, data: null } + } } - - return response.json(); + const data = await response.json() + return { isSuccess: true, errors: null, data: data }; }; export default fetchRequest; diff --git a/src/app/page.js b/src/app/page.js index ecfca6f..87ce7aa 100644 --- a/src/app/page.js +++ b/src/app/page.js @@ -1,149 +1,8 @@ -import Image from "next/image"; export default function Home() { return (
-
-

- Get started by editing  - src/app/page.js -

- -
- -
- Next.js Logo -
- -
- -

- Docs{" "} - - -> - -

-

- Find in-depth information about Next.js features and API. -

-
- - -

- Learn{" "} - - -> - -

-

- Learn about Next.js in an interactive course with quizzes! -

-
- - -

- Templates{" "} - - -> - -

-

- Explore starter templates for Next.js. -

-
- - -

- Deploy{" "} - - -> - -

-

- Instantly deploy your Next.js site to a shareable URL with Vercel. -

-

- Instantly deploy your Next.js site to a shareable URL with Vercel. -

-

- Instantly deploy your Next.js site to a shareable URL with Vercel. -

-

- Instantly deploy your Next.js site to a shareable URL with Vercel. -

-

- Instantly deploy your Next.js site to a shareable URL with Vercel. -

-

- Instantly deploy your Next.js site to a shareable URL with Vercel. -

-

- Instantly deploy your Next.js site to a shareable URL with Vercel. -

-

- Instantly deploy your Next.js site to a shareable URL with Vercel. -

-

- Instantly deploy your Next.js site to a shareable URL with Vercel. -

-

- Instantly deploy your Next.js site to a shareable URL with Vercel. -

-

- Instantly deploy your Next.js site to a shareable URL with Vercel. -

-

- Instantly deploy your Next.js site to a shareable URL with Vercel. -

-

- Instantly deploy your Next.js site to a shareable URL with Vercel. -

-
-
+ Home Page
); } diff --git a/src/components/Loader/Loader.css b/src/components/Loader/Loader.css new file mode 100644 index 0000000..1e1ca65 --- /dev/null +++ b/src/components/Loader/Loader.css @@ -0,0 +1,46 @@ +@keyframes ldio-gcsicpsikdq { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} + +.ldio-gcsicpsikdq div { + width: 50px; + height: 50px; + border-top-color: transparent; + border: 5px solid transparent; + border-radius: 50%; +} + +.ldio-gcsicpsikdq div { + animation: ldio-gcsicpsikdq 1s linear infinite; +} + +.loadingio-spinner-rolling-daoiuzlm498 { + width: min-content; + height: 70px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + background: transparent; +} + +.ldio-gcsicpsikdq { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + transform: translateZ(0) scale(1); + backface-visibility: hidden; + transform-origin: 0 0; + /* see note above */ +} + +.ldio-gcsicpsikdq div { + box-sizing: content-box; +} \ No newline at end of file diff --git a/src/components/Loader/Loader.jsx b/src/components/Loader/Loader.jsx new file mode 100644 index 0000000..2430f19 --- /dev/null +++ b/src/components/Loader/Loader.jsx @@ -0,0 +1,13 @@ +import React from 'react' +import "./Loader.css" +const Loader = ({ size, border, className, color }) => { + return ( +
+
+
+
+
+ ) +} + +export default Loader \ No newline at end of file -- GitLab From ab38e63869b479441512628b9dec581559d05059 Mon Sep 17 00:00:00 2001 From: Baligh ZOGHLAMI Date: Wed, 22 May 2024 14:30:04 +0100 Subject: [PATCH 03/20] add forgot password & change password features --- src/app/auth/change-password/page.jsx | 4 ++-- src/app/lib/constants.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/auth/change-password/page.jsx b/src/app/auth/change-password/page.jsx index be4882e..a89f484 100644 --- a/src/app/auth/change-password/page.jsx +++ b/src/app/auth/change-password/page.jsx @@ -5,7 +5,7 @@ import Link from 'next/link' import React, { useEffect, useState } from 'react' import { useSearchParams } from 'next/navigation' import Loader from '@/components/Loader/Loader' -import { passwordRegex } from '@/app/lib/constants' +import { PASSWORD_REGEX } from '@/app/lib/constants' const ChangePassword = () => { const [isSuccess, setIsSuccess] = useState(false) @@ -54,7 +54,7 @@ const ChangePassword = () => { if (password.length < 8) { currentErrors.push("Le mot de passe doit comporter au moins 8 caractères.") } - if (!passwordRegex.test(password)) { + if (!PASSWORD_REGEX.test(password)) { currentErrors.push("Le mot de passe doit contenir au moins une lettre, un chiffre et un caractère spécial.") } setFormErrors(currentErrors) diff --git a/src/app/lib/constants.js b/src/app/lib/constants.js index 01b3d8f..f4da663 100644 --- a/src/app/lib/constants.js +++ b/src/app/lib/constants.js @@ -1 +1 @@ -export const passwordRegex = /^(?=.*[a-zA-Z])(?=.*\d)(?=.*[^a-zA-Z\d\s]).*$/; \ No newline at end of file +export const PASSWORD_REGEX = /^(?=.*[a-zA-Z])(?=.*\d)(?=.*[^a-zA-Z\d\s]).*$/; \ No newline at end of file -- GitLab From abb29479bf6bfad33f77c4934329a2f8940dec69 Mon Sep 17 00:00:00 2001 From: Oussama El Benney Date: Wed, 22 May 2024 14:38:01 +0100 Subject: [PATCH 04/20] fixed login with jwt --- src/app/auth/login/page.jsx | 6 +++--- src/app/auth/verif/page.jsx | 2 +- src/app/ui/Header.jsx | 22 ---------------------- src/app/ui/LogoutButton.js | 13 ++++++++++--- 4 files changed, 14 insertions(+), 29 deletions(-) diff --git a/src/app/auth/login/page.jsx b/src/app/auth/login/page.jsx index 29be228..2d6459e 100644 --- a/src/app/auth/login/page.jsx +++ b/src/app/auth/login/page.jsx @@ -21,7 +21,7 @@ const LoginPage = () => { const login = async (event) => { event.preventDefault(); try { - const response = await fetch('http://localhost:8000/login/', { + const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/login/`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -29,8 +29,8 @@ const LoginPage = () => { body: JSON.stringify({ username, password }), }); const data = await response.json(); - if (data.error) { - setMessages(data.error); + if (data.non_field_errors) { + setMessages(data.non_field_errors[0]); } else { setMessages('Login successful'); diff --git a/src/app/auth/verif/page.jsx b/src/app/auth/verif/page.jsx index 79eec0f..b49360b 100644 --- a/src/app/auth/verif/page.jsx +++ b/src/app/auth/verif/page.jsx @@ -5,7 +5,7 @@ const Verif = () => { // fetch data from the server /example const fetchExampleData = async () => { try { - const data = await fetchRequest('http://localhost:8000/example/', + const data = await fetchRequest(`${process.env.NEXT_PUBLIC_API_URL}/example/`, {method: 'POST', body: JSON.stringify({ "name": "jhon", diff --git a/src/app/ui/Header.jsx b/src/app/ui/Header.jsx index e85b42e..b9d5df2 100644 --- a/src/app/ui/Header.jsx +++ b/src/app/ui/Header.jsx @@ -13,28 +13,6 @@ const Header = async () => { const {isAuth, sessionData} = await isAuthenticatedSSR() console.log('isAuth', isAuth) console.log('sessionData', sessionData) - // const [loggedInData, setLoggedInData] = useState(); - // - // - // useEffect(() => { - // isAuthenticated().then((data) => { - // console.log('data from headerv1', data) - // if (data.isAuth) { - // setLoggedInData(data.sessionData) - // console.log('data from header', loggedInData) - // } - // }) - // }, []); - - - // const logout = async () => { - // console.log('logout') - // await fetchRequest('http://localhost:8000/logout', {method: 'GET' }); - // Cookies.remove('session'); - // setLoggedInData(null); - // // reload the page - // window.location.href = '/'; - // } return (
diff --git a/src/app/ui/LogoutButton.js b/src/app/ui/LogoutButton.js index b0aa3fd..d3a137c 100644 --- a/src/app/ui/LogoutButton.js +++ b/src/app/ui/LogoutButton.js @@ -1,12 +1,19 @@ 'use client'; import Cookies from 'js-cookie'; +import fetchRequest from "@/app/lib/fetchRequest"; const LogoutButton = () => { const logout = async () => { - await fetch('http://localhost:8000/logout', { method: 'GET' }); - Cookies.remove('session'); - window.location.href = '/'; + const response = await fetchRequest(`${process.env.NEXT_PUBLIC_API_URL}/logout`, { + method: 'GET'}); + console.log(response); + if (response.statusCode === 200) { + console.log('logout successful'); + Cookies.remove('session'); + window.location.href = '/'; + } + }; return ( -- GitLab From 4608a92a3849b3a3d1e264d233297f2fed0ab819 Mon Sep 17 00:00:00 2001 From: Oussama El Benney Date: Wed, 22 May 2024 15:18:14 +0100 Subject: [PATCH 05/20] fixed fetch request /login/logout --- src/app/auth/verif/page.jsx | 2 +- src/app/lib/dal.js | 18 ------------------ src/app/lib/fetchRequest.js | 3 ++- src/app/ui/LogoutButton.js | 4 ++-- 4 files changed, 5 insertions(+), 22 deletions(-) delete mode 100644 src/app/lib/dal.js diff --git a/src/app/auth/verif/page.jsx b/src/app/auth/verif/page.jsx index b49360b..15d3d40 100644 --- a/src/app/auth/verif/page.jsx +++ b/src/app/auth/verif/page.jsx @@ -5,7 +5,7 @@ const Verif = () => { // fetch data from the server /example const fetchExampleData = async () => { try { - const data = await fetchRequest(`${process.env.NEXT_PUBLIC_API_URL}/example/`, + const data = await fetchRequest(`/example/`, {method: 'POST', body: JSON.stringify({ "name": "jhon", diff --git a/src/app/lib/dal.js b/src/app/lib/dal.js deleted file mode 100644 index 67dfeaf..0000000 --- a/src/app/lib/dal.js +++ /dev/null @@ -1,18 +0,0 @@ -// import 'server-only' -"use client"; - -import Cookies from 'js-cookie'; -import { decrypt } from '@/app/lib/session' -import {redirect} from "next/navigation"; - -export const verifySession = async () => { - const cookie = Cookies.get('session').value - const session = await decrypt(cookie) - console.log('session from dal', session) - - if (!session.token) { - redirect('/auth/login') - } - - return { isAuth: true, sessionData: session.sessionData } -} \ No newline at end of file diff --git a/src/app/lib/fetchRequest.js b/src/app/lib/fetchRequest.js index b6f73c2..9effc1e 100644 --- a/src/app/lib/fetchRequest.js +++ b/src/app/lib/fetchRequest.js @@ -10,7 +10,8 @@ const fetchRequest = async (url, options = {}) => { console.log('jwtDecrypted', jwtDecrypted) const headers = { ...options.headers, - 'Authorization': `Token ${jwtDecrypted.sessionData.token}`, + // add authorization header with token if there is jwtDecrypted.sessionData.token + Authorization: jwtDecrypted?.sessionData.token ? `Bearer ${jwtDecrypted.sessionData.token}` : undefined, 'Content-Type': 'application/json', }; diff --git a/src/app/ui/LogoutButton.js b/src/app/ui/LogoutButton.js index d3a137c..b219fc4 100644 --- a/src/app/ui/LogoutButton.js +++ b/src/app/ui/LogoutButton.js @@ -5,10 +5,10 @@ import fetchRequest from "@/app/lib/fetchRequest"; const LogoutButton = () => { const logout = async () => { - const response = await fetchRequest(`${process.env.NEXT_PUBLIC_API_URL}/logout`, { + const response = await fetchRequest(`/logout`, { method: 'GET'}); console.log(response); - if (response.statusCode === 200) { + if (response.isSuccess) { console.log('logout successful'); Cookies.remove('session'); window.location.href = '/'; -- GitLab From c738045759e2c636b6bc531a3c5f6d65537e9753 Mon Sep 17 00:00:00 2001 From: Oussama El Benney Date: Thu, 23 May 2024 09:43:38 +0100 Subject: [PATCH 06/20] added projects front only --- src/app/auth/login/page.jsx | 2 - src/app/lib/fetchRequest.js | 2 +- src/app/lib/fetchRequestServer.js | 41 ++++ src/app/lib/isAuthenticatedSSR.js | 1 - src/app/projects/ProjectForm.jsx | 99 +++++++++ src/app/projects/ProjectList.jsx | 109 ++++++++++ src/app/projects/SideBar.jsx | 334 ++++++++++++++++++++++++++++++ src/app/projects/page.jsx | 59 ++++++ src/middleware.js | 2 +- 9 files changed, 644 insertions(+), 5 deletions(-) create mode 100644 src/app/lib/fetchRequestServer.js create mode 100644 src/app/projects/ProjectForm.jsx create mode 100644 src/app/projects/ProjectList.jsx create mode 100644 src/app/projects/SideBar.jsx create mode 100644 src/app/projects/page.jsx diff --git a/src/app/auth/login/page.jsx b/src/app/auth/login/page.jsx index a657afd..e94f196 100644 --- a/src/app/auth/login/page.jsx +++ b/src/app/auth/login/page.jsx @@ -32,8 +32,6 @@ const LoginPage = () => { if (data.non_field_errors) { setMessages(data.non_field_errors[0]); } else { - setMessages('Login successful'); - await createSession(data); window.location.href = '/auth/verif'; } diff --git a/src/app/lib/fetchRequest.js b/src/app/lib/fetchRequest.js index 9effc1e..0a029d7 100644 --- a/src/app/lib/fetchRequest.js +++ b/src/app/lib/fetchRequest.js @@ -11,7 +11,7 @@ const fetchRequest = async (url, options = {}) => { const headers = { ...options.headers, // add authorization header with token if there is jwtDecrypted.sessionData.token - Authorization: jwtDecrypted?.sessionData.token ? `Bearer ${jwtDecrypted.sessionData.token}` : undefined, + Authorization: jwtDecrypted?.sessionData.token ? `Token ${jwtDecrypted.sessionData.token}` : undefined, 'Content-Type': 'application/json', }; diff --git a/src/app/lib/fetchRequestServer.js b/src/app/lib/fetchRequestServer.js new file mode 100644 index 0000000..ee50e36 --- /dev/null +++ b/src/app/lib/fetchRequestServer.js @@ -0,0 +1,41 @@ +// src/app/lib/fetchRequestServer.js +import { cookies as nextCookies } from 'next/headers'; +import { decrypt } from "@/app/lib/session"; + +const BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000"; + +const fetchRequestServer = async (url, options = {}) => { + const cookieStore = nextCookies(); + const jwtCookie = cookieStore.get('session')?.value; + + if (!jwtCookie) { + return { isSuccess: false, errors: "No session cookie found", data: null }; + } + + const jwtDecrypted = await decrypt(jwtCookie); + + const headers = { + ...options.headers, + Authorization: jwtDecrypted?.sessionData.token ? `Token ${jwtDecrypted.sessionData.token}` : undefined, + 'Content-Type': 'application/json', + }; + + const response = await fetch(`${BASE_URL}${url}`, { + ...options, + headers, + }); + + if (!response.ok) { + try { + const errorData = await response.json(); + return { isSuccess: false, errors: errorData, data: null }; + } catch (error) { + return { isSuccess: false, errors: error, data: null }; + } + } + + const data = await response.json(); + return { isSuccess: true, errors: null, data: data }; +}; + +export default fetchRequestServer; diff --git a/src/app/lib/isAuthenticatedSSR.js b/src/app/lib/isAuthenticatedSSR.js index 09a8fd7..b068815 100644 --- a/src/app/lib/isAuthenticatedSSR.js +++ b/src/app/lib/isAuthenticatedSSR.js @@ -1,5 +1,4 @@ //verify if the user is authenticated - import { cookies } from 'next/headers' import {decrypt} from "@/app/lib/session"; diff --git a/src/app/projects/ProjectForm.jsx b/src/app/projects/ProjectForm.jsx new file mode 100644 index 0000000..418e9b4 --- /dev/null +++ b/src/app/projects/ProjectForm.jsx @@ -0,0 +1,99 @@ +import { useState, useEffect } from 'react'; + +const ProjectForm = ({ onAddProject, onEditProject, editingProject }) => { + const [projectName, setProjectName] = useState(''); + const [clientName, setClientName] = useState(''); + const [emails, setEmails] = useState(['']); + + useEffect(() => { + if (editingProject) { + setProjectName(editingProject.projectName); + setClientName(editingProject.clientName); + setEmails(editingProject.emails); + } else { + setProjectName(''); + setClientName(''); + setEmails(['']); + } + }, [editingProject]); + + const handleEmailChange = (index, value) => { + const newEmails = [...emails]; + newEmails[index] = value; + setEmails(newEmails); + }; + + const handleAddEmail = () => { + setEmails([...emails, '']); + }; + + const handleRemoveEmail = (index) => { + setEmails(emails.filter((_, i) => i !== index)); + }; + + const handleSubmit = (e) => { + e.preventDefault(); + const project = { projectName, clientName, emails }; + + if (editingProject) { + onEditProject(editingProject.id, project); + } else { + onAddProject(project); + } + }; + + return ( +
+
+ + setProjectName(e.target.value)} + className="mt-1 block w-full px-3 py-2 bg-white border border-gray-300 rounded-md" + /> +
+
+ + setClientName(e.target.value)} + className="mt-1 block w-full px-3 py-2 bg-white border border-gray-300 rounded-md" + /> +
+
+ + {emails.map((email, index) => ( +
+ handleEmailChange(index, e.target.value)} + className="flex-1 px-3 py-2 bg-white border border-gray-300 rounded-md" + /> + +
+ ))} + +
+ +
+ ); +}; + +export default ProjectForm; diff --git a/src/app/projects/ProjectList.jsx b/src/app/projects/ProjectList.jsx new file mode 100644 index 0000000..a939286 --- /dev/null +++ b/src/app/projects/ProjectList.jsx @@ -0,0 +1,109 @@ +const ProjectList = ({ projects, onEdit, onDelete }) => { + return ( + <> + + + + + + + + + + {projects.map((project, index) => ( + + + + + + ))} + +
ProjetMembres de léquipeActions
{project.projectName}{project.emails.length} + + +
+
+
    +
  1. + + Prev Page + + + + +
  2. + +
  3. + + 1 + +
  4. + +
  5. + 2 +
  6. + +
  7. + + 3 + +
  8. + +
  9. + + 4 + +
  10. + +
  11. + + Next Page + + + + +
  12. +
+
+ + ); +}; + +export default ProjectList; diff --git a/src/app/projects/SideBar.jsx b/src/app/projects/SideBar.jsx new file mode 100644 index 0000000..c182af2 --- /dev/null +++ b/src/app/projects/SideBar.jsx @@ -0,0 +1,334 @@ +const SideBar = () => { + return ( +
+
+ + +
+
+ +
+
+
+ +
+
+ +
+
+
+ + ); +} +export default SideBar; \ No newline at end of file diff --git a/src/app/projects/page.jsx b/src/app/projects/page.jsx new file mode 100644 index 0000000..1db5d0a --- /dev/null +++ b/src/app/projects/page.jsx @@ -0,0 +1,59 @@ +'use client'; + +import SideBar from "@/app/projects/SideBar"; +import { useState } from 'react'; +import ProjectForm from "@/app/projects/ProjectForm"; +import ProjectList from "@/app/projects/ProjectList"; +const Projects = () => { + const [projects, setProjects] = useState([]); + const [editingProject, setEditingProject] = useState(null); + + const handleAddProject = (project) => { + project.id = projects.length ? projects[projects.length - 1].id + 1 : 1; + setProjects([...projects, project]); + setEditingProject(null); + }; + + const handleEditProject = (id, updatedProject) => { + const updatedProjects = projects.map((project) => + project.id === id ? { ...project, ...updatedProject } : project + ); + setProjects(updatedProjects); + setEditingProject(null); + }; + + const handleEditClick = (project) => { + setEditingProject(project); + }; + + const handleDeleteProject = (project) => { + setProjects(projects.filter((p) => p.id !== project.id)); + setEditingProject(null); + }; + + return ( +
+
+ +
+ +
+
+

Projets

+ + +
+
+
+ ); +} +export default Projects; + diff --git a/src/middleware.js b/src/middleware.js index 62024a0..9c10ccd 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -15,7 +15,7 @@ export default async function middleware(req) { // 3. Decrypt the session from the cookie const cookie = cookies().get('session')?.value const session = await decrypt(cookie) - console.log('session dfdf', session) + // console.log('session dfdf', session) // 5. Redirect to /login if the user is not authenticated if (isProtectedRoute && !session?.sessionData.token) { return NextResponse.redirect(new URL('/auth/login', req.nextUrl)) -- GitLab From 739d18abc69b4f544ca36280e800b315e4afcb37 Mon Sep 17 00:00:00 2001 From: Baligh ZOGHLAMI Date: Thu, 23 May 2024 16:58:26 +0100 Subject: [PATCH 07/20] feature: add privilege management UI --- jsconfig.json | 6 +- next.config.mjs | 25 +- package-lock.json | 3050 ++++++++++++++++++++- package.json | 1 + src/app/lib/TypesHelper.js | 3 + src/app/lib/fetchRequest.js | 3 +- src/app/privilege/CreatePrivilegeForm.jsx | 54 + src/app/privilege/PrivilegeTableRow.jsx | 112 + src/app/privilege/page.jsx | 55 + src/static/image/svg/cancel.svg | 1 + src/static/image/svg/check.svg | 1 + src/static/image/svg/delete.svg | 3 + src/static/image/svg/edit.svg | 1 + 13 files changed, 3230 insertions(+), 85 deletions(-) create mode 100644 src/app/lib/TypesHelper.js create mode 100644 src/app/privilege/CreatePrivilegeForm.jsx create mode 100644 src/app/privilege/PrivilegeTableRow.jsx create mode 100644 src/app/privilege/page.jsx create mode 100644 src/static/image/svg/cancel.svg create mode 100644 src/static/image/svg/check.svg create mode 100644 src/static/image/svg/delete.svg create mode 100644 src/static/image/svg/edit.svg diff --git a/jsconfig.json b/jsconfig.json index b8d6842..879f8fc 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -1,7 +1,9 @@ { "compilerOptions": { "paths": { - "@/*": ["./src/*"] + "@/*": [ + "./src/*" + ] } } -} +} \ No newline at end of file diff --git a/next.config.mjs b/next.config.mjs index c9a3c0c..1a2f07f 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -2,7 +2,30 @@ const nextConfig = { images: { domains: ['https://res.cloudinary.com'], - } + }, + webpack(config) { + const fileLoaderRule = config.module.rules.find((rule) => + rule.test?.test?.('.svg'), + ) + + config.module.rules.push( + { + ...fileLoaderRule, + test: /\.svg$/i, + resourceQuery: /url/, // *.svg?url + }, + { + test: /\.svg$/i, + issuer: fileLoaderRule.issuer, + resourceQuery: { not: [...fileLoaderRule.resourceQuery.not, /url/] }, // exclude if *.svg?url + use: ['@svgr/webpack'], + }, + ) + + fileLoaderRule.exclude = /\.svg$/i + + return config + }, }; export default nextConfig; diff --git a/package-lock.json b/package-lock.json index d4a9662..617d640 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "react-dom": "^18" }, "devDependencies": { + "@svgr/webpack": "^8.1.0", "eslint": "^8", "eslint-config-next": "14.2.3", "postcss": "^8", @@ -33,6 +34,1942 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", + "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", + "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz", + "integrity": "sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.24.5", + "@babel/helpers": "^7.24.5", + "@babel/parser": "^7.24.5", + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.5", + "@babel/types": "^7.24.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz", + "integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.5", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", + "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.5.tgz", + "integrity": "sha512-uRc4Cv8UQWnE4NXlYTIIdM7wfFkOqlFztcC/gVXDKohKoVB3OyonfelUBaJzSwpBntZ2KYGF/9S7asCHsXwW6g==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-member-expression-to-functions": "^7.24.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.24.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.24.5", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", + "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", + "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.5.tgz", + "integrity": "sha512-4owRteeihKWKamtqg4JmWSsEZU445xpFRXPEwp44HbgbxdWlUV1b4Agg4lkA806Lil5XM/e+FJyS0vj5T6vmcA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", + "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz", + "integrity": "sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.24.3", + "@babel/helper-simple-access": "^7.24.5", + "@babel/helper-split-export-declaration": "^7.24.5", + "@babel/helper-validator-identifier": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz", + "integrity": "sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", + "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-wrap-function": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz", + "integrity": "sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-member-expression-to-functions": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz", + "integrity": "sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", + "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", + "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz", + "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.24.5.tgz", + "integrity": "sha512-/xxzuNvgRl4/HLNKvnFwdhdgN3cpLxgLROeLDl83Yx0AJ1SGvq1ak0OszTOjDfiB8Vx03eJbeDWh9r+jCCWttw==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.23.0", + "@babel/template": "^7.24.0", + "@babel/types": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.5.tgz", + "integrity": "sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==", + "dev": true, + "dependencies": { + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.5", + "@babel/types": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz", + "integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.5", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz", + "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.5.tgz", + "integrity": "sha512-LdXRi1wEMTrHVR4Zc9F8OewC3vdm5h4QB6L71zy6StmYeqGi1b3ttIO8UC+BfZKcH9jdr4aI249rBkm+3+YvHw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.1.tgz", + "integrity": "sha512-y4HqEnkelJIOQGd+3g1bTeKsA5c6qM7eOn7VggGVbBc0y8MLSKHacwcIE2PplNlQSj0PqS9rrXL/nkPVK+kUNg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.1.tgz", + "integrity": "sha512-Hj791Ii4ci8HqnaKHAlLNs+zaLXb0EzSDhiAWp5VNlyvCNymYfacs64pxTxbH1znW/NcArSmwpmG9IKE/TUVVQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.24.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.1.tgz", + "integrity": "sha512-m9m/fXsXLiHfwdgydIFnpk+7jlVbnvlK5B2EKiPdLUb6WX654ZaaEWJUjk8TftRbZpK0XibovlLWX4KIZhV6jw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.1.tgz", + "integrity": "sha512-IuwnI5XnuF189t91XbxmXeCDz3qs6iDRO7GJ++wcfgeXNs/8FmIlKcpDSXNVyuLQxlwvskmI3Ct73wUODkJBlQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.1.tgz", + "integrity": "sha512-zhQTMH0X2nVLnb04tz+s7AMuasX8U0FnpE+nHTOhSOINjWMnopoZTxtIKsd45n4GQ/HIZLyfIpoul8e2m0DnRA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz", + "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz", + "integrity": "sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.1.tgz", + "integrity": "sha512-ngT/3NkRhsaep9ck9uj2Xhv9+xB1zShY3tM3g6om4xxCELwCDN4g4Aq5dRn48+0hasAql7s2hdBOysCfNpr4fw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.3.tgz", + "integrity": "sha512-Qe26CMYVjpQxJ8zxM1340JFNjZaF+ISWpr1Kt/jGo+ZTUzKkfw/pphEWbRCb+lmSM6k/TOgfYLvmbHkUQ0asIg==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-remap-async-to-generator": "^7.22.20", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.1.tgz", + "integrity": "sha512-AawPptitRXp1y0n4ilKcGbRYWfbbzFWz2NqNu7dacYDtFtz0CMjG64b3LQsb3KIgnf4/obcUL78hfaOS7iCUfw==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-remap-async-to-generator": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.1.tgz", + "integrity": "sha512-TWWC18OShZutrv9C6mye1xwtam+uNi2bnTOCBUd5sZxyHOiWbU6ztSROofIMrK84uweEZC219POICK/sTYwfgg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.5.tgz", + "integrity": "sha512-sMfBc3OxghjC95BkYrYocHL3NaOplrcaunblzwXhGmlPwpmfsxr4vK+mBBt49r+S240vahmv+kUxkeKgs+haCw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.1.tgz", + "integrity": "sha512-OMLCXi0NqvJfORTaPQBwqLXHhb93wkBKZ4aNwMl6WtehO7ar+cmp+89iPEQPqxAnxsOKTaMcs3POz3rKayJ72g==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.4.tgz", + "integrity": "sha512-B8q7Pz870Hz/q9UgP8InNpY01CSLDSCyqX7zcRuv3FcPl87A2G17lASroHWaCtbdIcbYzOZ7kWmXFKbijMSmFg==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.4", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.5.tgz", + "integrity": "sha512-gWkLP25DFj2dwe9Ck8uwMOpko4YsqyfZJrOmqqcegeDYEbp7rmn4U6UQZNj08UF6MaX39XenSpKRCvpDRBtZ7Q==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-plugin-utils": "^7.24.5", + "@babel/helper-replace-supers": "^7.24.1", + "@babel/helper-split-export-declaration": "^7.24.5", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.1.tgz", + "integrity": "sha512-5pJGVIUfJpOS+pAqBQd+QMaTD2vCL/HcePooON6pDpHgRp4gNRmzyHTPIkXntwKsq3ayUFVfJaIKPw2pOkOcTw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/template": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.5.tgz", + "integrity": "sha512-SZuuLyfxvsm+Ah57I/i1HVjveBENYK9ue8MJ7qkc7ndoNjqquJiElzA7f5yaAXjyW2hKojosOTAQQRX50bPSVg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.1.tgz", + "integrity": "sha512-p7uUxgSoZwZ2lPNMzUkqCts3xlp8n+o05ikjy7gbtFJSt9gdU88jAmtfmOxHM14noQXBxfgzf2yRWECiNVhTCw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.1.tgz", + "integrity": "sha512-msyzuUnvsjsaSaocV6L7ErfNsa5nDWL1XKNnDePLgmz+WdU4w/J8+AxBMrWfi9m4IxfL5sZQKUPQKDQeeAT6lA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.1.tgz", + "integrity": "sha512-av2gdSTyXcJVdI+8aFZsCAtR29xJt0S5tas+Ef8NvBNmD1a+N/3ecMLeMBgfcK+xzsjdLDT6oHt+DFPyeqUbDA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.1.tgz", + "integrity": "sha512-U1yX13dVBSwS23DEAqU+Z/PkwE9/m7QQy8Y9/+Tdb8UWYaGNDYwTLi19wqIAiROr8sXVum9A/rtiH5H0boUcTw==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.1.tgz", + "integrity": "sha512-Ft38m/KFOyzKw2UaJFkWG9QnHPG/Q/2SkOrRk4pNBPg5IPZ+dOxcmkK5IyuBcxiNPyyYowPGUReyBvrvZs7IlQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.1.tgz", + "integrity": "sha512-OxBdcnF04bpdQdR3i4giHZNZQn7cm8RQKcSwA17wAAqEELo1ZOwp5FFgeptWUQXFyT9kwHo10aqqauYkRZPCAg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.1.tgz", + "integrity": "sha512-BXmDZpPlh7jwicKArQASrj8n22/w6iymRnvHYYd2zO30DbE277JO20/7yXJT3QxDPtiQiOxQBbZH4TpivNXIxA==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.1.tgz", + "integrity": "sha512-U7RMFmRvoasscrIFy5xA4gIp8iWnWubnKkKuUGJjsuOH7GfbMkB+XZzeslx2kLdEGdOJDamEmCqOks6e8nv8DQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.1.tgz", + "integrity": "sha512-zn9pwz8U7nCqOYIiBaOxoQOtYmMODXTJnkxG4AtX8fPmnCRYWBOHD0qcpwS9e2VDSp1zNJYpdnFMIKb8jmwu6g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.1.tgz", + "integrity": "sha512-OhN6J4Bpz+hIBqItTeWJujDOfNP+unqv/NJgyhlpSqgBTPm37KkMmZV6SYcOj+pnDbdcl1qRGV/ZiIjX9Iy34w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.1.tgz", + "integrity": "sha512-4ojai0KysTWXzHseJKa1XPNXKRbuUrhkOPY4rEGeR+7ChlJVKxFa3H3Bz+7tWaGKgJAXUWKOGmltN+u9B3+CVg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.1.tgz", + "integrity": "sha512-lAxNHi4HVtjnHd5Rxg3D5t99Xm6H7b04hUS7EHIXcUl2EV4yl1gWdqZrNzXnSrHveL9qMdbODlLF55mvgjAfaQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz", + "integrity": "sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-simple-access": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.1.tgz", + "integrity": "sha512-mqQ3Zh9vFO1Tpmlt8QPnbwGHzNz3lpNEMxQb1kAemn/erstyqw1r9KeOlOfo3y6xAnFEcOv2tSyrXfmMk+/YZA==", + "dev": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.1.tgz", + "integrity": "sha512-tuA3lpPj+5ITfcCluy6nWonSL7RvaG0AOTeAuvXqEKS34lnLzXpDb0dcP6K8jD0zWZFNDVly90AGFJPnm4fOYg==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", + "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.1.tgz", + "integrity": "sha512-/rurytBM34hYy0HKZQyA0nHbQgQNFm4Q/BOc9Hflxi2X3twRof7NaE5W46j4kQitm7SvACVRXsa6N/tSZxvPug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.1.tgz", + "integrity": "sha512-iQ+caew8wRrhCikO5DrUYx0mrmdhkaELgFa+7baMcVuhxIkN7oxt06CZ51D65ugIb1UWRQ8oQe+HXAVM6qHFjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.1.tgz", + "integrity": "sha512-7GAsGlK4cNL2OExJH1DzmDeKnRv/LXq0eLUSvudrehVA5Rgg4bIrqEUW29FbKMBRT0ztSqisv7kjP+XIC4ZMNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.5.tgz", + "integrity": "sha512-7EauQHszLGM3ay7a161tTQH7fj+3vVM/gThlz5HpFtnygTxjrlvoeq7MPVA1Vy9Q555OB8SnAOsMkLShNkkrHA==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-plugin-utils": "^7.24.5", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.1.tgz", + "integrity": "sha512-oKJqR3TeI5hSLRxudMjFQ9re9fBVUU0GICqM3J1mi8MqlhVr6hC/ZN4ttAyMuQR6EZZIY6h/exe5swqGNNIkWQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-replace-supers": "^7.24.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.1.tgz", + "integrity": "sha512-oBTH7oURV4Y+3EUrf6cWn1OHio3qG/PVwO5J03iSJmBg6m2EhKjkAu/xuaXaYwWW9miYtvbWv4LNf0AmR43LUA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.5.tgz", + "integrity": "sha512-xWCkmwKT+ihmA6l7SSTpk8e4qQl/274iNbSKRRS8mpqFR32ksy36+a+LWY8OXCCEefF8WFlnOHVsaDI2231wBg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.5.tgz", + "integrity": "sha512-9Co00MqZ2aoky+4j2jhofErthm6QVLKbpQrvz20c3CH9KQCLHyNB+t2ya4/UrRpQGR+Wrwjg9foopoeSdnHOkA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.1.tgz", + "integrity": "sha512-tGvisebwBO5em4PaYNqt4fkw56K2VALsAbAakY0FjTYqJp7gfdrgr7YX76Or8/cpik0W6+tj3rZ0uHU9Oil4tw==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.5.tgz", + "integrity": "sha512-JM4MHZqnWR04jPMujQDTBVRnqxpLLpx2tkn7iPn+Hmsc0Gnb79yvRWOkvqFOx3Z7P7VxiRIR22c4eGSNj87OBQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.24.5", + "@babel/helper-plugin-utils": "^7.24.5", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.1.tgz", + "integrity": "sha512-LetvD7CrHmEx0G442gOomRr66d7q8HzzGGr4PMHGr+5YIm6++Yke+jxj246rpvsbyhJwCLxcTn6zW1P1BSenqA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-constant-elements": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.24.1.tgz", + "integrity": "sha512-QXp1U9x0R7tkiGB0FOk8o74jhnap0FlZ5gNkRIWdG3eP+SvMFg118e1zaWewDzgABb106QSKpVsD3Wgd8t6ifA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.1.tgz", + "integrity": "sha512-mvoQg2f9p2qlpDQRBC7M3c3XTr0k7cp/0+kFKKO/7Gtu0LSw16eKB+Fabe2bDT/UpsyasTBBkAnbdsLrkD5XMw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.23.4.tgz", + "integrity": "sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.23.3", + "@babel/types": "^7.23.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz", + "integrity": "sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==", + "dev": true, + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.24.1.tgz", + "integrity": "sha512-+pWEAaDJvSm9aFvJNpLiM2+ktl2Sn2U5DdyiWdZBxmLc6+xGt88dvFqsHiAiDS+8WqUwbDfkKz9jRxK3M0k+kA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.1.tgz", + "integrity": "sha512-sJwZBCzIBE4t+5Q4IGLaaun5ExVMRY0lYwos/jNecjMrVCygCdph3IKv0tkP5Fc87e/1+bebAmEAGBfnRD+cnw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.1.tgz", + "integrity": "sha512-JAclqStUfIwKN15HrsQADFgeZt+wexNQ0uLhuqvqAUFoqPMjEcFCYZBhq0LUdz6dZK/mD+rErhW71fbx8RYElg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.1.tgz", + "integrity": "sha512-LyjVB1nsJ6gTTUKRjRWx9C1s9hE7dLfP/knKdrfeH9UPtAGjYGgxIbFfx7xyLIEWs7Xe1Gnf8EWiUqfjLhInZA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.1.tgz", + "integrity": "sha512-KjmcIM+fxgY+KxPVbjelJC6hrH1CgtPmTvdXAfn3/a9CnWGSTY7nH4zm5+cjmWJybdcPSsD0++QssDsjcpe47g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.1.tgz", + "integrity": "sha512-9v0f1bRXgPVcPrngOQvLXeGNNVLc8UjMVfebo9ka0WF3/7+aVUHmaJVT3sa0XCzEFioPfPHZiOcYG9qOsH63cw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.1.tgz", + "integrity": "sha512-WRkhROsNzriarqECASCNu/nojeXCDTE/F2HmRgOzi7NGvyfYGq1NEjKBK3ckLfRgGc6/lPAqP0vDOSw3YtG34g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.5.tgz", + "integrity": "sha512-UTGnhYVZtTAjdwOTzT+sCyXmTn8AhaxOS/MjG9REclZ6ULHWF9KoCZur0HSGU7hk8PdBFKKbYe6+gqdXWz84Jg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.5.tgz", + "integrity": "sha512-E0VWu/hk83BIFUWnsKZ4D81KXjN5L3MobvevOHErASk9IPwKHOkTgvqzvNo1yP/ePJWqqK2SpUR5z+KQbl6NVw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.24.5", + "@babel/helper-plugin-utils": "^7.24.5", + "@babel/plugin-syntax-typescript": "^7.24.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.1.tgz", + "integrity": "sha512-RlkVIcWT4TLI96zM660S877E7beKlQw7Ig+wqkKBiWfj0zH5Q4h50q6er4wzZKRNSYpfo6ILJ+hrJAGSX2qcNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.1.tgz", + "integrity": "sha512-Ss4VvlfYV5huWApFsF8/Sq0oXnGO+jB+rijFEFugTd3cwSObUSnUi88djgR5528Csl0uKlrI331kRqe56Ov2Ng==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.1.tgz", + "integrity": "sha512-2A/94wgZgxfTsiLaQ2E36XAOdcZmGAaEEgVmxQWwZXWkGhvoHbaqXcKnU8zny4ycpu3vNqg0L/PcCiYtHtA13g==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.1.tgz", + "integrity": "sha512-fqj4WuzzS+ukpgerpAoOnMfQXwUHFxXUZUE84oL2Kao2N8uSlvcpnAidKASgsNgzZHBsHWvcm8s9FPWUhAb8fA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.5.tgz", + "integrity": "sha512-UGK2ifKtcC8i5AI4cH+sbLLuLc2ktYSFJgBAXorKAsHUZmrQ1q6aQ6i3BvU24wWs2AAKqQB6kq3N9V9Gw1HiMQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.24.4", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-plugin-utils": "^7.24.5", + "@babel/helper-validator-option": "^7.23.5", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.5", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.1", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.24.1", + "@babel/plugin-syntax-import-attributes": "^7.24.1", + "@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/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.24.1", + "@babel/plugin-transform-async-generator-functions": "^7.24.3", + "@babel/plugin-transform-async-to-generator": "^7.24.1", + "@babel/plugin-transform-block-scoped-functions": "^7.24.1", + "@babel/plugin-transform-block-scoping": "^7.24.5", + "@babel/plugin-transform-class-properties": "^7.24.1", + "@babel/plugin-transform-class-static-block": "^7.24.4", + "@babel/plugin-transform-classes": "^7.24.5", + "@babel/plugin-transform-computed-properties": "^7.24.1", + "@babel/plugin-transform-destructuring": "^7.24.5", + "@babel/plugin-transform-dotall-regex": "^7.24.1", + "@babel/plugin-transform-duplicate-keys": "^7.24.1", + "@babel/plugin-transform-dynamic-import": "^7.24.1", + "@babel/plugin-transform-exponentiation-operator": "^7.24.1", + "@babel/plugin-transform-export-namespace-from": "^7.24.1", + "@babel/plugin-transform-for-of": "^7.24.1", + "@babel/plugin-transform-function-name": "^7.24.1", + "@babel/plugin-transform-json-strings": "^7.24.1", + "@babel/plugin-transform-literals": "^7.24.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.1", + "@babel/plugin-transform-member-expression-literals": "^7.24.1", + "@babel/plugin-transform-modules-amd": "^7.24.1", + "@babel/plugin-transform-modules-commonjs": "^7.24.1", + "@babel/plugin-transform-modules-systemjs": "^7.24.1", + "@babel/plugin-transform-modules-umd": "^7.24.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.24.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.1", + "@babel/plugin-transform-numeric-separator": "^7.24.1", + "@babel/plugin-transform-object-rest-spread": "^7.24.5", + "@babel/plugin-transform-object-super": "^7.24.1", + "@babel/plugin-transform-optional-catch-binding": "^7.24.1", + "@babel/plugin-transform-optional-chaining": "^7.24.5", + "@babel/plugin-transform-parameters": "^7.24.5", + "@babel/plugin-transform-private-methods": "^7.24.1", + "@babel/plugin-transform-private-property-in-object": "^7.24.5", + "@babel/plugin-transform-property-literals": "^7.24.1", + "@babel/plugin-transform-regenerator": "^7.24.1", + "@babel/plugin-transform-reserved-words": "^7.24.1", + "@babel/plugin-transform-shorthand-properties": "^7.24.1", + "@babel/plugin-transform-spread": "^7.24.1", + "@babel/plugin-transform-sticky-regex": "^7.24.1", + "@babel/plugin-transform-template-literals": "^7.24.1", + "@babel/plugin-transform-typeof-symbol": "^7.24.5", + "@babel/plugin-transform-unicode-escapes": "^7.24.1", + "@babel/plugin-transform-unicode-property-regex": "^7.24.1", + "@babel/plugin-transform-unicode-regex": "^7.24.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.24.1", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.4", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.31.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.24.1.tgz", + "integrity": "sha512-eFa8up2/8cZXLIpkafhaADTXSnl7IsUFCYenRWrARBz0/qZwcT0RBXpys0LJU4+WfPoF2ZG6ew6s2V6izMCwRA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-validator-option": "^7.23.5", + "@babel/plugin-transform-react-display-name": "^7.24.1", + "@babel/plugin-transform-react-jsx": "^7.23.4", + "@babel/plugin-transform-react-jsx-development": "^7.22.5", + "@babel/plugin-transform-react-pure-annotations": "^7.24.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.24.1.tgz", + "integrity": "sha512-1DBaMmRDpuYQBPWD8Pf/WEwCrtgRHxsZnP4mIy9G/X+hFfbI47Q2G4t1Paakld84+qsk2fSsUPMKg71jkoOOaQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-validator-option": "^7.23.5", + "@babel/plugin-syntax-jsx": "^7.24.1", + "@babel/plugin-transform-modules-commonjs": "^7.24.1", + "@babel/plugin-transform-typescript": "^7.24.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true + }, "node_modules/@babel/runtime": { "version": "7.24.5", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz", @@ -45,6 +1982,64 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/template": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", + "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz", + "integrity": "sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.24.5", + "@babel/parser": "^7.24.5", + "@babel/types": "^7.24.5", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz", + "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.24.1", + "@babel/helper-validator-identifier": "^7.24.5", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -307,112 +2302,369 @@ "node": ">= 10" } }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.3.tgz", - "integrity": "sha512-BTAbq0LnCbF5MtoM7I/9UeUu/8ZBY0i8SFjUMCbPDOLv+un67e2JgyN4pmgfXBwy/I+RHu8q+k+MCkDN6P9ViQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], + "node_modules/@next/swc-linux-x64-musl": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.3.tgz", + "integrity": "sha512-BTAbq0LnCbF5MtoM7I/9UeUu/8ZBY0i8SFjUMCbPDOLv+un67e2JgyN4pmgfXBwy/I+RHu8q+k+MCkDN6P9ViQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.3.tgz", + "integrity": "sha512-AEHIw/dhAMLNFJFJIJIyOFDzrzI5bAjI9J26gbO5xhAKHYTZ9Or04BesFPXiAYXDNdrwTP2dQceYA4dL1geu8A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.3.tgz", + "integrity": "sha512-vga40n1q6aYb0CLrM+eEmisfKCR45ixQYXuBXxOOmmoV8sYST9k7E3US32FsY+CkkF7NtzdcebiFT4CHuMSyZw==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.3.tgz", + "integrity": "sha512-Q1/zm43RWynxrO7lW4ehciQVj+5ePBhOK+/K2P7pLFX3JaJ/IZVC69SHidrmZSOkqz7ECIOhhy7XhAFG4JYyHA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.3.tgz", + "integrity": "sha512-qC/xYId4NMebE6w/V33Fh9gWxLgURiNYgVNObbJl2LZv0GUUItCcCqC5axQSwRaAgaxl2mELq1rMzlswaQ0Zxg==", + "dev": true + }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "dev": true, "engines": { - "node": ">= 10" + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.3.tgz", - "integrity": "sha512-AEHIw/dhAMLNFJFJIJIyOFDzrzI5bAjI9J26gbO5xhAKHYTZ9Or04BesFPXiAYXDNdrwTP2dQceYA4dL1geu8A==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", + "dev": true, "engines": { - "node": ">= 10" + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.3.tgz", - "integrity": "sha512-vga40n1q6aYb0CLrM+eEmisfKCR45ixQYXuBXxOOmmoV8sYST9k7E3US32FsY+CkkF7NtzdcebiFT4CHuMSyZw==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", + "dev": true, "engines": { - "node": ">= 10" + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.3.tgz", - "integrity": "sha512-Q1/zm43RWynxrO7lW4ehciQVj+5ePBhOK+/K2P7pLFX3JaJ/IZVC69SHidrmZSOkqz7ECIOhhy7XhAFG4JYyHA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], + "node_modules/@svgr/babel-preset": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "dev": true, + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", + "@svgr/babel-plugin-transform-svg-component": "8.0.0" + }, "engines": { - "node": ">= 10" + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@svgr/core": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "dev": true, "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^8.1.3", + "snake-case": "^3.0.4" }, "engines": { - "node": ">= 8" + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.21.3", + "entities": "^4.4.0" + }, "engines": { - "node": ">= 8" + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/@svgr/plugin-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "dev": true, "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "@svgr/hast-util-to-babel-ast": "8.0.0", + "svg-parser": "^2.0.4" }, "engines": { - "node": ">= 8" + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "optional": true, + "node_modules/@svgr/plugin-svgo": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz", + "integrity": "sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==", + "dev": true, + "dependencies": { + "cosmiconfig": "^8.1.3", + "deepmerge": "^4.3.1", + "svgo": "^3.0.2" + }, "engines": { "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" } }, - "node_modules/@rushstack/eslint-patch": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.3.tgz", - "integrity": "sha512-qC/xYId4NMebE6w/V33Fh9gWxLgURiNYgVNObbJl2LZv0GUUItCcCqC5axQSwRaAgaxl2mELq1rMzlswaQ0Zxg==", - "dev": true + "node_modules/@svgr/webpack": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-8.1.0.tgz", + "integrity": "sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.21.3", + "@babel/plugin-transform-react-constant-elements": "^7.21.3", + "@babel/preset-env": "^7.20.2", + "@babel/preset-react": "^7.18.6", + "@babel/preset-typescript": "^7.21.0", + "@svgr/core": "8.1.0", + "@svgr/plugin-jsx": "8.1.0", + "@svgr/plugin-svgo": "8.1.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } }, "node_modules/@swc/counter": { "version": "0.1.3", @@ -436,6 +2688,15 @@ "tailwindcss": ">=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1" } }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -878,6 +3139,54 @@ "dequal": "^2.0.3" } }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", + "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.2", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz", + "integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.1", + "core-js-compat": "^3.36.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", + "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -894,6 +3203,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -915,6 +3230,38 @@ "node": ">=8" } }, + "node_modules/browserslist": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -954,6 +3301,18 @@ "node": ">=6" } }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", @@ -1066,17 +3425,103 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/core-js-compat": { + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.1.tgz", + "integrity": "sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dev": true, + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" }, "engines": { - "node": ">= 8" + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" } }, "node_modules/cssesc": { @@ -1090,6 +3535,39 @@ "node": ">=4" } }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "dev": true, + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dev": true, + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "dev": true + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -1170,6 +3648,15 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -1247,11 +3734,82 @@ "node": ">=6.0.0" } }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dev": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, + "node_modules/electron-to-chromium": { + "version": "1.4.779", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.779.tgz", + "integrity": "sha512-oaTiIcszNfySXVJzKcjxd2YjPxziAd+GmXyb2HbidCeFo6Z88ygOT7EimlrEQhM2U08VhSrbKhLOXP0kKUCZ6g==", + "dev": true + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -1270,6 +3828,27 @@ "node": ">=10.13.0" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/es-abstract": { "version": "1.23.3", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", @@ -1428,6 +4007,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -2041,6 +4629,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -2377,6 +4974,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, "node_modules/is-async-function": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", @@ -2804,12 +5407,30 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -2917,6 +5538,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -2934,6 +5561,15 @@ "loose-envify": "cli.js" } }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, "node_modules/lru-cache": { "version": "10.2.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", @@ -2942,6 +5578,12 @@ "node": "14 || >=16.14" } }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "dev": true + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3106,6 +5748,22 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -3114,6 +5772,18 @@ "node": ">=0.10.0" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3314,6 +5984,24 @@ "node": ">=6" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "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" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3668,12 +6356,39 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", + "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "dev": true }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, "node_modules/regexp.prototype.flags": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", @@ -3692,6 +6407,44 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dev": true, + "dependencies": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -3936,6 +6689,16 @@ "node": ">=8" } }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dev": true, + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/source-map-js": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", @@ -4197,6 +6960,46 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", + "dev": true + }, + "node_modules/svgo": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", + "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", + "dev": true, + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^5.1.0", + "css-tree": "^2.3.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.0.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/svgo/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, "node_modules/tailwindcss": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz", @@ -4267,6 +7070,15 @@ "node": ">=0.8" } }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -4438,6 +7250,76 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", + "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.2", + "picocolors": "^1.0.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -4647,6 +7529,12 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, "node_modules/yaml": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.2.tgz", diff --git a/package.json b/package.json index c2cd5f9..b38ca7e 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "react-dom": "^18" }, "devDependencies": { + "@svgr/webpack": "^8.1.0", "eslint": "^8", "eslint-config-next": "14.2.3", "postcss": "^8", diff --git a/src/app/lib/TypesHelper.js b/src/app/lib/TypesHelper.js new file mode 100644 index 0000000..8077bc8 --- /dev/null +++ b/src/app/lib/TypesHelper.js @@ -0,0 +1,3 @@ +export const isArray = (arg) => { + return Array.isArray(arg) +} \ No newline at end of file diff --git a/src/app/lib/fetchRequest.js b/src/app/lib/fetchRequest.js index b6f73c2..1b0bc04 100644 --- a/src/app/lib/fetchRequest.js +++ b/src/app/lib/fetchRequest.js @@ -10,7 +10,7 @@ const fetchRequest = async (url, options = {}) => { console.log('jwtDecrypted', jwtDecrypted) const headers = { ...options.headers, - 'Authorization': `Token ${jwtDecrypted.sessionData.token}`, + 'Authorization': jwtDecrypted?.sessionData.token ? `Token ${jwtDecrypted.sessionData.token}` : undefined, 'Content-Type': 'application/json', }; @@ -28,6 +28,7 @@ const fetchRequest = async (url, options = {}) => { } } const data = await response.json() + return { isSuccess: true, errors: null, data: data }; }; diff --git a/src/app/privilege/CreatePrivilegeForm.jsx b/src/app/privilege/CreatePrivilegeForm.jsx new file mode 100644 index 0000000..38ae465 --- /dev/null +++ b/src/app/privilege/CreatePrivilegeForm.jsx @@ -0,0 +1,54 @@ +'use client' +import Loader from '@/components/Loader/Loader' +import React, { useRef, useState } from 'react' +import fetchRequest from '../lib/fetchRequest' + +const CreatePrivilegeForm = ({ appendPrivilege }) => { + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState(null) + const [privilegeName, setPrivilegeName] = useState("") + const handlePrivilegeNameChange = (event) => { + setError("") + setPrivilegeName(event.target.value) + } + const inputRef = useRef(null) + const handleSubmit = async (event) => { + event.preventDefault() + setIsLoading(true) + const { data, errors, isSuccess } = await fetchRequest("/privileges/", { + method: "POST", + body: JSON.stringify({ name: privilegeName }) + }) + if (isSuccess) { + setIsLoading(false) + appendPrivilege(data) + inputRef.current.value = "" + setPrivilegeName("") + } else { + setIsLoading(false) + if (errors.type === "ValidationError") { + if (errors.detail.name) { + setError("Le privilège existe déjà") + } + } + console.log(errors) + } + } + return ( +
+

Ajout d'habilitation

+
+ + +
+

{error}

+
+ +
+
+ ) +} + +export default CreatePrivilegeForm \ No newline at end of file diff --git a/src/app/privilege/PrivilegeTableRow.jsx b/src/app/privilege/PrivilegeTableRow.jsx new file mode 100644 index 0000000..13324e6 --- /dev/null +++ b/src/app/privilege/PrivilegeTableRow.jsx @@ -0,0 +1,112 @@ +'use client' +import React, { useEffect, useRef, useState } from 'react' +import fetchRequest from '../lib/fetchRequest' +import Loader from '@/components/Loader/Loader' +import DeleteIcon from "@/static/image/svg/delete.svg" +import EditIcon from "@/static/image/svg/edit.svg" +import CancelIcon from "@/static/image/svg/cancel.svg" +import CheckIcon from "@/static/image/svg/check.svg" +const PrivilegeTableRow = ({ id, name, setPrivileges }) => { + const [isUpdating, setIsUpdating] = useState(false) + const [privilegeName, setPrivilegeName] = useState(name) + const [loadingStatus, setLoadingStatus] = useState(false) + useEffect(() => { + setPrivilegeName(name) + inputRef.current.value = name + }, [name]) + const handleUpdatePrivilege = async () => { + setLoadingStatus(true) + const { isSuccess, errors, data } = await fetchRequest(`/privileges/${id}/`, { + method: "PATCH", + body: JSON.stringify({ name: privilegeName }) + }) + setLoadingStatus(false) + if (isSuccess) { + setPrivileges((privileges) => privileges.map((element) => element.id === id ? data.data : element)) + setIsUpdating(false) + } else { + // TODO : display the success UI for the UPDATE + console.log(errors) + } + + } + const handleDelete = async () => { + const { isSuccess, errors } = await fetchRequest(`/privileges/${id}/`, { method: "DELETE" }) + if (isSuccess) { + setPrivileges((privileges) => privileges.filter((element) => element.id !== id)) + // TODO : display the success UI for the DELETE + } else { + console.log(errors) + // TODO : display the error alert UI with the error + } + } + const cancelUpdate = () => { + setIsUpdating(false) + setPrivilegeName(name) + inputRef.current.value = name + } + const inputRef = useRef(null) + const rowRef = useRef(null) + const handleUpdateBlur = (event) => { + const eventTarget = event.target + let isInsideRowRef = false; + let element = eventTarget; + while (element !== null) { + if (element === rowRef.current) { + isInsideRowRef = true; + break; + } + if (element.parentElement === null) { + isInsideRowRef = false; + break; + } + element = element.parentElement; + } + if (!isInsideRowRef && element?.classList.contains("privilegeRowSVG")) return; + if (!isInsideRowRef) { + cancelUpdate(); + document.removeEventListener("click", handleUpdateBlur); + } + } + useEffect(() => { + if (isUpdating && inputRef?.current) { + inputRef.current.focus() + document.addEventListener("click", handleUpdateBlur) + } + return () => { + document.removeEventListener("click", handleUpdateBlur) + } + }, [isUpdating]) + return ( + + + setPrivilegeName(event.target.value)} defaultValue={name} type='text' className='disabled:bg-white w-full border-0 rounded-md px-2 enabled:drop-shadow border-none enabled:bg-gray-100 duration-100 h-10 outline-none' /> + + + {!isUpdating + ?
+ + +
+ :
+ + +
+ } + + + ) + +} + +export default PrivilegeTableRow \ No newline at end of file diff --git a/src/app/privilege/page.jsx b/src/app/privilege/page.jsx new file mode 100644 index 0000000..eb6376a --- /dev/null +++ b/src/app/privilege/page.jsx @@ -0,0 +1,55 @@ +'use client' +import React, { useEffect, useState } from 'react' +import CreatePrivilegeForm from './CreatePrivilegeForm' +import fetchRequest from '../lib/fetchRequest' +import { isArray } from '../lib/TypesHelper' +import PrivilegeTableRows from './PrivilegeTableRow' +import Loader from '@/components/Loader/Loader' +const Privilege = () => { + const [privileges, setPrivileges] = useState([]) + const [isLoading, setIsLoading] = useState(true) + useEffect(() => { + const getPrivileges = async () => { + const { data, errors, isSuccess } = await fetchRequest("/privileges") + setIsLoading(false) + if (isSuccess) { + setPrivileges(data) + } else { + console.log(errors) + } + } + getPrivileges() + }, []) + const appendPrivilege = (privilege) => { + setPrivileges((data) => [privilege, ...data]) + } + return ( +
+
+
+ +

List des habilitations

+ {isLoading &&
} + {!isLoading && <> {(!isArray(privileges) || privileges?.length === 0) + ?
+

Pas encore des habilitations

+
+ :
+ + + + + + {privileges.map((element) => { + return + })} +
ProjetAction
+
+ }} +
+
+
+ ) +} + +export default Privilege \ No newline at end of file diff --git a/src/static/image/svg/cancel.svg b/src/static/image/svg/cancel.svg new file mode 100644 index 0000000..1a52a76 --- /dev/null +++ b/src/static/image/svg/cancel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/static/image/svg/check.svg b/src/static/image/svg/check.svg new file mode 100644 index 0000000..f169344 --- /dev/null +++ b/src/static/image/svg/check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/static/image/svg/delete.svg b/src/static/image/svg/delete.svg new file mode 100644 index 0000000..37a3374 --- /dev/null +++ b/src/static/image/svg/delete.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/static/image/svg/edit.svg b/src/static/image/svg/edit.svg new file mode 100644 index 0000000..9f6730f --- /dev/null +++ b/src/static/image/svg/edit.svg @@ -0,0 +1 @@ + \ No newline at end of file -- GitLab From 10a324dd9342fd35c838b23a42fb3475f9694890 Mon Sep 17 00:00:00 2001 From: Oussama El Benney Date: Thu, 23 May 2024 17:04:11 +0100 Subject: [PATCH 08/20] finished projects front workflow --- src/app/lib/fetchRequest.js | 19 +- src/app/lib/fetchRequestServer.js | 21 +- src/app/projects/ProjectForm.jsx | 117 +++++++--- src/app/projects/ProjectList.jsx | 186 +++++++--------- src/app/projects/SideBar.jsx | 359 +++++++++--------------------- src/app/projects/page.jsx | 119 ++++++++-- src/app/ui/Header.jsx | 2 +- 7 files changed, 407 insertions(+), 416 deletions(-) diff --git a/src/app/lib/fetchRequest.js b/src/app/lib/fetchRequest.js index 0a029d7..a52f45d 100644 --- a/src/app/lib/fetchRequest.js +++ b/src/app/lib/fetchRequest.js @@ -1,7 +1,8 @@ //custom fetch tha have the token in header import Cookies from 'js-cookie'; -import { decrypt } from "@/app/lib/session"; +import {decrypt} from "@/app/lib/session"; + const BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000" const fetchRequest = async (url, options = {}) => { const jwtCookie = Cookies.get('session'); @@ -23,13 +24,21 @@ const fetchRequest = async (url, options = {}) => { if (!response.ok) { try { const errorData = await response.json(); - return { isSuccess: false, errors: errorData, data: null } + return {isSuccess: false, errors: errorData, data: null} } catch (error) { - return { isSuccess: false, errors: error, data: null } + return {isSuccess: false, errors: error, data: null} } } - const data = await response.json() - return { isSuccess: true, errors: null, data: data }; + console.log('response', response) + // Check if the response has content before parsing it as JSON + const contentType = response.headers.get('content-type'); + if (contentType && contentType.includes('application/json')) { + const data = await response.json(); + return {isSuccess: true, errors: null, data: data}; + } + // If no JSON content, return null for data + return {isSuccess: true, errors: null, data: null}; + }; export default fetchRequest; diff --git a/src/app/lib/fetchRequestServer.js b/src/app/lib/fetchRequestServer.js index ee50e36..16aafe9 100644 --- a/src/app/lib/fetchRequestServer.js +++ b/src/app/lib/fetchRequestServer.js @@ -1,6 +1,6 @@ // src/app/lib/fetchRequestServer.js -import { cookies as nextCookies } from 'next/headers'; -import { decrypt } from "@/app/lib/session"; +import {cookies as nextCookies} from 'next/headers'; +import {decrypt} from "@/app/lib/session"; const BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000"; @@ -9,7 +9,7 @@ const fetchRequestServer = async (url, options = {}) => { const jwtCookie = cookieStore.get('session')?.value; if (!jwtCookie) { - return { isSuccess: false, errors: "No session cookie found", data: null }; + return {isSuccess: false, errors: "No session cookie found", data: null}; } const jwtDecrypted = await decrypt(jwtCookie); @@ -28,14 +28,21 @@ const fetchRequestServer = async (url, options = {}) => { if (!response.ok) { try { const errorData = await response.json(); - return { isSuccess: false, errors: errorData, data: null }; + return {isSuccess: false, errors: errorData, data: null}; } catch (error) { - return { isSuccess: false, errors: error, data: null }; + return {isSuccess: false, errors: error, data: null}; } } - const data = await response.json(); - return { isSuccess: true, errors: null, data: data }; + // Check if the response has content before parsing it as JSON + const contentType = response.headers.get('content-type'); + if (contentType && contentType.includes('application/json')) { + const data = await response.json(); + return {isSuccess: true, errors: null, data: data}; + } + // If no JSON content, return null for data + return {isSuccess: true, errors: null, data: null}; + }; export default fetchRequestServer; diff --git a/src/app/projects/ProjectForm.jsx b/src/app/projects/ProjectForm.jsx index 418e9b4..c7aa2a4 100644 --- a/src/app/projects/ProjectForm.jsx +++ b/src/app/projects/ProjectForm.jsx @@ -3,37 +3,66 @@ import { useState, useEffect } from 'react'; const ProjectForm = ({ onAddProject, onEditProject, editingProject }) => { const [projectName, setProjectName] = useState(''); const [clientName, setClientName] = useState(''); - const [emails, setEmails] = useState(['']); + const [dateDebut, setDateDebut] = useState(''); + const [dateFin, setDateFin] = useState(''); + const [userIds, setUserIds] = useState(['']); + const [errors, setErrors] = useState({}); useEffect(() => { if (editingProject) { - setProjectName(editingProject.projectName); - setClientName(editingProject.clientName); - setEmails(editingProject.emails); + setProjectName(editingProject.nom); + setClientName(editingProject.nomClient); + setDateDebut(editingProject.dateDebut); + setDateFin(editingProject.dateFin); + setUserIds(editingProject.users || []); } else { setProjectName(''); setClientName(''); - setEmails(['']); + setDateDebut(''); + setDateFin(''); + setUserIds(['']); } }, [editingProject]); - const handleEmailChange = (index, value) => { - const newEmails = [...emails]; - newEmails[index] = value; - setEmails(newEmails); + const handleUserIdChange = (index, value) => { + const newUserIds = [...userIds]; + newUserIds[index] = value; + setUserIds(newUserIds); }; - const handleAddEmail = () => { - setEmails([...emails, '']); + const handleAddUserId = () => { + setUserIds([...userIds, '']); }; - const handleRemoveEmail = (index) => { - setEmails(emails.filter((_, i) => i !== index)); + const handleRemoveUserId = (index) => { + setUserIds(userIds.filter((_, i) => i !== index)); + }; + + const validateForm = () => { + const newErrors = {}; + + if (!projectName) newErrors.projectName = "Project name is required"; + if (!clientName) newErrors.clientName = "Client name is required"; + if (!dateDebut) newErrors.dateDebut = "Start date is required"; + // if (!dateFin) newErrors.dateFin = "End date is required"; + if (!userIds.length || userIds.some(id => !id)) newErrors.userIds = "At least one user ID is required"; + + setErrors(newErrors); + return Object.keys(newErrors).length === 0; }; const handleSubmit = (e) => { e.preventDefault(); - const project = { projectName, clientName, emails }; + + if (!validateForm()) return; + + const project = { + nom: projectName, + nomClient: clientName, + dateDebut, + dateFin, + users: userIds.map(id => parseInt(id)), + }; if (editingProject) { onEditProject(editingProject.id, project); @@ -43,38 +72,67 @@ const ProjectForm = ({ onAddProject, onEditProject, editingProject }) => { }; return ( -
+
- + setProjectName(e.target.value)} - className="mt-1 block w-full px-3 py-2 bg-white border border-gray-300 rounded-md" + className="mt-1 block w-full px-3 py-2 bg-white border border-chicago-300 rounded-md" + required /> + {errors.projectName &&

{errors.projectName}

}
- + setClientName(e.target.value)} - className="mt-1 block w-full px-3 py-2 bg-white border border-gray-300 rounded-md" + className="mt-1 block w-full px-3 py-2 bg-white border border-chicago-300 rounded-md" + required /> + {errors.clientName &&

{errors.clientName}

} +
+
+
+ + setDateDebut(e.target.value)} + className="mt-1 block w-full px-3 py-2 bg-white border border-chicago-300 rounded-md" + required + /> + {errors.dateDebut &&

{errors.dateDebut}

} +
+
+ + setDateFin(e.target.value)} + className="mt-1 block w-full px-3 py-2 bg-white border border-chicago-300 rounded-md" + // required + /> + {errors.dateFin &&

{errors.dateFin}

} +
- - {emails.map((email, index) => ( + + {userIds.map((userId, index) => (
handleEmailChange(index, e.target.value)} - className="flex-1 px-3 py-2 bg-white border border-gray-300 rounded-md" + type="text" + value={userId} + onChange={(e) => handleUserIdChange(index, e.target.value)} + className="flex-1 px-3 py-2 bg-white border border-chicago-300 rounded-md" + required /> + {errors.userIds &&

{errors.userIds}

}
+ } ); }; diff --git a/src/app/projects/SideBar.jsx b/src/app/projects/SideBar.jsx index c182af2..35e67e9 100644 --- a/src/app/projects/SideBar.jsx +++ b/src/app/projects/SideBar.jsx @@ -1,7 +1,7 @@ const SideBar = () => { return (
-
+
{
-
- - -
- -
-
- -
-
); diff --git a/src/app/projects/page.jsx b/src/app/projects/page.jsx index 1db5d0a..adba7fe 100644 --- a/src/app/projects/page.jsx +++ b/src/app/projects/page.jsx @@ -1,54 +1,135 @@ 'use client'; import SideBar from "@/app/projects/SideBar"; -import { useState } from 'react'; +import {useEffect, useState} from 'react'; import ProjectForm from "@/app/projects/ProjectForm"; import ProjectList from "@/app/projects/ProjectList"; +import fetchRequest from "@/app/lib/fetchRequest"; + const Projects = () => { + const [pageUrl, setPageUrl] = useState('/projects/'); const [projects, setProjects] = useState([]); const [editingProject, setEditingProject] = useState(null); + {/* errors from request*/} + const [errors, setErrors] = useState(); + const [loading, setLoading] = useState(false); + const fetchProjects = async () => { + const { isSuccess, errors , data } = await fetchRequest(pageUrl); + if (isSuccess) { + setProjects(data); + setErrors(null); + } else { + console.error("Failed to fetch projects"); + setErrors(errors) + } + }; + useEffect( () => { + + fetchProjects(); + }, [pageUrl]); + + // pageURL - const handleAddProject = (project) => { - project.id = projects.length ? projects[projects.length - 1].id + 1 : 1; - setProjects([...projects, project]); - setEditingProject(null); + const handleAddProject = async (project) => { + setLoading(true); + const { isSuccess, errors , data } = await fetchRequest('/projects/', { + method: 'POST', + body: JSON.stringify(project), + }); + + if (isSuccess) { + setProjects([...projects, data]); + setEditingProject(null); + setErrors(null); + } else { + console.error("Failed to add project"); + setErrors(errors) + } + setLoading(false); }; - const handleEditProject = (id, updatedProject) => { - const updatedProjects = projects.map((project) => - project.id === id ? { ...project, ...updatedProject } : project - ); - setProjects(updatedProjects); - setEditingProject(null); + const handleEditProject = async (id, updatedProject) => { + setLoading(true); + const { isSuccess, errors , data } = await fetchRequest(`/projects/${id}/`, { + method: 'PUT', + body: JSON.stringify(updatedProject), + }); + + if (isSuccess) { + const updatedProjects = projects.map((project) => + project.id === id ? data : project + ); + setProjects(updatedProjects); + setEditingProject(null); + setErrors(null); + } else { + console.error("Failed to edit project"); + setErrors(errors) + } + setLoading(false); }; const handleEditClick = (project) => { setEditingProject(project); }; - const handleDeleteProject = (project) => { - setProjects(projects.filter((p) => p.id !== project.id)); - setEditingProject(null); + const handleDeleteProject = async (project) => { + setLoading(true) + const { isSuccess, errors } = await fetchRequest(`/projects/${project.id}/`, { + method: 'DELETE', + }); + + if (isSuccess) { + // setProjects(projects.filter((p) => p.id !== project.id)); + await fetchProjects(); + setEditingProject(null); + setErrors(null); + } else { + console.error("Failed to delete project"); + setErrors(errors) + } + setLoading(false); }; return ( -
-
+
+
-
-
-

Projets

+
+
+

Projets + {loading ? ... : null}

+ {/*errors from request*/} + {errors && errors.detail && Object.keys(errors.detail).map((key) => ( +
+
+ + + + Something went wrong +
+

+ {errors.detail[key]} +

+
+ ))} +
diff --git a/src/app/ui/Header.jsx b/src/app/ui/Header.jsx index b9d5df2..1137e74 100644 --- a/src/app/ui/Header.jsx +++ b/src/app/ui/Header.jsx @@ -24,7 +24,7 @@ const Header = async () => {
    {isAuth ? ( <> -
  • +
  • {sessionData.username}

  • {/*
  • */} -- GitLab From 8cb765e8fe54ded231eb4b1cd2b064dd1cd554c9 Mon Sep 17 00:00:00 2001 From: Oussama El Benney Date: Fri, 24 May 2024 11:19:51 +0100 Subject: [PATCH 09/20] added feature/add users to projects by search+ confirmation modal --- src/app/projects/ProjectForm.jsx | 152 ++++++++++++++++++------------- src/app/projects/ProjectList.jsx | 42 +++++++-- src/app/ui/ConfirmationModal.jsx | 30 ++++++ 3 files changed, 153 insertions(+), 71 deletions(-) create mode 100644 src/app/ui/ConfirmationModal.jsx diff --git a/src/app/projects/ProjectForm.jsx b/src/app/projects/ProjectForm.jsx index c7aa2a4..350d3d0 100644 --- a/src/app/projects/ProjectForm.jsx +++ b/src/app/projects/ProjectForm.jsx @@ -1,11 +1,14 @@ import { useState, useEffect } from 'react'; +import fetchRequest from '@/app/lib/fetchRequest'; const ProjectForm = ({ onAddProject, onEditProject, editingProject }) => { const [projectName, setProjectName] = useState(''); const [clientName, setClientName] = useState(''); const [dateDebut, setDateDebut] = useState(''); const [dateFin, setDateFin] = useState(''); - const [userIds, setUserIds] = useState(['']); + const [userIds, setUserIds] = useState([]); + const [searchQuery, setSearchQuery] = useState(''); + const [userSuggestions, setUserSuggestions] = useState([]); const [errors, setErrors] = useState({}); useEffect(() => { @@ -20,18 +23,28 @@ const ProjectForm = ({ onAddProject, onEditProject, editingProject }) => { setClientName(''); setDateDebut(''); setDateFin(''); - setUserIds(['']); + setUserIds([]); } }, [editingProject]); - const handleUserIdChange = (index, value) => { - const newUserIds = [...userIds]; - newUserIds[index] = value; - setUserIds(newUserIds); - }; + useEffect(() => { + if (searchQuery.length > 1) { + const fetchUsers = async () => { + const { isSuccess, data } = await fetchRequest(`/search-users/?q=${searchQuery}`); + if (isSuccess) { + setUserSuggestions(data); + } + }; + fetchUsers(); + } else { + setUserSuggestions([]); + } + }, [searchQuery]); - const handleAddUserId = () => { - setUserIds([...userIds, '']); + const handleUserSelect = (user) => { + setUserIds([...userIds, user]); + setSearchQuery(''); + setUserSuggestions([]); }; const handleRemoveUserId = (index) => { @@ -40,12 +53,10 @@ const ProjectForm = ({ onAddProject, onEditProject, editingProject }) => { const validateForm = () => { const newErrors = {}; - if (!projectName) newErrors.projectName = "Project name is required"; if (!clientName) newErrors.clientName = "Client name is required"; if (!dateDebut) newErrors.dateDebut = "Start date is required"; - // if (!dateFin) newErrors.dateFin = "End date is required"; - if (!userIds.length || userIds.some(id => !id)) newErrors.userIds = "At least one user ID is required"; + if (!userIds.length) newErrors.userIds = "At least one user is required"; setErrors(newErrors); return Object.keys(newErrors).length === 0; @@ -61,7 +72,7 @@ const ProjectForm = ({ onAddProject, onEditProject, editingProject }) => { nomClient: clientName, dateDebut, dateFin, - users: userIds.map(id => parseInt(id)), + users: userIds.map(user => user.id), }; if (editingProject) { @@ -71,9 +82,13 @@ const ProjectForm = ({ onAddProject, onEditProject, editingProject }) => { } }; + const isUserChosen = (user) => { + return userIds.some(chosenUser => chosenUser.id === user.id); + }; + return (
    -
    +
    { /> {errors.projectName &&

    {errors.projectName}

    }
    -
    +
    { {errors.clientName &&

    {errors.clientName}

    }
    -
    - - setDateDebut(e.target.value)} - className="mt-1 block w-full px-3 py-2 bg-white border border-chicago-300 rounded-md" - required - /> - {errors.dateDebut &&

    {errors.dateDebut}

    } +
    + + setDateDebut(e.target.value)} + className="mt-1 block w-full px-3 py-2 bg-white border border-chicago-300 rounded-md" + required + /> + {errors.dateDebut &&

    {errors.dateDebut}

    } +
    +
    + + setDateFin(e.target.value)} + className="mt-1 block w-full px-3 py-2 bg-white border border-chicago-300 rounded-md" + /> + {errors.dateFin &&

    {errors.dateFin}

    } +
    -
    - +
    + setDateFin(e.target.value)} + type="text" + value={searchQuery} + onChange={(e) => setSearchQuery(e.target.value)} + placeholder="Type to search users..." className="mt-1 block w-full px-3 py-2 bg-white border border-chicago-300 rounded-md" - // required /> - {errors.dateFin &&

    {errors.dateFin}

    } -
    -
    -
    - - {userIds.map((userId, index) => ( -
    - handleUserIdChange(index, e.target.value)} - className="flex-1 px-3 py-2 bg-white border border-chicago-300 rounded-md" - required - /> - -
    - ))} - + {userSuggestions.length > 0 && ( +
      + {userSuggestions.map(user => ( +
    • !isUserChosen(user) && handleUserSelect(user)} + className={`px-3 py-2 border-b border-chicago-200 cursor-pointer ${isUserChosen(user) ? 'bg-gray-200 cursor-not-allowed' : 'hover:bg-sushi-200'}`} + > + {/*add tick is chosen*/} + {isUserChosen(user) && '✔ '} + {user.username} ({user.email}) {isUserChosen(user) && '(selectionné)'} +
    • + ))} +
    + )} +
    + {userIds.map((user, index) => ( +
    + + +
    + ))} +
    {errors.userIds &&

    {errors.userIds}

    }
    + } +
    +
    +
+ + ) +} + +export default Zone \ No newline at end of file diff --git a/src/static/image/svg/cancel.svg b/src/static/image/svg/cancel.svg new file mode 100644 index 0000000..1a52a76 --- /dev/null +++ b/src/static/image/svg/cancel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/static/image/svg/check.svg b/src/static/image/svg/check.svg new file mode 100644 index 0000000..f169344 --- /dev/null +++ b/src/static/image/svg/check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/static/image/svg/delete.svg b/src/static/image/svg/delete.svg new file mode 100644 index 0000000..37a3374 --- /dev/null +++ b/src/static/image/svg/delete.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/static/image/svg/edit.svg b/src/static/image/svg/edit.svg new file mode 100644 index 0000000..9f6730f --- /dev/null +++ b/src/static/image/svg/edit.svg @@ -0,0 +1 @@ + \ No newline at end of file -- GitLab From 5bffa9a640ce00ba1724383f3c25dddca77e1e30 Mon Sep 17 00:00:00 2001 From: Baligh ZOGHLAMI Date: Fri, 24 May 2024 12:55:23 +0100 Subject: [PATCH 13/20] fix: reset password and change password exceptions --- src/app/auth/change-password/page.jsx | 12 +++++++++--- src/app/auth/forgot-password/page.jsx | 11 +++++++++-- src/app/role/CreateRoleForm.jsx | 1 + src/app/role/page.jsx | 2 +- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/app/auth/change-password/page.jsx b/src/app/auth/change-password/page.jsx index a89f484..6bc46ca 100644 --- a/src/app/auth/change-password/page.jsx +++ b/src/app/auth/change-password/page.jsx @@ -6,6 +6,7 @@ import React, { useEffect, useState } from 'react' import { useSearchParams } from 'next/navigation' import Loader from '@/components/Loader/Loader' import { PASSWORD_REGEX } from '@/app/lib/constants' +import { useNotification } from '@/context/NotificationContext' const ChangePassword = () => { const [isSuccess, setIsSuccess] = useState(false) @@ -14,6 +15,7 @@ const ChangePassword = () => { const [confirmPassword, setConfirmPassword] = useState("") const [isLoading, setIsLoading] = useState(false) const params = useSearchParams(); + const { toggleNotification } = useNotification() const handleChangePassword = async (event) => { event.preventDefault() setIsLoading(true) @@ -30,18 +32,22 @@ const ChangePassword = () => { else { console.log(errors) setIsLoading(false) - if (errors.type === "Validation Error") { + if (errors.type === "ValidationError") { if (errors.detail.token) { setFormErrors(["Le lien que vous avez utilisé pour réinitialiser votre mot de passe est invalide"]) } else { setFormErrors(["Le Mot de passe est invalide"]) } - } else if (errors?.detail?.startsWith("The OTP password entered is not valid")) { + } else if (errors?.detail?.detail?.startsWith("The OTP password entered is not valid")) { setFormErrors(["Le lien que vous avez utilisé pour réinitialiser votre mot de passe est déja utilisé"]) } else { - console.log("Internal Server Error") + toggleNotification({ + type: "error", + message: "Internal Server Error", + visible: true + }) } } } diff --git a/src/app/auth/forgot-password/page.jsx b/src/app/auth/forgot-password/page.jsx index 3895740..d74d5db 100644 --- a/src/app/auth/forgot-password/page.jsx +++ b/src/app/auth/forgot-password/page.jsx @@ -1,6 +1,7 @@ 'use client' import fetchRequest from '@/app/lib/fetchRequest' import Loader from '@/components/Loader/Loader' +import { useNotification } from '@/context/NotificationContext' import Image from 'next/image' import Link from 'next/link' import React, { useState } from 'react' @@ -10,6 +11,7 @@ const ForgotPassword = () => { const [isSuccess, setIsSuccess] = useState(false) const [isLoading, setIsLoading] = useState(false) const [requestErrors, setRequestErrors] = useState([]) + const { toggleNotification } = useNotification() const handleForgetPassword = async (event) => { event.preventDefault() setIsLoading(true) @@ -22,13 +24,18 @@ const ForgotPassword = () => { } else { setIsLoading(false) - if (errors.type === "Validation Error") { + if (errors.type === "ValidationError") { // setRequestErrors(Object.keys(errors.detail).reduce((prev, current) => { // return [...prev, ...errors.detail[current]] // }, [])) + toggleNotification setRequestErrors(["Nous n'avons pas pu trouver de compte associé à cet e-mail. Veuillez essayer une autre adresse e-mail."]) } else { - console.log("Internal server error") + console.log(errors) + toggleNotification({ + type: "error", + message: "Internal Server Error" + }) } } } diff --git a/src/app/role/CreateRoleForm.jsx b/src/app/role/CreateRoleForm.jsx index ea823b7..29e177c 100644 --- a/src/app/role/CreateRoleForm.jsx +++ b/src/app/role/CreateRoleForm.jsx @@ -109,6 +109,7 @@ const CreateRoleForm = ({ appendRole }) => {
{!isAllSelected ? "Sélectionner tout" : "désélectionner"}
+
{privileges?.map((privilege) => { diff --git a/src/app/role/page.jsx b/src/app/role/page.jsx index 1e77b42..78c99ed 100644 --- a/src/app/role/page.jsx +++ b/src/app/role/page.jsx @@ -44,7 +44,7 @@ const Role = () => { :
- + {roles.map((element) => { -- GitLab From 3ac2dc65a948f70bbfc428bd8b2cce4504fa2ed3 Mon Sep 17 00:00:00 2001 From: Oussama El Benney Date: Fri, 24 May 2024 15:08:14 +0100 Subject: [PATCH 14/20] fixed createprivilegeform --- src/app/privilege/CreatePrivilegeForm.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/privilege/CreatePrivilegeForm.jsx b/src/app/privilege/CreatePrivilegeForm.jsx index 1aefd65..d124e2e 100644 --- a/src/app/privilege/CreatePrivilegeForm.jsx +++ b/src/app/privilege/CreatePrivilegeForm.jsx @@ -2,11 +2,13 @@ import Loader from '@/components/Loader/Loader' import React, { useRef, useState } from 'react' import fetchRequest from '../lib/fetchRequest' +import {useNotification} from "@/context/NotificationContext"; const CreatePrivilegeForm = ({ appendPrivilege }) => { const [isLoading, setIsLoading] = useState(false) const [error, setError] = useState(null) - const [privilegeName, setPrivilegeName] = useState("") + const [privilegeName, setPrivilegeName] = useState(""); + const { toggleNotification } = useNotification() const handlePrivilegeNameChange = (event) => { setError("") setPrivilegeName(event.target.value) -- GitLab From 2aa245e61e05e5b7044e17e151887a3e9d780291 Mon Sep 17 00:00:00 2001 From: Baligh ZOGHLAMI Date: Fri, 24 May 2024 17:33:13 +0100 Subject: [PATCH 15/20] feat: add update role --- src/app/privilege/PrivilegeTableRow.jsx | 68 ++++++----- src/app/role/CreateRoleForm.jsx | 72 ++++++----- src/app/role/RoleTableRows.jsx | 151 ++++++------------------ src/app/role/UpdateRoleForm.jsx | 144 ++++++++++++++++++++++ src/app/role/page.jsx | 24 ++-- src/static/image/svg/add.svg | 1 + 6 files changed, 282 insertions(+), 178 deletions(-) create mode 100644 src/app/role/UpdateRoleForm.jsx create mode 100644 src/static/image/svg/add.svg diff --git a/src/app/privilege/PrivilegeTableRow.jsx b/src/app/privilege/PrivilegeTableRow.jsx index 96f1d1d..7537e45 100644 --- a/src/app/privilege/PrivilegeTableRow.jsx +++ b/src/app/privilege/PrivilegeTableRow.jsx @@ -7,11 +7,16 @@ import EditIcon from "@/static/image/svg/edit.svg" import CancelIcon from "@/static/image/svg/cancel.svg" import CheckIcon from "@/static/image/svg/check.svg" import { useNotification } from '@/context/NotificationContext' +import ConfirmationModal from '../ui/ConfirmationModal' const PrivilegeTableRow = ({ id, name, setPrivileges }) => { const [isUpdating, setIsUpdating] = useState(false) const [privilegeName, setPrivilegeName] = useState(name) const [loadingStatus, setLoadingStatus] = useState(false) const { toggleNotification } = useNotification() + const [isModalOpen, setModalOpen] = useState(false) + const showDeletePopup = () => { + setModalOpen(true); + } useEffect(() => { setPrivilegeName(name) inputRef.current.value = name @@ -87,6 +92,7 @@ const PrivilegeTableRow = ({ id, name, setPrivileges }) => { type: "error" }) } + setModalOpen(false) } const cancelUpdate = () => { setIsUpdating(false) @@ -126,33 +132,41 @@ const PrivilegeTableRow = ({ id, name, setPrivileges }) => { } }, [isUpdating]) return ( - - - - + <> + + + + + setModalOpen(false)} + onConfirm={handleDelete} + message={`Voulez-vous vraiment supprimer l'habilitation ?`} + /> + ) } diff --git a/src/app/role/CreateRoleForm.jsx b/src/app/role/CreateRoleForm.jsx index 29e177c..546c0c0 100644 --- a/src/app/role/CreateRoleForm.jsx +++ b/src/app/role/CreateRoleForm.jsx @@ -2,10 +2,9 @@ import Loader from '@/components/Loader/Loader' import React, { useState, useRef, useEffect, useMemo } from 'react' import fetchRequest from '../lib/fetchRequest' import { useNotification } from '@/context/NotificationContext' - -const CreateRoleForm = ({ appendRole }) => { +import CancelIcon from "@/static/image/svg/cancel.svg" +const CreateRoleForm = ({ appendRole, setIsOpen }) => { const [isLoading, setIsLoading] = useState(false) - const [error, setError] = useState(null) const [roleName, setRoleName] = useState("") const [privileges, setPrivileges] = useState(null) const [selectedPrivileges, setSelectedPrivileges] = useState([]) @@ -28,7 +27,6 @@ const CreateRoleForm = ({ appendRole }) => { getPrivileges() }, []) const handleRoleNameChange = (event) => { - setError("") setRoleName(event.target.value) } const inputRef = useRef(null) @@ -50,6 +48,7 @@ const CreateRoleForm = ({ appendRole }) => { message: "Le rôle a été créé avec succès", type: "success" }) + setIsOpen(false) } else { setIsLoading(false) if (errors.type === "ValidationError") { @@ -66,6 +65,7 @@ const CreateRoleForm = ({ appendRole }) => { message: "Erreur de validation de rôle", type: "warning" }) + setIsOpen(false) } } else if (status === 409) { @@ -74,12 +74,21 @@ const CreateRoleForm = ({ appendRole }) => { message: "Roles created with 2 or more same privileges", type: "error" }) + setIsOpen(false) + } else if (errors.detail === "Privilege matching query does not exist.") { + toggleNotification({ + visible: true, + message: "Des privilèges que vous avez utilisés ont été supprimés.", + type: "warning" + }) + setIsOpen(false) } else { toggleNotification({ visible: true, message: "Internal Server Error", type: "error" }) + setIsOpen(false) } console.log(errors) } @@ -98,32 +107,37 @@ const CreateRoleForm = ({ appendRole }) => { } var isAllSelected = useMemo(() => privileges ? privileges.every((element) => selectedPrivileges.find((priv) => priv.id === element.id)) : false, [selectedPrivileges, privileges]) return ( -
-

Ajout de Rôle

-
- - -
-

{error}

- {(privileges) ?
-
- -
{!isAllSelected ? "Sélectionner tout" : "désélectionner"}
- -
-
- {privileges?.map((privilege) => { - const isSelected = selectedPrivileges.find((element) => element.id === privilege.id) !== undefined - return
handlePrivilegeClick(privilege)} key={privilege.id} className={`${!isSelected ? 'text-neutral-400 hover:border-neutral-400 hover:text-neutral-500 border-neutral-300 bg-neutral-100' : 'text-indigo-500 hover:border-indigo-500 hover:text-indigo-500 border-indigo-400 bg-indigo-100'} will-change-contents h-6 text-sm flex items-center justify-center duration-150 delay-75 cursor-pointer text-semibold leading-[0] border-2 py-0.5 rounded-full w-fit px-2`}>{privilege.name}
- })} -
-
:
} -
- +
+
+ setIsOpen(false)} className="h-8 w-8 cursor-pointer absolute top-2 right-2 fill-neutral-600" /> + +

Ajout de Rôle

+
+ + +
+ {(privileges) ?
+
+ +
{!isAllSelected ? "Sélectionner tout" : "désélectionner"}
+
+
+ {privileges.length !== 0 ? privileges?.map((privilege) => { + const isSelected = selectedPrivileges.find((element) => element.id === privilege.id) !== undefined + return
handlePrivilegeClick(privilege)} key={privilege.id} className={`${!isSelected ? 'text-neutral-400 hover:border-neutral-400 hover:text-neutral-500 border-neutral-300 bg-neutral-100' : 'text-indigo-500 hover:border-indigo-500 hover:text-indigo-500 border-indigo-400 bg-indigo-100'} will-change-contents h-6 text-sm flex items-center justify-center duration-150 delay-75 cursor-pointer text-semibold leading-[0] border-2 py-0.5 rounded-full w-fit px-2`}>{privilege.name}
+ }) :
+

Pas encore des habilitations

+
} +
+
:
} +
+ +
+
- +
) } diff --git a/src/app/role/RoleTableRows.jsx b/src/app/role/RoleTableRows.jsx index b6c4ea8..43444b5 100644 --- a/src/app/role/RoleTableRows.jsx +++ b/src/app/role/RoleTableRows.jsx @@ -1,67 +1,19 @@ import React, { useEffect, useRef, useState } from 'react' import fetchRequest from '../lib/fetchRequest' -import Loader from '@/components/Loader/Loader' import DeleteIcon from "@/static/image/svg/delete.svg" import EditIcon from "@/static/image/svg/edit.svg" -import CancelIcon from "@/static/image/svg/cancel.svg" -import CheckIcon from "@/static/image/svg/check.svg" import { useNotification } from '@/context/NotificationContext' -const RoleTableRows = ({ name, setRoles, id, privileges }) => { - const [isUpdating, setIsUpdating] = useState(false) - const [roleName, setRoleName] = useState(name) - const [loadingStatus, setLoadingStatus] = useState(false) +import ConfirmationModal from '../ui/ConfirmationModal' + +const RoleTableRows = ({ name, setRoles, id, privileges, setRoleToUpdate }) => { const { toggleNotification } = useNotification() + const [isModalOpen, setModalOpen] = useState(false); + const inputRef = useRef(null) useEffect(() => { - setRoleName(name) inputRef.current.value = name }, [name]) - const handleUpdatePrivilege = async () => { - setLoadingStatus(true) - const { isSuccess, errors, data, status } = await fetchRequest(`/roles/${id}/`, { - method: "PATCH", - body: JSON.stringify({ name: roleName }) - }) - setLoadingStatus(false) - if (isSuccess) { - setRoles((roles) => roles.map((element) => element.id === id ? data.data : element)) - setIsUpdating(false) - toggleNotification({ - visible: true, - message: "Le rôle a été modifé avec succès", - type: "success" - }) - } else { - if (errors.type === "ValidationError") { - if (errors.detail.name) { - toggleNotification({ - type: "warning", - message: "Le nom de rôle existe déjà", - visible: true, - }) - } - else { - toggleNotification({ - visible: true, - message: "Erreur de validation de rôle", - type: "warning" - }) - } - } else if (status === 404) { - toggleNotification({ - visible: true, - message: "Le rôle n'a pas été trouvé", - type: "warning" - }) - } else { - toggleNotification({ - visible: true, - message: "Internal Server Error", - type: "error" - }) - } - console.log(errors) - } - + const showDeletePopup = () => { + setModalOpen(true); } const handleDelete = async () => { const { isSuccess, errors, status } = await fetchRequest(`/roles/${id}/`, { method: "DELETE" }) @@ -78,7 +30,14 @@ const RoleTableRows = ({ name, setRoles, id, privileges }) => { message: "Le rôle n'a pas été trouvé", type: "warning" }) - } else { + } else if (errors.detail?.indexOf("Cannot delete some instances of model 'Role'") !== -1) { + toggleNotification({ + visible: true, + message: "Impossible de supprimer ce rôle car il est attribué à des utilisateurs", + type: "warning" + }) + } + else { console.log(errors) toggleNotification({ visible: true, @@ -86,73 +45,35 @@ const RoleTableRows = ({ name, setRoles, id, privileges }) => { type: "error" }) } + setModalOpen(false) console.log(errors) } - const cancelUpdate = () => { - setIsUpdating(false) - setRoleName(name) - inputRef.current.value = name - } - const inputRef = useRef(null) - const rowRef = useRef(null) - const handleUpdateBlur = (event) => { - const eventTarget = event.target - let isInsideRowRef = false; - let element = eventTarget; - while (element !== null) { - if (element === rowRef.current) { - isInsideRowRef = true; - break; - } - if (element.parentElement === null) { - isInsideRowRef = false; - break; - } - element = element.parentElement; - } - if (!isInsideRowRef && element?.classList.contains("privilegeRowSVG")) return; - if (!isInsideRowRef) { - cancelUpdate(); - document.removeEventListener("click", handleUpdateBlur); - } - } - useEffect(() => { - if (isUpdating && inputRef?.current) { - inputRef.current.focus() - document.addEventListener("click", handleUpdateBlur) - } - return () => { - document.removeEventListener("click", handleUpdateBlur) - } - }, [isUpdating]) + return ( -
- - + + - + + + setModalOpen(false)} + onConfirm={handleDelete} + message={`Voulez-vous vraiment supprimer le rôle ?`} + /> + + ) } diff --git a/src/app/role/UpdateRoleForm.jsx b/src/app/role/UpdateRoleForm.jsx new file mode 100644 index 0000000..7bf368b --- /dev/null +++ b/src/app/role/UpdateRoleForm.jsx @@ -0,0 +1,144 @@ +import { useNotification } from '@/context/NotificationContext' +import React, { useState, useRef, useEffect, useMemo } from 'react' +import fetchRequest from '../lib/fetchRequest' +import Loader from '@/components/Loader/Loader' +import CancelIcon from "@/static/image/svg/cancel.svg" +import { isArray } from '../lib/TypesHelper' +const UpdateRoleForm = ({ setRoleToUpdate, setRoles, roles, privileges: rolePrivileges, name, id }) => { + const { toggleNotification } = useNotification() + const [loadingStatus, setLoadingStatus] = useState(false) + const [roleName, setRoleName] = useState(name) + const [privileges, setPrivileges] = useState(null) + const [selectedPrivileges, setSelectedPrivileges] = useState(isArray(rolePrivileges) ? rolePrivileges : []) + const inputRef = useRef(null) + console.log("les priv de role ", rolePrivileges) + useEffect(() => { + const getPrivileges = async () => { + const { data, errors, isSuccess } = await fetchRequest("/privileges") + if (isSuccess) { + setPrivileges(data) + } else { + setPrivileges([]) + toggleNotification({ + visible: true, + message: "Internal Server Error", + type: "error" + }) + console.log(errors) + } + } + getPrivileges() + }, []) + const handleSubmit = async (event) => { + event.preventDefault() + setLoadingStatus(true) + const { isSuccess, errors, data, status } = await fetchRequest(`/roles/${id}/`, { + method: "PATCH", + //.filter((element) => privileges.find((prvElement) => prvElement.id === element.id)) + body: JSON.stringify({ name: roleName, privileges: selectedPrivileges.map((element) => element.id) }) + }) + console.log(data) + setLoadingStatus(false) + if (isSuccess) { + setRoles((roles) => roles.map((element) => element.id === id ? data : element)) + toggleNotification({ + visible: true, + message: "Le rôle a été modifé avec succès", + type: "success" + }) + setRoleToUpdate(null) + } else { + if (errors.type === "ValidationError") { + if (errors.detail.name) { + toggleNotification({ + type: "warning", + message: "Le nom de rôle existe déjà", + visible: true, + }) + } + else { + toggleNotification({ + visible: true, + message: "Erreur de validation de rôle", + type: "warning" + }) + setRoleToUpdate(null) + } + } else if (status === 404) { + toggleNotification({ + visible: true, + message: "Le rôle n'a pas été trouvé", + type: "warning" + }) + setRoleToUpdate(null) + } else if (errors.detail === "Privilege matching query does not exist.") { + toggleNotification({ + visible: true, + message: "Des privilèges que vous avez utilisés ont été supprimés.", + type: "warning" + }) + setRoleToUpdate(null) + } + else { + toggleNotification({ + visible: true, + message: "Internal Server Error", + type: "error" + }) + setRoleToUpdate(null) + } + console.log(errors) + } + } + const handleRoleNameChange = (event) => { + setRoleName(event.target.value) + } + const handlePrivilegeClick = (privilege) => { + if (selectedPrivileges.find((element) => element.id === privilege.id)) { + setSelectedPrivileges(selectedPrivileges.filter((element) => element.id !== privilege.id)) + } else { + setSelectedPrivileges([...selectedPrivileges, privilege]) + } + } + const selectAll = () => { + if (privileges.every((element) => selectedPrivileges.find((priv) => priv.id === element.id))) + setSelectedPrivileges([]) + else setSelectedPrivileges(privileges) + } + var isAllSelected = useMemo(() => privileges ? privileges.every((element) => selectedPrivileges.find((priv) => priv.id === element.id)) : false, [selectedPrivileges, privileges]) + return ( +
+
+ setRoleToUpdate(null)} className="h-8 w-8 cursor-pointer absolute top-2 right-2 fill-neutral-600" /> +
+

Modification de Rôle

+
+ + +
+ {(privileges) ?
+
+ +
{!isAllSelected ? "Sélectionner tout" : "désélectionner"}
+
+
+ {privileges.length !== 0 ? privileges?.map((privilege) => { + const isSelected = selectedPrivileges.find((element) => element.id === privilege.id) !== undefined + return
handlePrivilegeClick(privilege)} key={privilege.id} className={`${!isSelected ? 'text-neutral-400 hover:border-neutral-400 hover:text-neutral-500 border-neutral-300 bg-neutral-100' : 'text-indigo-500 hover:border-indigo-500 hover:text-indigo-500 border-indigo-400 bg-indigo-100'} will-change-contents h-6 text-sm flex items-center justify-center duration-150 delay-75 cursor-pointer text-semibold leading-[0] border-2 py-0.5 rounded-full w-fit px-2`}>{privilege.name}
+ }) :
+

Pas encore des habilitations

+
} +
+
:
} +
+ +
+ +
+
+ ) +} + +export default UpdateRoleForm \ No newline at end of file diff --git a/src/app/role/page.jsx b/src/app/role/page.jsx index 78c99ed..a2c5f38 100644 --- a/src/app/role/page.jsx +++ b/src/app/role/page.jsx @@ -5,10 +5,13 @@ import Loader from '@/components/Loader/Loader' import RoleTableRows from './RoleTableRows' import fetchRequest from '../lib/fetchRequest' import { isArray } from '../lib/TypesHelper' - +import AddIcon from "@/static/image/svg/add.svg" +import UpdateRoleForm from './UpdateRoleForm' const Role = () => { const [roles, setRoles] = useState([]) const [isLoading, setIsLoading] = useState(true) + const [openCreatePopup, setOpenCreatePopup] = useState(null) + const [roleToUpdate, setRoleToUpdate] = useState(null) useEffect(() => { const getRoles = async () => { const { data, errors, isSuccess } = await fetchRequest("/roles") @@ -34,21 +37,28 @@ const Role = () => {
- -

List des Roles

+ {openCreatePopup && } + {roleToUpdate && } +
+

List des Roles

+ +
{isLoading &&
} {!isLoading && <> {(!isArray(roles) || roles?.length === 0) ?

Pas encore des roles

- :
+ :
HabilitationRôle Action
- setPrivilegeName(event.target.value)} defaultValue={name} type='text' className='disabled:bg-white w-full border-0 rounded-md px-2 enabled:drop-shadow border-none enabled:bg-gray-100 duration-100 h-10 outline-none' /> - - {!isUpdating - ?
- - -
- :
- - -
- } -
+ setPrivilegeName(event.target.value)} defaultValue={name} type='text' className='disabled:bg-white w-full border-0 rounded-md px-2 enabled:drop-shadow border-none enabled:bg-gray-100 duration-100 h-10 outline-none' /> + + {!isUpdating + ?
+ + +
+ :
+ + +
+ } +
- setRoleName(event.target.value)} defaultValue={name} type='text' className='disabled:bg-white w-full border-0 rounded-md px-2 enabled:drop-shadow border-none enabled:bg-gray-100 duration-100 h-10 outline-none' /> - - {!isUpdating - ?
-
+ + +
+ -
- :
- - -
- } -
- + - {roles.map((element) => { - return + {roles?.map((element) => { + return })}
Rôlerôle Action
diff --git a/src/static/image/svg/add.svg b/src/static/image/svg/add.svg new file mode 100644 index 0000000..37eaf78 --- /dev/null +++ b/src/static/image/svg/add.svg @@ -0,0 +1 @@ + \ No newline at end of file -- GitLab From aac4d4d1b3cd89b7cf87b6a1e34dd8fe302057dd Mon Sep 17 00:00:00 2001 From: Baligh ZOGHLAMI Date: Mon, 27 May 2024 09:13:28 +0100 Subject: [PATCH 16/20] fix: create role with not found privilege --- src/app/role/UpdateRoleForm.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/role/UpdateRoleForm.jsx b/src/app/role/UpdateRoleForm.jsx index 7bf368b..e980dbf 100644 --- a/src/app/role/UpdateRoleForm.jsx +++ b/src/app/role/UpdateRoleForm.jsx @@ -34,8 +34,7 @@ const UpdateRoleForm = ({ setRoleToUpdate, setRoles, roles, privileges: rolePriv setLoadingStatus(true) const { isSuccess, errors, data, status } = await fetchRequest(`/roles/${id}/`, { method: "PATCH", - //.filter((element) => privileges.find((prvElement) => prvElement.id === element.id)) - body: JSON.stringify({ name: roleName, privileges: selectedPrivileges.map((element) => element.id) }) + body: JSON.stringify({ name: roleName, privileges: selectedPrivileges.filter((element) => privileges.find((prvElement) => prvElement.id === element.id)).map((element) => element.id) }) }) console.log(data) setLoadingStatus(false) -- GitLab From 499c3bbcb1db5810753dee5c4c50a01087041a23 Mon Sep 17 00:00:00 2001 From: Baligh ZOGHLAMI Date: Mon, 27 May 2024 09:41:56 +0100 Subject: [PATCH 17/20] fix: display privileges for each role --- src/app/role/RoleTableRows.jsx | 14 +++++++++----- src/app/role/page.jsx | 5 +++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/app/role/RoleTableRows.jsx b/src/app/role/RoleTableRows.jsx index 43444b5..7acdf0b 100644 --- a/src/app/role/RoleTableRows.jsx +++ b/src/app/role/RoleTableRows.jsx @@ -8,10 +8,7 @@ import ConfirmationModal from '../ui/ConfirmationModal' const RoleTableRows = ({ name, setRoles, id, privileges, setRoleToUpdate }) => { const { toggleNotification } = useNotification() const [isModalOpen, setModalOpen] = useState(false); - const inputRef = useRef(null) - useEffect(() => { - inputRef.current.value = name - }, [name]) + const showDeletePopup = () => { setModalOpen(true); } @@ -53,7 +50,14 @@ const RoleTableRows = ({ name, setRoles, id, privileges, setRoleToUpdate }) => { <> - +

{name}

+ + +
+ {privileges?.map((element, index) => { + return
{element.name}
+ })} +
diff --git a/src/app/role/page.jsx b/src/app/role/page.jsx index a2c5f38..67c4bc1 100644 --- a/src/app/role/page.jsx +++ b/src/app/role/page.jsx @@ -39,7 +39,7 @@ const Role = () => {
{openCreatePopup && } {roleToUpdate && } -
+

List des Roles

+
+ ) +} + +export default UserPage \ No newline at end of file -- GitLab From f24a66943101928a6b7a2ca4b74a3b5ea80f72f2 Mon Sep 17 00:00:00 2001 From: Baligh ZOGHLAMI Date: Tue, 28 May 2024 17:59:17 +0100 Subject: [PATCH 19/20] feature: update user --- src/app/lib/constants.js | 3 +- src/app/projects/ProjectForm.jsx | 2 +- src/app/projects/ProjectList.jsx | 154 +++++++++++------------ src/app/projects/page.jsx | 17 +-- src/app/role/RoleTableRows.jsx | 4 +- src/app/user/CreateUserForm.jsx | 192 ++++++++++++++++------------- src/app/user/UpdateUserForm.jsx | 203 ++++++++++++++++++++++++++++++- src/app/user/UserTableRow.jsx | 10 +- src/app/user/page.jsx | 2 +- 9 files changed, 403 insertions(+), 184 deletions(-) diff --git a/src/app/lib/constants.js b/src/app/lib/constants.js index f4da663..889cdc8 100644 --- a/src/app/lib/constants.js +++ b/src/app/lib/constants.js @@ -1 +1,2 @@ -export const PASSWORD_REGEX = /^(?=.*[a-zA-Z])(?=.*\d)(?=.*[^a-zA-Z\d\s]).*$/; \ No newline at end of file +export const PASSWORD_REGEX = /^(?=.*[a-zA-Z])(?=.*\d)(?=.*[^a-zA-Z\d\s]).*$/; +export const EMAIL_REGEX = /^[a-zA-Z0-9._%+-]+@teamwillgroup\.com$/ \ No newline at end of file diff --git a/src/app/projects/ProjectForm.jsx b/src/app/projects/ProjectForm.jsx index 350d3d0..e8a184e 100644 --- a/src/app/projects/ProjectForm.jsx +++ b/src/app/projects/ProjectForm.jsx @@ -72,7 +72,7 @@ const ProjectForm = ({ onAddProject, onEditProject, editingProject }) => { nomClient: clientName, dateDebut, dateFin, - users: userIds.map(user => user.id), + user_ids: userIds.map(user => user.id), }; if (editingProject) { diff --git a/src/app/projects/ProjectList.jsx b/src/app/projects/ProjectList.jsx index df3458c..16d3fe3 100644 --- a/src/app/projects/ProjectList.jsx +++ b/src/app/projects/ProjectList.jsx @@ -1,5 +1,5 @@ import ConfirmationModal from "@/app/ui/ConfirmationModal"; -import {useState} from "react"; +import { useState } from "react"; const ProjectList = ({ projects, onEdit, onDelete, onHandlePageUrl }) => { const [isModalOpen, setModalOpen] = useState(false); @@ -18,90 +18,90 @@ const ProjectList = ({ projects, onEdit, onDelete, onHandlePageUrl }) => { <> - - - - - - + + + + + + - {projects - .sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at)) - .map((project, index) => ( - - - - - - - ))} + {projects + .sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at)) + .map((project, index) => ( + + + + + + + ))}
ProjetMembres de léquipeNom du clientActions
ProjetMembres de léquipeNom du clientActions
{project.nom}{project.users.length}{project.nomClient} - - -
{project.nom}{project.users.length}{project.nomClient} + + +
{/* Pagination */} {projects.count && -
-
    -
  1. - -
  2. -
  3. - 1 -
  4. -
  5. - +
  6. +
  7. + 1 +
  8. +
  9. + -
  10. -
-
+ Next Page + + + + + + +
} {/* Confirmation Modal */} { const [pageUrl, setPageUrl] = useState('/projects/'); const [projects, setProjects] = useState([]); const [editingProject, setEditingProject] = useState(null); - {/* errors from request*/} + {/* errors from request*/ } const [errors, setErrors] = useState(); const [loading, setLoading] = useState(false); const fetchProjects = async () => { - const { isSuccess, errors , data } = await fetchRequest(pageUrl); + const { isSuccess, errors, data } = await fetchRequest(pageUrl); if (isSuccess) { setProjects(data); setErrors(null); @@ -23,16 +23,16 @@ const Projects = () => { setErrors(errors) } }; - useEffect( () => { + useEffect(() => { - fetchProjects(); + fetchProjects(); }, [pageUrl]); // pageURL const handleAddProject = async (project) => { setLoading(true); - const { isSuccess, errors , data } = await fetchRequest('/projects/', { + const { isSuccess, errors, data } = await fetchRequest('/projects/', { method: 'POST', body: JSON.stringify(project), }); @@ -42,6 +42,7 @@ const Projects = () => { setEditingProject(null); setErrors(null); } else { + console.log(errors); console.error("Failed to add project"); setErrors(errors) } @@ -50,7 +51,7 @@ const Projects = () => { const handleEditProject = async (id, updatedProject) => { setLoading(true); - const { isSuccess, errors , data } = await fetchRequest(`/projects/${id}/`, { + const { isSuccess, errors, data } = await fetchRequest(`/projects/${id}/`, { method: 'PUT', body: JSON.stringify(updatedProject), }); @@ -94,7 +95,7 @@ const Projects = () => { return (
- +
diff --git a/src/app/role/RoleTableRows.jsx b/src/app/role/RoleTableRows.jsx index 5c44906..c937dc0 100644 --- a/src/app/role/RoleTableRows.jsx +++ b/src/app/role/RoleTableRows.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react' +import React, { useState } from 'react' import fetchRequest from '../lib/fetchRequest' import DeleteIcon from "@/static/image/svg/delete.svg" import EditIcon from "@/static/image/svg/edit.svg" @@ -50,7 +50,7 @@ const RoleTableRows = ({ name, setRoles, id, privileges, setRoleToUpdate }) => { <> -

{name}

+

{name}

diff --git a/src/app/user/CreateUserForm.jsx b/src/app/user/CreateUserForm.jsx index f06c5ca..a3346f5 100644 --- a/src/app/user/CreateUserForm.jsx +++ b/src/app/user/CreateUserForm.jsx @@ -3,6 +3,7 @@ import Loader from '@/components/Loader/Loader' import fetchRequest from '../lib/fetchRequest' import { useNotification } from '@/context/NotificationContext' import CancelIcon from "@/static/image/svg/cancel.svg" +import { EMAIL_REGEX } from '../lib/constants' @@ -35,7 +36,7 @@ const CreateUserForm = ({ setIsOpen, appendUser }) => { const [isLoading, setIsLoading] = useState(false) const [selectedRole, setSelectedRole] = useState(null) const { toggleNotification } = useNotification() - const [selectProjects, setSelectedProjects] = useState([]) + const [selectedProject, setSelectedProject] = useState([]) const [roles, setRoles] = useState(null) const [projects, setProjects] = useState(null) @@ -70,103 +71,109 @@ const CreateUserForm = ({ setIsOpen, appendUser }) => { getRoles() getProjects() }, []) - const handleFieldChange = (event) => setUserData({ ...userData, [event.target.name]: event.target.value }) - + const handleFieldChange = (event) => { + setUserData({ ...userData, [event.target.name]: event.target.value }) + setErrors({ ...errors, [event.target.name]: "" }) + } + const [errors, setErrors] = useState({ first_name: "", last_name: "", email: "", role: "" }) const [userData, setUserData] = useState({ email: "", password: generateRandomPassword(), first_name: "", last_name: "" }) + + const isValidFields = () => { + const localErrors = { first_name: "", last_name: "", email: "", role: "" } + if (userData.first_name === "") localErrors.first_name = "Le prénom doit être spécifier." + if (userData.last_name === "") localErrors.last_name = "Le nom doit être spécifier." + if (!selectedRole) localErrors.role = "Le rôle doit être spécifier." + if (!userData.email) localErrors.email = "L'email doit être spécifier." + else if (!EMAIL_REGEX.test(userData.email)) localErrors.email = "Votre adresse email n'est pas valide." + setErrors({ ...localErrors }) + return Object.values(localErrors).find((element) => element !== "") === undefined + } const handleSubmit = async (event) => { event.preventDefault() - setIsLoading(true) - const { data, errors, isSuccess, status } = await fetchRequest("/users/", { - method: "POST", - body: JSON.stringify({ ...userData, username: userData.email, role: selectedRole.id, project_ids: selectProjects.map((element) => element.id) }) - }) - if (isSuccess) { - setIsLoading(false) - setSelectedRole(null) - appendUser(data.data) - toggleNotification({ - visible: true, - message: "Le rôle a été créé avec succès", - type: "success" + if (isValidFields()) { + setIsLoading(true) + const { data, errors: requestErrors, isSuccess, status } = await fetchRequest("/users/", { + method: "POST", + body: JSON.stringify({ ...userData, username: userData.email, role: selectedRole, projects: selectedProject }) }) - setIsOpen(false) - } else { - setIsLoading(false) - if (errors.type === "ValidationError") { - if (errors.detail.name) { - toggleNotification({ - visible: true, - message: "Le rôle existe déjà", - type: "warning" - }) + if (isSuccess) { + setIsLoading(false) + setSelectedRole(null) + appendUser(data) + toggleNotification({ + visible: true, + message: "L'utilisateur a été créé avec succès", + type: "success" + }) + setIsOpen(false) + } else { + setIsLoading(false) + if (requestErrors.type === "ValidationError") { + if (requestErrors.detail.email) { + setErrors({ ...errors, email: "Cette adresse email est déjà utilisée." }) + } + else if (requestErrors.detail.role) { + toggleNotification({ + visible: true, + message: "Erreur de validation de rôle (le rôle peut être supprimé)", + type: "warning" + }) + } + else { + toggleNotification({ + visible: true, + message: "Erreur de validation de utilisateur", + type: "warning" + }) + setIsOpen(false) + } } else { toggleNotification({ visible: true, - message: "Erreur de validation de rôle", - type: "warning" + message: "Internal Server Error", + type: "error" }) setIsOpen(false) } + console.log(requestErrors) } - else if (status === 409) { - toggleNotification({ - visible: true, - message: "role created with 2 or more same privileges", - type: "error" - }) - setIsOpen(false) - } else if (errors.detail === "Privilege matching query does not exist.") { - toggleNotification({ - visible: true, - message: "Des privilèges que vous avez utilisés ont été supprimés.", - type: "warning" - }) - setIsOpen(false) - } else { - toggleNotification({ - visible: true, - message: "Internal Server Error", - type: "error" - }) - setIsOpen(false) - } - console.log(errors) } + } - const handleRoleClick = (newRole) => { - if (selectedRole?.id === newRole.id) { - setSelectedRole(null) - } else { - setSelectedRole(newRole) - } + const handleRoleChange = (event) => { + setErrors({ ...errors, role: "" }) + if (event.target.value) + setSelectedRole(event.target.value) + else setSelectedRole(null) } - const handleProjectClick = (project) => { - if (selectProjects.find((element) => element.id === project.id)) { - setSelectedProjects(selectProjects.filter((element) => element.id !== project.id)) - } else { - setSelectedProjects([...selectProjects, project]) - } + const handleProjectChange = (event) => { + if (event.target.value) + setSelectedProject([event.target.value]) + else setSelectedProject([]) } return (
setIsOpen(false)} className="h-8 w-8 cursor-pointer absolute top-2 right-2 fill-neutral-600" />
-

Ajout d utilisateur

+

Ajout d'utilisateur

- - + + +

{errors.last_name}

- - + + +

{errors.first_name}

- + +

{errors.email}

@@ -176,30 +183,43 @@ const CreateUserForm = ({ setIsOpen, appendUser }) => {
-
- {roles.length !== 0 ? roles?.map((role) => { - const isSelected = selectedRole?.id === role.id - return
handleRoleClick(role)} key={role.id} className={`${!isSelected ? 'text-neutral-400 hover:border-neutral-400 hover:text-neutral-500 border-neutral-300 bg-neutral-100' : 'text-indigo-500 hover:border-indigo-500 hover:text-indigo-500 border-indigo-400 bg-indigo-100'} will-change-contents h-6 text-sm flex items-center justify-center duration-150 delay-75 cursor-pointer text-semibold leading-[0] border-2 py-0.5 rounded-full w-fit px-2`}> - {role.name} +
+ {roles.length !== 0 ? +
+ +

{errors.role}

- }) :
-

Pas encore des habilitations

-
} + :
+

Pas encore des rôles

+
}
:
} {(projects) ?
- +
-
- {projects.length !== 0 ? projects?.map((project) => { - const isSelected = selectProjects.find((element) => element.id === project.id) !== undefined - return
handleProjectClick(project)} key={project.id} className={`${!isSelected ? 'text-neutral-400 hover:border-neutral-400 hover:text-neutral-500 border-neutral-300 bg-neutral-100' : 'text-indigo-500 hover:border-indigo-500 hover:text-indigo-500 border-indigo-400 bg-indigo-100'} will-change-contents h-6 text-sm flex items-center justify-center duration-150 delay-75 cursor-pointer text-semibold leading-[0] border-2 py-0.5 rounded-full w-fit px-2`}> - {project.nom} +
+ {projects.length !== 0 ? +
+
- }) :
-

Pas encore des projets

-
} + :
+

Pas encore des projets

+
}
:
}
diff --git a/src/app/user/UpdateUserForm.jsx b/src/app/user/UpdateUserForm.jsx index d214f18..8d06a96 100644 --- a/src/app/user/UpdateUserForm.jsx +++ b/src/app/user/UpdateUserForm.jsx @@ -1,8 +1,205 @@ -import React from 'react' +import React, { useState, useEffect } from 'react' +import Loader from '@/components/Loader/Loader' +import fetchRequest from '../lib/fetchRequest' +import { useNotification } from '@/context/NotificationContext' +import CancelIcon from "@/static/image/svg/cancel.svg" +import { EMAIL_REGEX } from '../lib/constants' -const UpdateUserForm = () => { + +const UpdateUserForm = ({ setUserToUpdate, userToUpdate, setUsers }) => { + const [isLoading, setIsLoading] = useState(false) + const [selectedRole, setSelectedRole] = useState(userToUpdate.role?.id || null) + const { toggleNotification } = useNotification() + const [selectedProject, setSelectedProject] = useState(userToUpdate.projects?.length ? [userToUpdate.projects[0].id] : []) + + const [roles, setRoles] = useState(null) + const [projects, setProjects] = useState(null) + useEffect(() => { + const getRoles = async () => { + const { data, errors, isSuccess } = await fetchRequest("/roles/") + if (isSuccess) { + setRoles(data) + } else { + setRoles([]) + toggleNotification({ + visible: true, + message: "Internal Server Error", + type: "error" + }) + console.log(errors) + } + } + const getProjects = async () => { + const { isSuccess, errors, data } = await fetchRequest("/projects/"); + if (isSuccess) { + setProjects(data); + } else { + toggleNotification({ + visible: true, + message: "Internal Server Error", + type: "error" + }) + console.log(errors) + } + } + getRoles() + getProjects() + }, []) + const handleFieldChange = (event) => { + setUserData({ ...userData, [event.target.name]: event.target.value }) + setErrors({ ...errors, [event.target.name]: "" }) + } + const [errors, setErrors] = useState({ first_name: "", last_name: "", email: "", role: "" }) + const [userData, setUserData] = useState({ email: userToUpdate.email, first_name: userToUpdate.first_name, last_name: userToUpdate.last_name }) + + const isValidFields = () => { + const localErrors = { first_name: "", last_name: "", email: "", role: "" } + if (userData.first_name === "") localErrors.first_name = "Le prénom doit être spécifier." + if (userData.last_name === "") localErrors.last_name = "Le nom doit être spécifier." + if (!selectedRole) localErrors.role = "Le rôle doit être spécifier." + if (!userData.email) localErrors.email = "L'email doit être spécifier." + else if (!EMAIL_REGEX.test(userData.email)) localErrors.email = "Votre adresse email n'est pas valide." + setErrors({ ...localErrors }) + return Object.values(localErrors).find((element) => element !== "") === undefined + } + const handleSubmit = async (event) => { + event.preventDefault() + if (isValidFields()) { + setIsLoading(true) + const { data, errors: requestErrors, isSuccess, status } = await fetchRequest(`/users/${userToUpdate.id}/`, { + method: "PATCH", + body: JSON.stringify({ ...userData, username: userData.email, role: selectedRole, projects: selectedProject }) + }) + if (isSuccess) { + setUsers((users) => users.map((element) => element.id === userToUpdate.id ? data : element)) + setIsLoading(false) + setSelectedRole(null) + toggleNotification({ + visible: true, + message: "L'utilisateur a été modifié avec succès", + type: "success" + }) + setUserToUpdate(null) + } else { + setIsLoading(false) + if (requestErrors.type === "ValidationError") { + if (requestErrors.detail.email) { + setErrors({ ...errors, email: "Cette adresse email est déjà utilisée." }) + } + else if (requestErrors.detail.role) { + toggleNotification({ + visible: true, + message: "Erreur de validation de rôle (le rôle peut être supprimé)", + type: "warning" + }) + } + else { + toggleNotification({ + visible: true, + message: "Erreur de validation de utilisateur", + type: "warning" + }) + setUserToUpdate(null) + } + } + else { + toggleNotification({ + visible: true, + message: "Internal Server Error", + type: "error" + }) + setUserToUpdate(null) + } + console.log(requestErrors) + } + } + + } + const handleRoleChange = (event) => { + setErrors({ ...errors, role: "" }) + if (event.target.value) + setSelectedRole(event.target.value) + else setSelectedRole(null) + } + const handleProjectChange = (event) => { + if (event.target.value) + setSelectedProject([event.target.value]) + else setSelectedProject([]) + } return ( -
UpdateUserForm
+
+
+ setUserToUpdate(null)} className="h-8 w-8 cursor-pointer absolute top-2 right-2 fill-neutral-600" /> + +

Modification d'utilisateur

+
+
+ + +

{errors.last_name}

+
+
+ + +

{errors.first_name}

+
+
+
+ + +

{errors.email}

+
+ {(roles) ?
+
+ +
+
+ {roles.length !== 0 ? +
+ +

{errors.role}

+
+ :
+

Pas encore des rôles

+
} +
+
:
} + {(projects) ?
+
+ +
+
+ {projects.length !== 0 ? +
+ +
+ :
+

Pas encore des projets

+
} +
+
:
} +
+ +
+ +
+
) } diff --git a/src/app/user/UserTableRow.jsx b/src/app/user/UserTableRow.jsx index 1860ee8..e057532 100644 --- a/src/app/user/UserTableRow.jsx +++ b/src/app/user/UserTableRow.jsx @@ -5,7 +5,7 @@ import ConfirmationModal from '../ui/ConfirmationModal' import { useNotification } from '@/context/NotificationContext' import fetchRequest from '../lib/fetchRequest' -const UserTableRow = ({ email, last_name, first_name, id, setUsers, role, projects }) => { +const UserTableRow = ({ email, last_name, first_name, id, setUsers, role, projects, setUserToUpdate }) => { const { toggleNotification } = useNotification() const [isModalOpen, setModalOpen] = useState(false); const showDeletePopup = () => { @@ -23,13 +23,13 @@ const UserTableRow = ({ email, last_name, first_name, id, setUsers, role, projec } else if (status == 404) { toggleNotification({ visible: true, - message: "L'utilisateur n'a pas été trouvé", + message: "L'utilisateur n'a pas été trouvé", type: "warning" }) - } else if (errors.detail?.indexOf("Cannot delete some instances of model 'Role'") !== -1) { + } else if (errors.detail?.indexOf("Cannot delete some instances of model") !== -1) { toggleNotification({ visible: true, - message: "Impossible de supprimer cet utilisateur car il est attribué à des utilisateurs", + message: "Impossible de supprimer cet utilisateur car il est attribué à d'autre objets", type: "warning" }) } @@ -66,7 +66,7 @@ const UserTableRow = ({ email, last_name, first_name, id, setUsers, role, projec
-
- + :
}
) diff --git a/src/app/role/UpdateRoleForm.jsx b/src/app/role/UpdateRoleForm.jsx index e980dbf..348ebfc 100644 --- a/src/app/role/UpdateRoleForm.jsx +++ b/src/app/role/UpdateRoleForm.jsx @@ -109,13 +109,13 @@ const UpdateRoleForm = ({ setRoleToUpdate, setRoles, roles, privileges: rolePriv
setRoleToUpdate(null)} className="h-8 w-8 cursor-pointer absolute top-2 right-2 fill-neutral-600" /> -
+ {(privileges) ?

Modification de Rôle

- {(privileges) ?
+
{!isAllSelected ? "Sélectionner tout" : "désélectionner"}
@@ -128,13 +128,13 @@ const UpdateRoleForm = ({ setRoleToUpdate, setRoles, roles, privileges: rolePriv

Pas encore des habilitations

}
-
:
} +
- + :
}
) diff --git a/src/app/user/CreateUserForm.jsx b/src/app/user/CreateUserForm.jsx index a3346f5..1b4540d 100644 --- a/src/app/user/CreateUserForm.jsx +++ b/src/app/user/CreateUserForm.jsx @@ -156,7 +156,7 @@ const CreateUserForm = ({ setIsOpen, appendUser }) => {
setIsOpen(false)} className="h-8 w-8 cursor-pointer absolute top-2 right-2 fill-neutral-600" /> -
+ {(roles && projects) ?

Ajout d'utilisateur

@@ -179,7 +179,7 @@ const CreateUserForm = ({ setIsOpen, appendUser }) => {
- {(roles) ?
+
@@ -200,8 +200,8 @@ const CreateUserForm = ({ setIsOpen, appendUser }) => {

Pas encore des rôles

}
-
:
} - {(projects) ?
+
+
@@ -221,13 +221,13 @@ const CreateUserForm = ({ setIsOpen, appendUser }) => {

Pas encore des projets

}
-
:
} +
- + :
}
) diff --git a/src/app/user/UpdateUserForm.jsx b/src/app/user/UpdateUserForm.jsx index 8d06a96..1e62cce 100644 --- a/src/app/user/UpdateUserForm.jsx +++ b/src/app/user/UpdateUserForm.jsx @@ -130,7 +130,7 @@ const UpdateUserForm = ({ setUserToUpdate, userToUpdate, setUsers }) => {
setUserToUpdate(null)} className="h-8 w-8 cursor-pointer absolute top-2 right-2 fill-neutral-600" /> -
+ {(roles && projects) ?

Modification d'utilisateur

@@ -149,7 +149,7 @@ const UpdateUserForm = ({ setUserToUpdate, userToUpdate, setUsers }) => {

{errors.email}

- {(roles) ?
+
@@ -170,8 +170,8 @@ const UpdateUserForm = ({ setUserToUpdate, userToUpdate, setUsers }) => {

Pas encore des rôles

}
-
:
} - {(projects) ?
+
+
@@ -191,13 +191,13 @@ const UpdateUserForm = ({ setUserToUpdate, userToUpdate, setUsers }) => {

Pas encore des projets

}
-
:
} +
- + :
}
) diff --git a/src/app/user/page.jsx b/src/app/user/page.jsx index edba6ff..de82ec7 100644 --- a/src/app/user/page.jsx +++ b/src/app/user/page.jsx @@ -1,5 +1,5 @@ 'use client'; -import React, { useEffect, useState } from 'react' +import React, { useEffect, useState, useDeferredValue } from 'react' import CreateUserForm from './CreateUserForm' import UpdateUserForm from './UpdateUserForm' import AddIcon from "@/static/image/svg/add.svg" @@ -8,34 +8,49 @@ import fetchRequest from '../lib/fetchRequest'; import { isArray } from '../lib/TypesHelper'; import { useNotification } from '@/context/NotificationContext'; import UserTableRow from './UserTableRow'; - +import { PAGINATION_SIZE } from '../lib/constants'; +import ArrowRightIcon from "@/static/image/svg/chevron-right.svg" +import ArrowLeftIcon from "@/static/image/svg/chevron-left.svg" const UserPage = () => { const [users, setUsers] = useState([]) + const [paginationData, setPaginationData] = useState(null) const [isLoading, setIsLoading] = useState(true) const [openCreatePopup, setOpenCreatePopup] = useState(null) const [userToUpdate, setUserToUpdate] = useState(null) const { toggleNotification } = useNotification() - + const [query, setQuery] = useState(''); + const getUsers = async (pageNumber = 1, signal) => { + setIsLoading(true) + if (search) var { data, errors, isSuccess } = await fetchRequest(`/users/?page=${pageNumber}&search=${query}`, { signal: signal }) + else var { data, errors, isSuccess } = await fetchRequest(`/users/?page=${pageNumber}`) + setIsLoading(false) + if (isSuccess) { + console.log(data) + setUsers(data.results) + setPaginationData({ pagesNumber: Math.ceil((data.count || 0) / PAGINATION_SIZE), currentPage: pageNumber }) + } else { + toggleNotification({ + visible: true, + message: "Internal Server Error", + type: "error" + }) + } + } useEffect(() => { - const getUsers = async () => { - const { data, errors, isSuccess } = await fetchRequest("/users") - setIsLoading(false) - if (isSuccess) { - console.log(data) - setUsers(data) - } else { - toggleNotification({ - visible: true, - message: "Internal Server Error", - type: "error" - }) - } + const controller = new AbortController() + const signal = controller.signal + getUsers(1, signal) + return () => { + controller.abort("fetching another users") } - getUsers() - }, []) + }, [query]) const appendUser = (newUser) => { setUsers([newUser, ...users]) } + + const handleSearchChange = (event) => { + setQuery(event.target.value) + } return (
@@ -48,12 +63,16 @@ const UserPage = () => {

Utilisateur

- {(isLoading) &&
} +
+ +
+ {(isLoading) &&
} {(!isLoading) && <> {(!isArray(users) || users?.length === 0) - ?
+ ?

Pas encore des utilisateurs

- :
+ : +
@@ -66,8 +85,31 @@ const UserPage = () => { return })}
Nom
-
- }} +
} + + } +
+ {(paginationData) &&
+ {(paginationData.currentPage > 1) &&
getUsers(paginationData.currentPage - 1)} className='flex cursor-pointer hover:bg-neutral-200 duration-150 delay-75 h-8 w-9 items-center justify-center'> + +
} + {paginationData && Array.from({ length: paginationData.pagesNumber }, (_, index) => index).map((element, index) => { + if (element + 1 === paginationData.currentPage + 3) return

+ ... +

+ else if (paginationData.currentPage === element + 1) return

+ {element + 1} +

+ else if (paginationData.currentPage < element + 3 && element - paginationData.currentPage < 3) return

getUsers(element + 1)} key={element} className='h-8 w-9 hover:bg-neutral-200 cursor-pointer duration-150 delay-75 font-bold text-neutral-700 text-sm flex items-center justify-center' > + {element + 1} +

+ else return <> + })} + {(paginationData.currentPage !== paginationData.pagesNumber) &&
getUsers(paginationData.currentPage + 1)} className='flex h-8 w-9 items-center hover:bg-neutral-200 duration-150 delay-75 cursor-pointer justify-center'> + +
} +
} +
diff --git a/src/static/image/svg/chevron-left.svg b/src/static/image/svg/chevron-left.svg new file mode 100644 index 0000000..bfbedda --- /dev/null +++ b/src/static/image/svg/chevron-left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/static/image/svg/chevron-right.svg b/src/static/image/svg/chevron-right.svg new file mode 100644 index 0000000..49c8a53 --- /dev/null +++ b/src/static/image/svg/chevron-right.svg @@ -0,0 +1 @@ + \ No newline at end of file -- GitLab