diff --git a/backend/backend/settings.py b/backend/backend/settings.py index ebd0133ba..328cd01c7 100644 --- a/backend/backend/settings.py +++ b/backend/backend/settings.py @@ -258,6 +258,7 @@ "testing.apps.TestingConfig", "django_probes", "dissertations.apps.DissertationsConfig", + "testimonials.apps.TestimonialsConfig" ] + (["django_gsuite_email"] if "django_gsuite_email" in EMAIL_BACKEND else []) MIDDLEWARE = [ diff --git a/backend/backend/urls.py b/backend/backend/urls.py index 5556a57a0..b151998c6 100644 --- a/backend/backend/urls.py +++ b/backend/backend/urls.py @@ -49,6 +49,7 @@ def wrapper(request): path("api/document/", include("documents.urls")), path("api/stats/", include("stats.urls")), path("api/dissertations/", include("dissertations.urls")), + path("api/testimonials/", include("testimonials.urls")), re_path( r"^static/(?P.*)$", views.cached_serve, diff --git a/backend/categories/models.py b/backend/categories/models.py index d38e5eba6..8ff82fe0b 100644 --- a/backend/categories/models.py +++ b/backend/categories/models.py @@ -2,6 +2,7 @@ class Category(models.Model): + id = models.AutoField(primary_key=True) displayname = models.CharField(max_length=256) slug = models.CharField(max_length=256, unique=True) form = models.CharField( diff --git a/backend/categories/urls.py b/backend/categories/urls.py index bb3047774..803d19f09 100644 --- a/backend/categories/urls.py +++ b/backend/categories/urls.py @@ -4,6 +4,7 @@ urlpatterns = [ path("list/", views.list_categories, name="list"), + path("listwithid/", views.list_categories_with_id, name="listwithid"), path("listwithmeta/", views.list_categories_with_meta, name="listwithmeta"), path("listonlyadmin/", views.list_categories_only_admin, name="listonlyadmin"), path("add/", views.add_category, name="add"), diff --git a/backend/categories/views.py b/backend/categories/views.py index 30715346e..9543e218f 100644 --- a/backend/categories/views.py +++ b/backend/categories/views.py @@ -9,6 +9,20 @@ from util import response, func_cache +@response.request_get() +def list_categories_with_id(request): + categories = Category.objects.order_by("displayname").all() + res = [ + { + "category_id": cat.id, + "displayname": cat.displayname, + "slug": cat.slug, + "euclid_codes": [euclidcode.code for euclidcode in cat.euclid_codes.all()] + } + for cat in categories + ] + return response.success(value=res) + @response.request_get() def list_categories(request): categories = Category.objects.order_by("displayname").all() @@ -27,6 +41,7 @@ def list_categories_with_meta(request): categories = Category.objects.select_related("meta").order_by("displayname").all() res = [ { + "category_id": cat.id, "displayname": cat.displayname, "slug": cat.slug, "examcountpublic": cat.meta.examcount_public, @@ -153,6 +168,7 @@ def list_exams(request, slug): def get_category_data(request, cat): res = { + "category_id": cat.id, "displayname": cat.displayname, "slug": cat.slug, "admins": [], diff --git a/backend/ediauth/auth_backend.py b/backend/ediauth/auth_backend.py index 2255160bb..cfe1445f3 100644 --- a/backend/ediauth/auth_backend.py +++ b/backend/ediauth/auth_backend.py @@ -71,6 +71,7 @@ def add_auth_to_request(request: HttpRequest): NotificationType.NEW_COMMENT_TO_COMMENT, NotificationType.NEW_ANSWER_TO_ANSWER, NotificationType.NEW_COMMENT_TO_DOCUMENT, + NotificationType.UPDATE_TO_TESTIMONIAL_APPROVAL_STATUS ]: setting = NotificationSetting(user=user, type=type_.value) setting.save() diff --git a/backend/notifications/models.py b/backend/notifications/models.py index b650bcf5c..7c4453826 100644 --- a/backend/notifications/models.py +++ b/backend/notifications/models.py @@ -8,6 +8,7 @@ class NotificationType(enum.Enum): NEW_COMMENT_TO_COMMENT = 2 NEW_ANSWER_TO_ANSWER = 3 NEW_COMMENT_TO_DOCUMENT = 4 + UPDATE_TO_TESTIMONIAL_APPROVAL_STATUS = 5 class Notification(models.Model): diff --git a/backend/notifications/notification_util.py b/backend/notifications/notification_util.py index 721ac9878..814ea822c 100644 --- a/backend/notifications/notification_util.py +++ b/backend/notifications/notification_util.py @@ -33,6 +33,18 @@ def send_notification( associated_data: Answer, ) -> None: ... +@overload +def send_notification( + sender: User, + receiver: User, + type_: Literal[ + NotificationType.UPDATE_TO_TESTIMONIAL_APPROVAL_STATUS + ], + title: str, + message: str, + associated_data: str, #Update to Testimonial +) -> None: ... + @overload def send_notification( @@ -51,10 +63,11 @@ def send_notification( type_: NotificationType, title: str, message: str, - associated_data: Union[Answer, Document], + associated_data: Union[Answer, Document, str], ): if sender == receiver: return + if is_notification_enabled(receiver, type_): send_inapp_notification( sender, receiver, type_, title, message, associated_data @@ -97,7 +110,7 @@ def send_email_notification( type_: NotificationType, title: str, message: str, - data: Union[Document, Answer], + data: Union[Document, Answer, str], ): """If the user has email notifications enabled, send an email notification. @@ -114,16 +127,34 @@ def send_email_notification( ) ): return - - send_mail( - f"BetterInformatics: {title} / {data.display_name if isinstance(data, Document) else data.answer_section.exam.displayname}", + + email_body = "" + if isinstance(data, Document): + email_body = f"BetterInformatics: {title} / {data.display_name}", ( f"Hello {receiver.profile.display_username}!\n" f"{message}\n\n" f"View it in context here: {get_absolute_notification_url(data)}" - ), + ) + elif isinstance(data, str): + email_body = f"BetterInformatics: {title}", + ( + f"Hello {receiver.profile.display_username}!\n" + f"{message}\n\n" + ) + else: + email_body = f"BetterInformatics: {title} / {data.answer_section.exam.displayname}", + ( + f"Hello {receiver.profile.display_username}!\n" + f"{message}\n\n" + f"View it in context here: {get_absolute_notification_url(data)}" + ) + + send_mail( + email_body, f'"{sender.username} (via BetterInformatics)" <{settings.VERIF_CODE_FROM_EMAIL_ADDRESS}>', - [receiver.email], + from_email=[sender.email], + recipient_list=[receiver.email], fail_silently=False, ) @@ -200,3 +231,13 @@ def new_comment_to_document(document: Document, new_comment: DocumentComment): "A new comment was added to your document.\n\n{}".format(new_comment.text), document, ) + +def update_to_testimonial_status(sender, receiver, title, message): + send_notification( + sender, + receiver, + NotificationType.UPDATE_TO_TESTIMONIAL_APPROVAL_STATUS, + title, + message, + "" + ) \ No newline at end of file diff --git a/backend/testimonials/__init__.py b/backend/testimonials/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/testimonials/admin.py b/backend/testimonials/admin.py new file mode 100644 index 000000000..8c38f3f3d --- /dev/null +++ b/backend/testimonials/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/backend/testimonials/apps.py b/backend/testimonials/apps.py new file mode 100644 index 000000000..64fc74b83 --- /dev/null +++ b/backend/testimonials/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class TestimonialsConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "testimonials" diff --git a/backend/testimonials/migrations/__init__.py b/backend/testimonials/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/testimonials/models.py b/backend/testimonials/models.py new file mode 100644 index 000000000..e531cecc0 --- /dev/null +++ b/backend/testimonials/models.py @@ -0,0 +1,34 @@ +from django.db import models +from django.db.models import Q, UniqueConstraint + + +class ApprovalStatus(models.IntegerChoices): + APPROVED = 0, "Approved" + PENDING = 1, "Pending" + REJECTED = 2, "Rejected" + + +class Testimonial(models.Model): + id = models.AutoField(primary_key=True) + author = models.ForeignKey("auth.User", on_delete=models.CASCADE, default="") + category = models.ForeignKey( # Link Testimonial to a Category + "categories.Category", + on_delete=models.CASCADE# Delete testimonials if category is deleted + ) + testimonial = models.TextField() + year_taken = models.IntegerField() + approval_status = models.IntegerField( + choices=ApprovalStatus.choices, + default=ApprovalStatus.PENDING, + ) + + class Meta: + #Only one row with (author, course) where approval_status is APPROVED or PENDING can exist. + #Multiple rejected rows can exist for (author, course) combination. + constraints = [ + UniqueConstraint( + fields=["author", "category"], + condition=Q(approval_status__in=[ApprovalStatus.APPROVED, ApprovalStatus.PENDING]), + name="unique_approved_or_pending_per_author_course", + ), + ] \ No newline at end of file diff --git a/backend/testimonials/tests.py b/backend/testimonials/tests.py new file mode 100644 index 000000000..7ce503c2d --- /dev/null +++ b/backend/testimonials/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/backend/testimonials/urls.py b/backend/testimonials/urls.py new file mode 100644 index 000000000..9dbf679d7 --- /dev/null +++ b/backend/testimonials/urls.py @@ -0,0 +1,9 @@ +from django.urls import path +from testimonials import views +urlpatterns = [ + path("listtestimonials/", views.testimonial_metadata, name="testimonial_list"), + path("gettestimonial/", views.get_testimonial_metadata_by_code, name="get_testimonial"), + path('addtestimonial/', views.add_testimonial, name='add_testimonial'), + path('removetestimonial/', views.remove_testimonial, name='remove_testimonial'), + path('updatetestimonialapproval/', views.update_testimonial_approval_status, name="update_testimonial_approval_status") +] \ No newline at end of file diff --git a/backend/testimonials/views.py b/backend/testimonials/views.py new file mode 100644 index 000000000..97d437a7f --- /dev/null +++ b/backend/testimonials/views.py @@ -0,0 +1,140 @@ +from util import response +from ediauth import auth_check +from testimonials.models import Testimonial, ApprovalStatus +from categories.models import Category +from django.contrib.auth.models import User +from django.shortcuts import get_object_or_404 +from datetime import timedelta +from django.http import JsonResponse +from django.views.decorators.csrf import csrf_exempt +from notifications.notification_util import update_to_testimonial_status +import ediauth.auth_check as auth_check + +@response.request_get() +@auth_check.require_login +def testimonial_metadata(request): + testimonials = Testimonial.objects.all() + res = [ + { + "author_id": testimonial.author.username, + "author_diplay_name": testimonial.author.profile.display_username, + "category_id": testimonial.category.id, + "euclid_codes": [euclidcode.code for euclidcode in testimonial.category.euclid_codes.all()], + "course_name": testimonial.category.displayname, + "testimonial": testimonial.testimonial, + "testimonial_id": testimonial.id, + "year_taken": testimonial.year_taken, + "approval_status": testimonial.approval_status, + } + for testimonial in testimonials + ] + return response.success(value=res) + +@response.request_get("category_id") +@auth_check.require_login +def get_testimonial_metadata_by_code(request): + category_id = request.POST.get('category_id') + try: + category_obj = Category.objects.get(id=category_id) + except Category.DoesNotExist: + return response.not_possible(f"The category with id {category_id} does not exist in the database.") + testimonials = Testimonial.objects.filter(category=category_obj) + res = [ + { + "author_id": testimonial.author.username, + "author_diplay_name": testimonial.author.profile.display_username, + "category_id": testimonial.category.id, + "euclid_codes": testimonial.category.euclid_codes, + "course_name": testimonial.category.displayname, + "testimonial": testimonial.testimonial, + "testimonial_id": testimonial.id, + "year_taken": testimonial.year_taken, + "approval_status": testimonial.approval_status, + } + for testimonial in testimonials + ] + return response.success(value=res) + +@response.request_post("category_id", "year_taken", optional=True) +@auth_check.require_login +def add_testimonial(request): + author = request.user + category_id = request.POST.get('category_id') #course code instead of course name + year_taken = request.POST.get('year_taken') + testimonial = request.POST.get('testimonial') + + if not author: + return response.not_possible("Missing argument: author") + if not year_taken: + return response.not_possible("Missing argument: year_taken") + if not testimonial: + return response.not_possible("Missing argument: testimonial") + + testimonials = Testimonial.objects.all() + category_obj = Category.objects.get(id=category_id) + + for t in testimonials: + if t.author == author and t.category == category_obj and (t.approval_status == ApprovalStatus.APPROVED): + return response.not_possible("You have written a testimonial for this course that has been approved.") + elif t.author == author and t.category == category_obj and (t.approval_status == ApprovalStatus.PENDING): + return response.not_possible("You have written a testimonial for this course that is currently pending approval.") + + testimonial = Testimonial.objects.create( + author=author, + category=category_obj, + year_taken=year_taken, + approval_status= ApprovalStatus.PENDING, + testimonial=testimonial, + ) + + return response.success(value={"testimonial_id" : testimonial.id, "approved" : False}) + +@response.request_post("username", 'testimonial_id', optional=True) +@auth_check.require_login +def remove_testimonial(request): + username = request.POST.get('username') + testimonial_id = request.POST.get('testimonial_id') + + testimonial = Testimonial.objects.filter(id=testimonial_id) #Since id is primary key, always returns 1 or none. + + if not testimonial: + return response.not_possible("Testimonial not found for author: " + username + " with id " + testimonial_id) + + if not (testimonial[0].author == request.user or auth_check.has_admin_rights(request)): + return response.not_possible("No permission to delete this.") + + testimonial.delete() + return response.success(value="Deleted Testimonial " + str(testimonial)) + +@response.request_post("title", "message", optional=True) +@auth_check.require_login +def update_testimonial_approval_status(request): + sender = request.user + has_admin_rights = auth_check.has_admin_rights(request) + testimonial_author = request.POST.get('author') + receiver = get_object_or_404(User, username=testimonial_author) + testimonial_id = request.POST.get('testimonial_id') + title = request.POST.get('title') + message = request.POST.get('message') + approval_status = request.POST.get('approval_status') + course_name = request.POST.get('course_name') + + testimonial = Testimonial.objects.filter(id=testimonial_id) + + final_message = "" + if has_admin_rights: + testimonial.update(approval_status=approval_status) + if approval_status == str(ApprovalStatus.APPROVED.value): + final_message = f'Your Testimonial to {course_name}: \n"{testimonial[0].testimonial}" has been Accepted, it is now available to see in the Testimonials tab.' + if (sender != receiver): + update_to_testimonial_status(sender, receiver, title, final_message) #notification + return response.success(value="Testimonial Accepted and the notification has been sent to " + str(receiver) + ".") + elif approval_status == str(ApprovalStatus.REJECTED.value): + final_message = f'Your Testimonial to {course_name}: \n"{testimonial[0].testimonial}" has not been accepted due to: {message}' + if (sender != receiver): + update_to_testimonial_status(sender, receiver, title, final_message) #notification + return response.success(value="Testimonial Not Accepted " + "and the notification has been sent to " + str(receiver) + ".") + else: + return response.not_possible("Cannot Update the Testimonial to approval_status: " + str(approval_status)) + else: + return response.not_possible("No permission to approve/disapprove this testimonial.") \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index 5f104bc65..c723e4b55 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,20 +4,25 @@ "type": "module", "private": true, "dependencies": { - "@mantine/charts": "^8.0.0", - "@mantine/colors-generator": "^8.0.0", - "@mantine/core": "^8.0.0", + "@formkit/auto-animate": "^0.8.2", + "@mantine/charts": "^8.2.1", + "@mantine/colors-generator": "^7.7.0", + "@mantine/core": "^8.2.1", + "@mantine/dates": "^8.2.1", "@mantine/form": "^8.1.2", - "@mantine/hooks": "^8.0.0", - "@tabler/icons-react": "^3.0.0", + "@mantine/hooks": "^8.1.2", + "@tabler/icons-react": "^3.34.0", "@umijs/hooks": "1.9.3", "chroma-js": "^2.4.2", - "clsx": "^2.1.0", + "clsx": "^2.1.1", "date-fns": "^3.3.0", + "dayjs": "^1.11.13", + "googleapis": "^154.1.0", "jszip": "^3.10.0", "jwt-decode": "^4.0.0", "katex": "^0.16.9", "lodash-es": "^4.17.21", + "mantine-datatable": "^8.2.0", "moment": "^2.29.1", "pdfjs-dist": "^4.0.0", "query-string": "^7.1.0", diff --git a/frontend/src/api/hooks.ts b/frontend/src/api/hooks.ts index b8639dd8f..6e4f1bc3b 100644 --- a/frontend/src/api/hooks.ts +++ b/frontend/src/api/hooks.ts @@ -47,6 +47,7 @@ export const useUserInfo = (username: string) => { ); return [error, loading, data, run] as const; }; + const setUserDisplayUsername = async (displayUsername: string) => { await fetchPost("/api/auth/update_name/", { display_username: displayUsername, @@ -593,4 +594,4 @@ export const regenerateDocumentAPIKey = async (documentSlug: string) => { export const useRegenerateDocumentAPIKey = ( documentSlug: string, onSuccess?: (res: Document) => void, -) => useMutation(() => regenerateDocumentAPIKey(documentSlug), onSuccess); +) => useMutation(() => regenerateDocumentAPIKey(documentSlug), onSuccess); \ No newline at end of file diff --git a/frontend/src/api/testimonials.ts b/frontend/src/api/testimonials.ts new file mode 100644 index 000000000..4c8f68e21 --- /dev/null +++ b/frontend/src/api/testimonials.ts @@ -0,0 +1,18 @@ +import { fetchGet, fetchPost } from "./fetch-utils"; + +export const listCategories = async () => { + return (await fetchGet("/api/category/listwithid")).value as { + category_id: string; + displayname: string; + slug: string; + euclid_codes: string[]; + }[]; + }; + +export const loadTestimonials = async () => { + return (await fetchGet("/api/testimonials/listtestimonials")) +}; + +export const removeTestimonial = async (testimonialId: string) => { + return (await fetchPost(`/api/testimonials/removetestimonial/${testimonialId}/`, {})); +}; diff --git a/frontend/src/app.tsx b/frontend/src/app.tsx index 24ad4f5ee..b13419cba 100644 --- a/frontend/src/app.tsx +++ b/frontend/src/app.tsx @@ -10,7 +10,10 @@ import { CSSVariablesResolver, SegmentedControl, } from "@mantine/core"; -import "@mantine/core/styles.css"; +import "@mantine/hooks"; +import '@mantine/core/styles.layer.css'; +import 'mantine-datatable/styles.layer.css'; +import './layout.css'; import "@mantine/charts/styles.css"; import React, { useEffect, useState } from "react"; import { Redirect, Route, Switch } from "react-router-dom"; @@ -49,6 +52,8 @@ import { defaultConfigOptions, } from "./components/Navbar/constants"; import { useDisclosure } from "@mantine/hooks"; +import TestimonialsPage from "./pages/testimonials-page"; +import AddTestimonialsPage from "./pages/add-testimonials-page"; import AnnouncementHeader from "./components/Navbar/AnnouncementHeader"; function calculateShades(primaryColor: string): MantineColorsTuple { @@ -188,6 +193,7 @@ const App: React.FC<{}> = () => { const bottomHeaderNav = [ { title: "Home", href: "/" }, { title: "Search", href: "/search" }, + { title: "Testimonials", href:"/testimonials"}, { title: "Dissertations", href: "/dissertations" }, { title: "More", @@ -232,7 +238,7 @@ const App: React.FC<{}> = () => { }); return ( - + @@ -272,6 +278,16 @@ const App: React.FC<{}> = () => { path="/dissertations" component={DissertationListPage} /> + + { return "Other answer to same question"; case 4: return "Comment to my document"; + case 5: + return "Update to my testimonial status"; default: return "Unknown"; } @@ -46,7 +48,7 @@ const UserNotificationsSettings: React.FC = ({ - {[1, 2, 3, 4].map(type => ( + {[1, 2, 3, 4, 5].map(type => ( {mapTypeToString(type)} diff --git a/frontend/src/components/user-testimonials.tsx b/frontend/src/components/user-testimonials.tsx new file mode 100644 index 000000000..d6dd1117a --- /dev/null +++ b/frontend/src/components/user-testimonials.tsx @@ -0,0 +1,338 @@ + +import { useUser } from "../auth"; +import { Modal, LoadingOverlay, SegmentedControl, Alert, Space, Textarea, Card, Text, Box, Tooltip, Group, Flex, Button, Stack} from "@mantine/core"; +import { IconInfoCircle } from "@tabler/icons-react"; +import { + loadTestimonials + } from "../api/testimonials"; +import { CourseTestimonial, ApprovalStatus, Course, CourseWithTestimonial, getTableData } from "../pages/testimonials-page" +import { useRequest } from "@umijs/hooks"; +import { useDisclosure } from '@mantine/hooks'; +import { useState,useEffect } from 'react'; +import { fetchPost, fetchGet } from '../api/fetch-utils'; +import { useForm } from '@mantine/form'; + +interface UserTestimonialsProps { + username: string; +} + +interface TestimonialsProps{ + currentUserId: string, + isAdmin: boolean, +} + +const Testimonials: React.FC = ({currentUserId, isAdmin}) => { + const { data : testimonials, loading: loading_testimonials, error: error_testimonials, refresh } = useRequest( + () => loadTestimonials() + ); + const [updatedTestimonials, setUpdatedTestimonials] = useState([]); + const [loadingTestimonials, setLoadingTestimonials] = useState(true); + const [errorTestimonials, setErrorTestimonials] = useState(false); + const [approvalStatusFilter, setApprovalStatusFilter] = useState("All"); + + useEffect(() => { + if (testimonials) { + setUpdatedTestimonials(testimonials['value']); + setLoadingTestimonials(loading_testimonials); + if (error_testimonials){ + setErrorTestimonials(true); + } + } + }, [testimonials]); + + const AdminTestimonialCard: React.FC = ({category_id, course_name, username, displayName, yearTaken, testimonial, testimonial_id, testimonial_approval_status}) => { + const [opened, { open, close }] = useDisclosure(false); + const [success, setSuccess] = useState(null); + const [errorMessage, setErrorMessage] = useState(null); + + //useForm? + + const form = useForm({ + initialValues: { + message: '', + }, + + validate: { + message: (value: string) => (value ? null : 'Message is required.'), + }, + }); + + const disapproveTestimonial = async (values: typeof form.values) => { + + setLoadingTestimonials(true); + setSuccess(null); + setErrorMessage(null); + + const dataToSend = { + author: username, + category_id: category_id, + testimonial_id: testimonial_id, + course_name: course_name, + title: "Testimonial not Approved", + message: values.message, + approval_status: ApprovalStatus.REJECTED + }; + try { + + const response = await fetchPost('/api/testimonials/updatetestimonialapproval/', dataToSend); + + if (response.value) { + setSuccess(true); + refresh(); + setLoadingTestimonials(false); + } else { + setSuccess(false); + setErrorMessage(response.error || 'Unknown error during deletion.'); + } + } catch (error: any) { + setSuccess(false); + setErrorMessage(error || 'Network error during deletion.'); + } + } + + const approveTestimonial = async () => { + setLoadingTestimonials(true); + setSuccess(null); + setErrorMessage(null); + + const dataToSend = { + author: username, + category_id: category_id, + testimonial_id: testimonial_id, + title: "Testimonial Approved", + message: "Your testimonial has been approved.", + approval_status: ApprovalStatus.APPROVED, + course_name: course_name + }; + try { + const response = await fetchPost('/api/testimonials/updatetestimonialapproval/', dataToSend); + + if (response.value) { + setSuccess(true); + refresh(); + setLoadingTestimonials(false); + } else { + setSuccess(false); + setErrorMessage(response.error || 'Unknown error during deletion.'); + } + } catch (error: any) { + setSuccess(false); + setErrorMessage(error || 'Network error during deletion.'); + } + } + + return( + <> + + + Are you sure you'd like to disapprove this testimonial? This action cannot be undone. + When you disapprove a testimonial, the user will be notified. Please provide feedback below so they know what to improve. + +
+ + + {success === true && ( + Successfully disapproved testimonial, refreshing page now... + )} + + {success === false && errorMessage && ( + Failed to disapprove testimonial due to {errorMessage} + )} + + + + + +
+
+ +
+ + + + {testimonial_approval_status==ApprovalStatus.APPROVED ? "Approved" : testimonial_approval_status==ApprovalStatus.REJECTED ? "Rejected" : "Pending"} + + + + Course: {course_name} + + + + + + + {displayName} + + + @{username} + + + · + + took the course in {yearTaken} + + + + + "{testimonial}" + + + + + + + + + + + ) + } + + let approvalStatusMap = new Map([ + ["Approved", ApprovalStatus.APPROVED], + ["Pending", ApprovalStatus.PENDING], + ["Rejected", ApprovalStatus.REJECTED] + ]); + + + return ( + <> + + + + + + {errorTestimonials? There has been an error with loading the testimonials. : + updatedTestimonials && + (isAdmin? + (approvalStatusFilter== "All"? updatedTestimonials.map((testimonial, index) => + + ) : updatedTestimonials.filter((testimonial) => testimonial.approval_status === approvalStatusMap.get(approvalStatusFilter)).map((testimonial, index) => + + )): + (approvalStatusFilter== "All"? updatedTestimonials.filter((testimonial) => testimonial.author_id===currentUserId).map((testimonial, index) => + + ) : updatedTestimonials.filter((testimonial) => testimonial.author_id===currentUserId && testimonial.approval_status===approvalStatusMap.get(approvalStatusFilter)).map((testimonial, index) => + + )) + ) + } + + ); +} + + +interface AdminTestimonialCardProps{ + username: String, displayName: String, category_id: String, course_name:String, yearTaken: String, testimonial:String, testimonial_id:String, testimonial_approval_status:Number + } + + + interface UserTestimonialCardProps{ + username: String, displayName: String, category_id: String, course_name:String, yearTaken: String, testimonial:String, testimonial_approval_status:Number + } + +const UserTestimonialCard: React.FC = ({category_id, course_name, username, displayName, yearTaken, testimonial, testimonial_approval_status}) => { + + return( + <> + + + {testimonial_approval_status==ApprovalStatus.APPROVED ? "Approved" : testimonial_approval_status==ApprovalStatus.REJECTED ? "Rejected" : "Pending"} + + + + Course: {course_name} + + + + + + + {displayName} + + + @{username} + + + · + + took the course in {yearTaken} + + + + + "{testimonial}" + + + + + + ) +} + +const UserTestimonials: React.FC = ({ username }) => { + + const { username: currentUsername, isAdmin } = useUser()!; + + const isOwnProfile = username === currentUsername; + + return ( + <> + {isOwnProfile && ( + + + + + + Your Testimonials + + }> + {isAdmin ? + "As an admin, you can see all testimonials and flag testimonials that are toxic or contain names." : + "This page shows all your testimonials, including those under review."} + + + )} + + ); +} + +export default UserTestimonials; \ No newline at end of file diff --git a/frontend/src/interfaces.ts b/frontend/src/interfaces.ts index b279b466e..fb4d18630 100644 --- a/frontend/src/interfaces.ts +++ b/frontend/src/interfaces.ts @@ -153,6 +153,7 @@ export interface CategoryMetaDataOverview { } export interface CategoryMetaData { + category_id: string; displayname: string; // Name of category slug: string; admins: string[]; diff --git a/frontend/src/layout.css b/frontend/src/layout.css new file mode 100644 index 000000000..a031b77a6 --- /dev/null +++ b/frontend/src/layout.css @@ -0,0 +1,3 @@ +/* layout.css */ +/* 👇 Apply Mantine core styles first, DataTable styles second */ +@layer mantine, mantine-datatable; \ No newline at end of file diff --git a/frontend/src/pages/add-testimonials-page.tsx b/frontend/src/pages/add-testimonials-page.tsx new file mode 100644 index 000000000..3eaaec86b --- /dev/null +++ b/frontend/src/pages/add-testimonials-page.tsx @@ -0,0 +1,162 @@ +import React, { useState } from 'react'; +import { useHistory, useParams } from 'react-router-dom'; +import { Container, Alert, Text, Title, Autocomplete, Textarea, Notification, Modal, Group, NumberInput, Button, Rating, TextInput, Input, Flex, Center, Box, Card, Stack, useComputedColorScheme} from '@mantine/core'; +import { useForm } from '@mantine/form'; +import { useEffect } from 'react'; +import { fetchPost } from '../api/fetch-utils'; +import {TestimonialsTableProps, CourseWithTestimonial} from "./testimonials-page" +import { IconInfoCircle } from '@tabler/icons-react'; +import { + IconPlus,IconCalendarFilled, + } from "@tabler/icons-react"; +import { CategoryMetaData } from "../interfaces"; +import { useRequest } from "@umijs/hooks"; +import { + listCategories, loadTestimonials, +} from "../api/testimonials"; +import { + useBICourseList, +} from "../api/hooks"; +import { getTableData } from './testimonials-page'; + +interface CourseNameCategoryLabel { + value: string; + label: string; +} +const AddTestimonialsPage: React.FC = ({data}) => { + const { category_id, course_name } = useParams<{ category_id?: string; course_name?: string }>(); + const [uploadSuccess, setUploadSuccess] = useState(null); + const [errorMessage, setErrorMessage] = useState(null); + const [listCoursesData, setListCoursesData] = useState([]); + const initialCourse = course_name? `${course_name}` : '' + + const { data : courses, loading: loading_courses, error: error_courses} = useRequest( + () => listCategories() + ); + const { data : testimonials, loading: loading_testimonials, error: error_testimonials } = useRequest( + () => loadTestimonials() + ); + + const [bi_courses_error, bi_courses_loading, bi_courses_data] = useBICourseList(); + //also load the courses! + + + + useEffect(() => { + if (course_name && category_id && courses && testimonials && bi_courses_data) { + setListCoursesData(getTableData(courses, testimonials, bi_courses_data).map((course : CourseWithTestimonial) => ({ + value: `${course.course_name} - ${course.category_id}`, + label: course.course_name, + }))) + } + } + // const found = tableData.find( + // (course: CourseWithTestimonial) => + // course.course_name === course_name && + // course.category_id === category_id + // ); + + // if (found) { + // form.setFieldValue('courseName', `${found.course_name} - ${found.category_id}`); + // } + , [course_name, category_id, courses, testimonials, bi_courses_data]); + + const form = useForm({ + initialValues: { + courseName: initialCourse, + yearTakenValue: 2025, + testimonialString: '', + }, + + validate: { + courseName: (value: string) => (value ? null : 'Course Name is required'), + yearTakenValue: (value: number) => (value ? null : 'Year Taken is required'), + testimonialString: (value: string) => (value ? null : "Testimonial is required") + }, + }); + + const handleSubmit = async (values: typeof form.values) => { + setUploadSuccess(null); + setErrorMessage(null); + + // fetchPost expects a plain object, and it will construct FormData internally + const selectedCategoryId = listCoursesData.find(item => item.label === values.courseName)?.value.split(" - ")[1]; + const dataToSend = { + category_id: selectedCategoryId, + year_taken: values.yearTakenValue, + testimonial: values.testimonialString, + }; + //understand thens and comments + try { + const response = await fetchPost('/api/testimonials/addtestimonial/', dataToSend); + + if (response.value && response.value["approved"] == true) { + setUploadSuccess(true); + form.setInitialValues({ + courseName: '', + yearTakenValue: 2025, + testimonialString: '', + }) + form.reset(); + } else if (response.value && response.value["approved"] == false) { + setUploadSuccess(true); + setErrorMessage("Thank you for submitting your testimonial! We appreciate your feedback. Your message will be reviewed by a moderator before it is published, we will notify you in your Account Page when it has been reviewed."); + } + else { + setUploadSuccess(false); + setErrorMessage(response.error || 'Unknown error during upload.'); + } + } catch (error: any) { + setUploadSuccess(false); + setErrorMessage(error || 'Network error during upload.'); + } + }; + + return ( + + Add a Course Testimonial + {listCoursesData.length > 0 &&
+ + + } + {...form.getInputProps('yearTakenValue')} + label="When did you take this course?" + placeholder="Year" + required withAsterisk + /> + + + Testimonial (Experience, Advice, Study Tips) * + + + + {/* text area */} + {uploadSuccess === true && errorMessage == null && ( + setUploadSuccess(null)} icon={}> + Thanks for submitting your review, your knowledge will be much appreciated by future UoE students. + + )} + + {uploadSuccess === true && errorMessage && ( + setUploadSuccess(null)} icon={}> + {errorMessage} + + )} + + {uploadSuccess === false && errorMessage && ( + setUploadSuccess(null)} icon={}> + {errorMessage} + + )} + + + +
} +
+ ); +}; + +export default AddTestimonialsPage; \ No newline at end of file diff --git a/frontend/src/pages/category-page.tsx b/frontend/src/pages/category-page.tsx index 81eb8d909..02225972a 100644 --- a/frontend/src/pages/category-page.tsx +++ b/frontend/src/pages/category-page.tsx @@ -26,6 +26,9 @@ import { useRemoveCategory, useBICourseList, } from "../api/hooks"; +import { + loadTestimonials +} from "../api/testimonials"; import { UserContext, useUser } from "../auth"; import CategoryMetaDataEditor from "../components/category-metadata-editor"; import ExamList from "../components/exam-list"; @@ -50,17 +53,22 @@ import { import { EuclidCodeBadge } from "../components/euclid-code-badge"; import { useCategoryTabs } from "../hooks/useCategoryTabs"; import { PieChart } from "@mantine/charts"; +import { CourseTestimonial, TestimonialCard, ApprovalStatus } from "./testimonials-page"; interface CategoryPageContentProps { onMetaDataChange: (newMetaData: CategoryMetaData) => void; + testimonials: CourseTestimonial[]; metaData: CategoryMetaData; } + + const CategoryPageContent: React.FC = ({ onMetaDataChange, + testimonials, metaData, }) => { const computedColorScheme = useComputedColorScheme("light"); - + console.log(testimonials) const { data, loading: _, @@ -155,11 +163,18 @@ const CategoryPageContent: React.FC = ({ // defining Routes, we use `path`, but for Link/navigation we use `url`. const { path, url } = useRouteMatch(); + //get testimonial count + + // let currentCourseTestimonials : CourseTestimonial[] = quickinfo_data.map((c) => c?.code).testimonials['value'].filter( + // (testimonial: CourseTestimonial) => (testimonial.euclid_code == c && testimonial.approval_status == ApprovalStatus.APPROVED + // )); + + let courseTestimonials = testimonials.filter((testimonial) => (testimonial.category_id === metaData.category_id && testimonial.approval_status == ApprovalStatus.APPROVED)) //filter based on approval status const tabs = useCategoryTabs([ { name: "Resources", id: "resources" }, - { name: "Testimonials", id: "testimonials", count: 0, disabled: true }, + { name: "Testimonials", id: "testimonials", count: courseTestimonials? courseTestimonials.length: 0}, //okay haven't finished. { name: "Grade Stats", id: "statistics", disabled: true }, - ]); + ]); //get testimonial with a specific id // TODO: switch to betterinformatics.com/courses.json "session" field once that's live const { thisYear, nextYearSuffix } = useMemo(() => { @@ -260,7 +275,7 @@ const CategoryPageContent: React.FC = ({ {tabs.Component} - + {tabs.currentTabId == "resources" && @@ -433,6 +448,13 @@ const CategoryPageContent: React.FC = ({ )} + } + + { + tabs.currentTabId=="testimonials" && courseTestimonials.map((testimonial, index) => //add a key to the testimonial + ) + } +
@@ -458,11 +480,16 @@ const CategoryPage: React.FC<{}> = () => { ); useTitle(data?.displayname ?? slug); const user = useUser(); + const { data : testimonials, loading: loading_testimonials, error: error_testimonials, refresh } = useRequest( + () => loadTestimonials() + ); return ( {error && {error.message}} - {data === undefined && } - {data && ( + {(data === undefined && testimonials == undefined) && } + {console.log("DATA UPDATE")} + {console.log(data)} + {data && testimonials && ( = () => { > diff --git a/frontend/src/pages/testimonials-page.tsx b/frontend/src/pages/testimonials-page.tsx new file mode 100644 index 000000000..6f81bab0b --- /dev/null +++ b/frontend/src/pages/testimonials-page.tsx @@ -0,0 +1,448 @@ +import { useAutoAnimate } from '@formkit/auto-animate/react'; +import { useRequest } from "@umijs/hooks"; +import sortBy from 'lodash/sortBy'; +import {useHistory} from 'react-router-dom' +import { useDisclosure } from '@mantine/hooks'; +import { fetchPost, fetchGet } from '../api/fetch-utils'; +import { notLoggedIn, SetUserContext, User, UserContext } from "../auth"; +import {useEffect, useState, useMemo } from 'react'; +import { DataTable, type DataTableSortStatus } from 'mantine-datatable'; +import useTitle from '../hooks/useTitle'; +import { useDebouncedValue } from '@mantine/hooks'; +import { useLocalStorageState } from '@umijs/hooks'; +import { Alert, Anchor, Container, Text, CloseButton, Title, Autocomplete, Textarea, Notification, Modal, Group, NumberInput, Button, Rating, TextInput, Input, Flex, Center, Box, Card, Stack, useComputedColorScheme, ActionIcon} from '@mantine/core'; +import KawaiiBetterInformatics from "../assets/kawaii-betterinformatics.svg?react"; +import ShimmerButton from '../components/shimmer-button'; +import { + IconPlus,IconSearch, IconX, IconInfoCircle +} from "@tabler/icons-react"; + +import { + listCategories, + loadTestimonials +} from "../api/testimonials"; +import ContentContainer from '../components/secondary-container'; +import { BICourseDict } from "../interfaces"; +import { + useBICourseList, +} from "../api/hooks"; + + +//mock data +export type Course = { + course_code: string, + course_name: string, + course_delivery: string, + course_credits: number, + course_work_exam_ratio: number[], + course_level: number, + course_dpmt_link: string +} + +export enum ApprovalStatus { + APPROVED = 0, + PENDING = 1, + REJECTED = 2, +} + +export type CourseTestimonial = { + author_id: string, + author_diplay_name: string, + category_id: string, + euclid_codes: string[], + course_name: string, + testimonial: string, + testimonial_id: string, + year_taken: number + approval_status: number, +} + +export type CourseWithTestimonial = { + category_id: string, + course_codes: string[], + course_name: string, + course_delivery: string, + course_credits: number, + course_work_exam_ratio: number[], + course_level: number, + course_dpmt_link: string, + testimonials: CourseTestimonial[] +} + + +export interface TestimonialsTableProps{ + data: CourseWithTestimonial[], + user: User | undefined +} + +export function getTableData(courses: any, testimonials: any, bi_courses_data: BICourseDict) : CourseWithTestimonial[] { + let tableData = new Array(1); + if (courses && testimonials){ + tableData = new Array(courses.length); + + for (let i = 0; i < courses.length; i++){ + let course = courses[i]; + + let currentCourseTestimonials : CourseTestimonial[] = testimonials['value'].filter( + (testimonial: CourseTestimonial) => (testimonial.category_id == course.category_id && testimonial.approval_status == ApprovalStatus.APPROVED + )); + + //average of testimonials and etc! + let currentCourse = undefined + + for (let key in bi_courses_data) { + // Ensure the property belongs to the object itself, not its prototype chain + if (Object.prototype.hasOwnProperty.call(bi_courses_data, key)) { + const value = bi_courses_data[key]; + //loop through euclid codes, get the active one? + //course, euclid code, + if (String(value.name) === String(course.displayname) || String(value.euclid_code_shadow) === String(course.code)) { + currentCourse = { + acronym: value.acronym, + name: value.name, + level: value.level, + delivery: value.delivery, + credits: value.credits, + cw_exam_ratio: value.cw_exam_ratio, + course_url: value.course_url, + euclid_url: value.euclid_url, + // Set the shadow property to the main course code if this is a shadow + shadow: value.euclid_code_shadow === course.code ? course.euclid_code : undefined, + } + } + } + } + + + tableData[i] = { + category_id: course.category_id, + course_codes: course.euclid_codes, //i need the euclid codes babe + course_name: currentCourse? currentCourse.name : "undefined", + course_delivery: currentCourse? String(currentCourse.delivery) : "undefined", + course_credits: currentCourse? Number(currentCourse.credits) : -1, + course_work_exam_ratio: currentCourse? currentCourse.cw_exam_ratio : [], + course_level: currentCourse? Number(currentCourse.level) : -1, + course_dpmt_link: currentCourse? currentCourse.course_url : "undefined", + testimonials: currentCourseTestimonials + } + } + } + return tableData; +} + +const TestimonialsPage: React.FC<{}> = () => { + useTitle("Testimonials"); + const [user, setUser] = useState(undefined); + useEffect(() => { + let cancelled = false; + if (user === undefined) { + fetchGet("/api/auth/me/").then( + res => { + if (cancelled) return; + setUser({ + loggedin: res.loggedin, + username: res.username, + displayname: res.displayname, + isAdmin: res.adminrights, + isCategoryAdmin: res.adminrightscat, + }); + }, + () => { + setUser(notLoggedIn); + }, + ); + } + return () => { + cancelled = true; + }; + }, [user]); + + const [uwu, _] = useLocalStorageState("uwu", false); + const { data : courses, loading: loading_courses, error: error_courses} = useRequest( + () => listCategories() + ); + const { data : testimonials, loading: loading_testimonials, error: error_testimonials } = useRequest( + () => loadTestimonials() + ); + + const [bi_courses_error, bi_courses_loading, bi_courses_data] = useBICourseList(); + return ( + <> + + {uwu ? ( + + + + ) : ( + <> + Better­Informatics + Course Testimonials + + )} + + BetterInformatics Course Testimonials is a platform for students to share + their experiences and study tips in courses that they have taken to help future students make course choices. + + + + + {(courses && testimonials && bi_courses_data && )|| + ((loading_courses || error_courses || loading_testimonials || error_testimonials || bi_courses_loading || bi_courses_error) && ) + } + + + + ); +} + +const TestimonialsTable: React.FC = ({data, user}) => { + + + const [allData, setAllData] = useState(sortBy(data, 'course_name')); + + const [sortStatus, setSortStatus] = useState>({ + columnAccessor: 'course_name', + direction: 'asc', + }); + + const [query, setQuery] = useState(''); + const [debouncedQuery] = useDebouncedValue(query, 200); + + const computedColorScheme = useComputedColorScheme("light"); + const [parent] = useAutoAnimate(); + + const history = useHistory(); + + useEffect(() => { + setAllData(sortBy(data, 'course_name')) + }, [data]) + + useEffect(() => { + const initial_data = sortBy(data, sortStatus.columnAccessor) as CourseWithTestimonial[]; + let updatedData = sortStatus.direction === 'desc' ? initial_data.reverse() : initial_data; + if (debouncedQuery !== ''){ + updatedData = updatedData.filter((course) => (course.course_name.toLowerCase().includes(debouncedQuery.toLowerCase()))) + } + setAllData(updatedData); + }, [sortStatus, debouncedQuery]); + + return <> + : <> } + // noRecordsText={allData?.length === 0 ? "No records to show" : ""} + //scrollAreaProps={{ type: 'never' }} + columns = {[ + { + accessor: 'category_id', + title: 'Category ID', + width: "12%", + sortable: true, + hidden: true, + }, + // { + // accessor: 'course_codes', + // title: 'Course Code(s)', + // width: "12%", + // sortable: true, + // render: (record, index) => ( + // {record.course_codes.map((code) => code + " ")} + // ) + // }, + { + accessor: 'course_name', + title: 'Course Name', + sortable: true, + render: (record, index) => ( + {record.course_name} + ), + filter: ( + } + rightSection={ + setQuery('')}> + + + } + value={query} + onChange={(e) => setQuery(e.currentTarget.value)} + /> + ), + filtering: query !== '', + // minSize:100, + // maxSize:450, + }, + { + accessor: 'course_delivery', + title: 'Delivery', + sortable: true, + }, + { + accessor: 'course_credits', + title: 'Credits', + sortable: true, + }, + { + accessor: 'course_work_exam_ratio', + title: 'CW/Exam', + sortable: true, + render: (record, index) => ( + {record.course_work_exam_ratio.length===2? record.course_work_exam_ratio[0]+ "/" + record.course_work_exam_ratio[1] : ""} + ) + }, + { + accessor: 'course_level', + title: 'Level', + sortable: true, + }, + { + accessor: 'testimonials', + title: 'No. Testimonials', + sortable: true, + render: (record, index) => ( + {record.testimonials.length} + ) + + } + + ]} + records ={allData} + idAccessor="category_id" + rowExpansion={{ + collapseProps: { + transitionDuration: 150, + animateOpacity: false, + transitionTimingFunction: 'ease-out', + }, + allowMultiple: true, + content: ({ record }) => ( + + + history.push(`/addtestimonials/${record.category_id}/${record.course_name}`)} + leftSection={} + color={computedColorScheme === "dark" ? "compsocMain" : "dark"} + variant="outline" + > + Add new testimonial + + + { + record.testimonials.map((testimonial, index) => //add a key to the testimonial + + ) + } + + ), + }} + sortStatus={sortStatus} + onSortStatusChange={setSortStatus} + /> + ; +}; + +interface ratingProps{ + ratingType: String, ratingLevel: String +} + +const RatingBox: React.FC = ({ratingType, ratingLevel}) => { + return ( + ({ borderRadius: theme.radius.md, textAlign: 'center', fontSize: 'medium'})}> {ratingType} {ratingLevel}/5 + ) +} + +interface testimonialCardProps{ + currentUserUsername: String, isAdmin:boolean, username: String, displayName: String, category_id: String, yearTaken: String, testimonial:String, testimonial_id:String +} + + +export const TestimonialCard: React.FC = ({currentUserUsername, isAdmin, username, displayName, category_id, yearTaken, testimonial, testimonial_id}) => { + const [opened, { open, close }] = useDisclosure(false); + const [deleteSuccess, setDeleteSuccess] = useState(null); + const [errorMessage, setErrorMessage] = useState(null); + + const deleteTestimonial = async () => { + setDeleteSuccess(null); + setErrorMessage(null); + + const dataToSend = { + username: username, + testimonial_id: testimonial_id, + }; + try { + const response = await fetchPost('/api/testimonials/removetestimonial/', dataToSend); + + if (response.value) { + setDeleteSuccess(true); + setTimeout(() => (window.location.reload()),1000); + } else { + setDeleteSuccess(false); + setErrorMessage(response.error || 'Unknown error during deletion.'); + } + } catch (error: any) { + setDeleteSuccess(false); + setErrorMessage(error || 'Network error during deletion.'); + } + } + + return( + <> + + + Are you sure you'd like to delete your testimonial? This action cannot be undone. + + {deleteSuccess === true && ( + Successfully deleted testimonial, refreshing page now... + )} + + {deleteSuccess === false && errorMessage && ( + Failed to delete testimonial due to {errorMessage} + )} + + + + + + + + + + + + + + {displayName} + {currentUserUsername==username && " (you)"} + + + @{username} + + + · + + took the course in {yearTaken} + + {(currentUserUsername==username || isAdmin) && } + + + {testimonial} + + + + ) +} + + +export default TestimonialsPage; \ No newline at end of file diff --git a/frontend/src/pages/userinfo-page.tsx b/frontend/src/pages/userinfo-page.tsx index de0e48588..c7e694af8 100644 --- a/frontend/src/pages/userinfo-page.tsx +++ b/frontend/src/pages/userinfo-page.tsx @@ -1,4 +1,4 @@ -import { Container, Alert, Tabs, LoadingOverlay, Space } from "@mantine/core"; +import { Container, Text, Alert, Tabs, LoadingOverlay, Space } from "@mantine/core"; import React, { useState } from "react"; import { useParams } from "react-router-dom"; import { useUserInfo } from "../api/hooks"; @@ -10,6 +10,7 @@ import UserNotificationsSettings from "../components/user-notification-settings" import UserDisplayNameSettings from "../components/user-displayname-settings"; import UserDocuments from "../components/user-documents"; import UserScoreCard from "../components/user-score-card"; +import UserTestimonials from "../components/user-testimonials" import useTitle from "../hooks/useTitle"; const UserPage: React.FC<{}> = () => { @@ -38,6 +39,7 @@ const UserPage: React.FC<{}> = () => { Answers Comments Documents + Testimonials {isMyself && Settings} @@ -55,6 +57,9 @@ const UserPage: React.FC<{}> = () => { + + + {isMyself && ( <> diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 02db23e4a..a6170340c 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1101,11 +1101,9 @@ regenerator-runtime "^0.14.0" "@babel/runtime@^7.20.13": - version "7.24.4" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.4.tgz#de795accd698007a66ba44add6cc86542aff1edd" - integrity sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA== - dependencies: - regenerator-runtime "^0.14.0" + version "7.27.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.6.tgz#ec4070a04d76bae8ddbb10770ba55714a417b7c6" + integrity sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q== "@babel/template@^7.22.15", "@babel/template@^7.23.9": version "7.23.9" @@ -1288,41 +1286,46 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.56.0.tgz#ef20350fec605a7f7035a01764731b2de0f3782b" integrity sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A== -"@floating-ui/core@^1.7.0": - version "1.7.0" - resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.7.0.tgz#1aff27a993ea1b254a586318c29c3b16ea0f4d0a" - integrity sha512-FRdBLykrPPA6P76GGGqlex/e7fbe0F1ykgxHYNXQsH/iTEtjMj/f9bpY5oQqbjt5VgZvgz/uKXbGuROijh3VLA== +"@floating-ui/core@^1.7.3": + version "1.7.3" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.7.3.tgz#462d722f001e23e46d86fd2bd0d21b7693ccb8b7" + integrity sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w== dependencies: - "@floating-ui/utils" "^0.2.9" + "@floating-ui/utils" "^0.2.10" -"@floating-ui/dom@^1.0.0": - version "1.7.0" - resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.7.0.tgz#f9f83ee4fee78ac23ad9e65b128fc11a27857532" - integrity sha512-lGTor4VlXcesUMh1cupTUTDoCxMb0V6bm3CnxHzQcw8Eaf1jQbgQX4i02fYgT0vJ82tb5MZ4CZk1LRGkktJCzg== +"@floating-ui/dom@^1.7.4": + version "1.7.4" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.7.4.tgz#ee667549998745c9c3e3e84683b909c31d6c9a77" + integrity sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA== dependencies: - "@floating-ui/core" "^1.7.0" - "@floating-ui/utils" "^0.2.9" + "@floating-ui/core" "^1.7.3" + "@floating-ui/utils" "^0.2.10" -"@floating-ui/react-dom@^2.1.2": - version "2.1.2" - resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.2.tgz#a1349bbf6a0e5cb5ded55d023766f20a4d439a31" - integrity sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A== +"@floating-ui/react-dom@^2.1.6": + version "2.1.6" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.6.tgz#189f681043c1400561f62972f461b93f01bf2231" + integrity sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw== dependencies: - "@floating-ui/dom" "^1.0.0" + "@floating-ui/dom" "^1.7.4" -"@floating-ui/react@^0.26.28": - version "0.26.28" - resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.28.tgz#93f44ebaeb02409312e9df9507e83aab4a8c0dc7" - integrity sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw== +"@floating-ui/react@^0.27.16": + version "0.27.16" + resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.27.16.tgz#6e485b5270b7a3296fdc4d0faf2ac9abf955a2f7" + integrity sha512-9O8N4SeG2z++TSM8QA/KTeKFBVCNEz/AGS7gWPJf6KFRzmRWixFRnCnkPHRDwSVZW6QPDO6uT0P2SpWNKCc9/g== dependencies: - "@floating-ui/react-dom" "^2.1.2" - "@floating-ui/utils" "^0.2.8" + "@floating-ui/react-dom" "^2.1.6" + "@floating-ui/utils" "^0.2.10" tabbable "^6.0.0" -"@floating-ui/utils@^0.2.8", "@floating-ui/utils@^0.2.9": - version "0.2.9" - resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.9.tgz#50dea3616bc8191fb8e112283b49eaff03e78429" - integrity sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg== +"@floating-ui/utils@^0.2.10": + version "0.2.10" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.10.tgz#a2a1e3812d14525f725d011a73eceb41fef5bc1c" + integrity sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ== + +"@formkit/auto-animate@^0.8.2": + version "0.8.4" + resolved "https://registry.yarnpkg.com/@formkit/auto-animate/-/auto-animate-0.8.4.tgz#2c857102bbf612c0e7bd4a0aac0e05a7f0efd8e7" + integrity sha512-DHHC01EJ1p70Q0z/ZFRBIY8NDnmfKccQoyoM84Tgb6omLMat6jivCdf272Y8k3nf4Lzdin/Y4R9q8uFtU0GbnA== "@humanwhocodes/config-array@^0.11.13": version "0.11.14" @@ -1375,27 +1378,34 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@mantine/charts@^8.0.0": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@mantine/charts/-/charts-8.1.2.tgz#50bc11de6c51d6e48872ca33a39a580ef136604e" - integrity sha512-K1AfGQa+ABFLWNXT8d1mu+Zsqv6pG1LT+1FnIorpyxe3DOsWLXP3Tr4JHW9QSS0qFyvpOiFuKeCMdDUkdzyMfQ== +"@mantine/charts@^8.2.1": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@mantine/charts/-/charts-8.3.0.tgz#a867d77bae0312d84a9a70c80a2647efea3656f8" + integrity sha512-k34w0/DZKCr5lW/uEea+PX+1UYRIf2MkvhxdwYKoYtMH9BZ8GNM8p5BHljmXXHh3hOFHiiaoSlJW5eJxDfxhiA== -"@mantine/colors-generator@^8.0.0": - version "8.0.0" - resolved "https://registry.yarnpkg.com/@mantine/colors-generator/-/colors-generator-8.0.0.tgz#0bd833f8021e9f694d02887c1fb34df20e215a55" - integrity sha512-H7jMihsxaFCc+x6IdNaNeMvR0qs6iAoo7nfV3RO2OFSvUDXbhsYxjaDijxX8f3vVAFGrZOceqeupwNbsptW04w== +"@mantine/colors-generator@^7.7.0": + version "7.17.8" + resolved "https://registry.yarnpkg.com/@mantine/colors-generator/-/colors-generator-7.17.8.tgz#0e9a6a77b9b810e0c2870da3dc86570b0ea8db85" + integrity sha512-uLUYkmdwv2kQtoNTizBDyOeVLMuCIQfR7UdZDKAPJP921RUu+B8sbW7TAY6jAScDswqZNDZNLKdo5icYYBF/2g== -"@mantine/core@^8.0.0": - version "8.0.0" - resolved "https://registry.yarnpkg.com/@mantine/core/-/core-8.0.0.tgz#8a8fa5b8e71f1db2253afe54a15c8b52c2de6240" - integrity sha512-TskeJS2/+DbmUe85fXDoUAyErkSvR4YlbUl8MLqhjFBJUqwc72ZrLynmN13wuKtlVPakDYYjq4/IEDMReh3CYA== +"@mantine/core@^8.2.1": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@mantine/core/-/core-8.3.0.tgz#23258f713690d47590a6dd376ce3e7d3a5a705b7" + integrity sha512-WDKnEMkYYXrovqwKl3jHNqXWZkKS349/Btbvynldw0zierm0dJqYXNlLdPhjg9CkmTM3FFDg6MjYwlsHL8K8VQ== dependencies: - "@floating-ui/react" "^0.26.28" + "@floating-ui/react" "^0.27.16" clsx "^2.1.1" - react-number-format "^5.4.3" - react-remove-scroll "^2.6.2" + react-number-format "^5.4.4" + react-remove-scroll "^2.7.1" react-textarea-autosize "8.5.9" - type-fest "^4.27.0" + type-fest "^4.41.0" + +"@mantine/dates@^8.2.1": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@mantine/dates/-/dates-8.3.0.tgz#a7c66b2c0c25e9f91cd2577b5b56302a790ae5c5" + integrity sha512-FghERXp9qMwBK8nmQKMuVvbUdBVRh2r1ssQZLVyuTkM+OIiZxh14WvQwFrip1TqD0BxpViAtDHWf+UhbMa40iw== + dependencies: + clsx "^2.1.1" "@mantine/form@^8.1.2": version "8.1.2" @@ -1405,10 +1415,10 @@ fast-deep-equal "^3.1.3" klona "^2.0.6" -"@mantine/hooks@^8.0.0": - version "8.0.0" - resolved "https://registry.yarnpkg.com/@mantine/hooks/-/hooks-8.0.0.tgz#d105e0f1c890bbb2312e5ab452b212ae85959085" - integrity sha512-hrcgZMHUPsgu+VBfUVcJOqMG7Qi+AshYjFyc/qo0Cz8TEhqWmD0I1yJW+qj4sDTTDWRQC6kvI5c1h+87/9MvoA== +"@mantine/hooks@^8.1.2": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@mantine/hooks/-/hooks-8.3.0.tgz#51147cccec42e951e71c0acb2b891ded336cc0e2" + integrity sha512-y/D8Hi4C1iEjTpjeMWKmz9QHMdPm5qQsBsRz6rpdRlVurtBhNf3DCJEtvwtmmKN1F9vBK8DDGy/OjQPUHLDqcA== "@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": version "5.1.1-v1" @@ -1635,17 +1645,17 @@ "@svgr/hast-util-to-babel-ast" "8.0.0" svg-parser "^2.0.4" -"@tabler/icons-react@^3.0.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@tabler/icons-react/-/icons-react-3.1.0.tgz#5202b5cc7c6e57dde75c41981b618dcfebd69b50" - integrity sha512-k/WTlax2vbj/LpxvaJ+BmaLAAhVUgyLj4Ftgaczz66tUSNzqrAZXCFdOU7cRMYPNVBqyqE2IdQd2rzzhDEJvkw== +"@tabler/icons-react@^3.34.0": + version "3.34.0" + resolved "https://registry.yarnpkg.com/@tabler/icons-react/-/icons-react-3.34.0.tgz#8503c25ba9e50ff02a49a1f9c62ceecfd27fa3ab" + integrity sha512-OpEIR2iZsIXECtAIMbn1zfKfQ3zKJjXyIZlkgOGUL9UkMCFycEiF2Y8AVfEQsyre/3FnBdlWJvGr0NU47n2TbQ== dependencies: - "@tabler/icons" "3.1.0" + "@tabler/icons" "3.34.0" -"@tabler/icons@3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@tabler/icons/-/icons-3.1.0.tgz#d69d184eae572db6adb452b511562442133cc26d" - integrity sha512-CpZGyS1IVJKFcv88yZ2sYZIpWWhQ6oy76BQKQ5SF0fGgOqgyqKdBGG/YGyyMW632on37MX7VqQIMTzN/uQqmFg== +"@tabler/icons@3.34.0": + version "3.34.0" + resolved "https://registry.yarnpkg.com/@tabler/icons/-/icons-3.34.0.tgz#b11734dc76686abe969b68c2f33e565b116b70fb" + integrity sha512-jtVqv0JC1WU2TTEBN32D9+R6mc1iEBuPwLnBsWaR02SIEciu9aq5806AWkCHuObhQ4ERhhXErLEK7Fs+tEZxiA== "@types/babel__core@^7.20.5": version "7.20.5" @@ -2041,6 +2051,11 @@ acorn@^8.9.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== +agent-base@^7.1.2: + version "7.1.4" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.4.tgz#e3cd76d4c548ee895d3c3fd8dc1f6c5b9032e7a8" + integrity sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ== + ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -2272,11 +2287,16 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base64-js@^1.3.1: +base64-js@^1.3.0, base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +bignumber.js@^9.0.0: + version "9.3.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.3.1.tgz#759c5aaddf2ffdc4f154f7b493e1c8770f88c4d7" + integrity sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ== + bl@^4.0.3: version "4.1.0" resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" @@ -2311,6 +2331,11 @@ browserslist@^4.22.2: node-releases "^2.0.14" update-browserslist-db "^1.0.13" +buffer-equal-constant-time@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== + buffer@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" @@ -2319,6 +2344,14 @@ buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.6.tgz#6c46675fc7a5e9de82d75a233d586c8b7ac0d931" @@ -2329,6 +2362,14 @@ call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6: get-intrinsic "^1.2.3" set-function-length "^1.2.0" +call-bound@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" + integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== + dependencies: + call-bind-apply-helpers "^1.0.2" + get-intrinsic "^1.3.0" + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -2425,7 +2466,7 @@ chroma-js@^2.4.2: resolved "https://registry.yarnpkg.com/chroma-js/-/chroma-js-2.4.2.tgz#dffc214ed0c11fa8eefca2c36651d8e57cbfb2b0" integrity sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A== -clsx@^2.0.0, clsx@^2.1.0, clsx@^2.1.1: +clsx@^2.0.0, clsx@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== @@ -2612,11 +2653,28 @@ damerau-levenshtein@^1.0.8: resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA== +data-uri-to-buffer@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e" + integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== + date-fns@^3.3.0: version "3.3.1" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-3.3.1.tgz#7581daca0892d139736697717a168afbb908cfed" integrity sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw== +dayjs@^1.11.13: + version "1.11.13" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c" + integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg== + +debug@4: + version "4.4.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" + integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== + dependencies: + ms "^2.1.3" + debug@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" @@ -2757,6 +2815,22 @@ dot-case@^3.0.4: no-case "^3.0.4" tslib "^2.0.3" +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + +ecdsa-sig-formatter@1.0.11, ecdsa-sig-formatter@^1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + electron-to-chromium@^1.4.648: version "1.4.666" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.666.tgz#ecc65df60e5d3489962ff46b8a6b1dd3b8f863aa" @@ -2843,6 +2917,11 @@ es-array-method-boxes-properly@^1.0.0: resolved "https://registry.yarnpkg.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e" integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA== +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + es-errors@^1.0.0, es-errors@^1.1.0, es-errors@^1.2.1, es-errors@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" @@ -2869,6 +2948,13 @@ es-iterator-helpers@^1.0.12, es-iterator-helpers@^1.0.15: iterator.prototype "^1.1.2" safe-array-concat "^1.1.0" +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + es-set-tostringtag@^2.0.1, es-set-tostringtag@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz#11f7cc9f63376930a5f20be4915834f4bc74f9c9" @@ -3201,7 +3287,7 @@ expand-template@^2.0.3: resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== -extend@^3.0.0: +extend@^3.0.0, extend@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -3251,6 +3337,14 @@ fault@^1.0.0: dependencies: format "^0.2.0" +fetch-blob@^3.1.2, fetch-blob@^3.1.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" + integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ== + dependencies: + node-domexception "^1.0.0" + web-streams-polyfill "^3.0.3" + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -3304,6 +3398,13 @@ format@^0.2.0: resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b" integrity sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww== +formdata-polyfill@^4.0.10: + version "4.0.10" + resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" + integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== + dependencies: + fetch-blob "^3.1.2" + fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" @@ -3339,6 +3440,24 @@ functions-have-names@^1.2.3: resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== +gaxios@^7.0.0, gaxios@^7.0.0-rc.4: + version "7.1.1" + resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-7.1.1.tgz#255b86ce09891e9ed16926443a1ce239c8f9fd51" + integrity sha512-Odju3uBUJyVCkW64nLD4wKLhbh93bh6vIg/ZIXkWiLPBrdgtc65+tls/qml+un3pr6JqYVFDZbbmLDQT68rTOQ== + dependencies: + extend "^3.0.2" + https-proxy-agent "^7.0.1" + node-fetch "^3.3.2" + +gcp-metadata@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-7.0.1.tgz#43bb9cd482cf0590629b871ab9133af45b78382d" + integrity sha512-UcO3kefx6dCcZkgcTGgVOTFb7b1LlQ02hY1omMjjrrBzkajRMCFgYOjs7J71WqnuG1k2b+9ppGL7FsOfhZMQKQ== + dependencies: + gaxios "^7.0.0" + google-logging-utils "^1.0.0" + json-bigint "^1.0.0" + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -3355,11 +3474,35 @@ get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@ has-symbols "^1.0.3" hasown "^2.0.0" +get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + get-nonce@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3" integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q== +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + get-symbol-description@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5" @@ -3436,6 +3579,43 @@ globrex@^0.1.2: resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg== +google-auth-library@^10.1.0: + version "10.2.0" + resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-10.2.0.tgz#cdcee27c6021cf610fde6c1dd9abe85217b1bf6e" + integrity sha512-gy/0hRx8+Ye0HlUm3GrfpR4lbmJQ6bJ7F44DmN7GtMxxzWSojLzx0Bhv/hj7Wlj7a2On0FcT8jrz8Y1c1nxCyg== + dependencies: + base64-js "^1.3.0" + ecdsa-sig-formatter "^1.0.11" + gaxios "^7.0.0" + gcp-metadata "^7.0.0" + google-logging-utils "^1.0.0" + gtoken "^8.0.0" + jws "^4.0.0" + +google-logging-utils@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/google-logging-utils/-/google-logging-utils-1.1.1.tgz#4a1f44a69a187eb954629c88c5af89c0dfbca51a" + integrity sha512-rcX58I7nqpu4mbKztFeOAObbomBbHU2oIb/d3tJfF3dizGSApqtSwYJigGCooHdnMyQBIw8BrWyK96w3YXgr6A== + +googleapis-common@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/googleapis-common/-/googleapis-common-8.0.0.tgz#7a3ea3aa3863c9d3e7635070314d639dadfa164a" + integrity sha512-66if47It7y+Sab3HMkwEXx1kCq9qUC9px8ZXoj1CMrmLmUw81GpbnsNlXnlyZyGbGPGcj+tDD9XsZ23m7GLaJQ== + dependencies: + extend "^3.0.2" + gaxios "^7.0.0-rc.4" + google-auth-library "^10.1.0" + qs "^6.7.0" + url-template "^2.0.8" + +googleapis@^154.1.0: + version "154.1.0" + resolved "https://registry.yarnpkg.com/googleapis/-/googleapis-154.1.0.tgz#2d44401ab6ebd8239ba74c9548c3b396da9e7eff" + integrity sha512-lsgKftflkjR+PNdKDjy5e4zZUQO3BMaNLO0Q91uvr9YCy3aOqtEwkDtfhcBguFtFLsqj+XjdfDu2on6rtHNvYg== + dependencies: + google-auth-library "^10.1.0" + googleapis-common "^8.0.0" + gopd@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" @@ -3443,11 +3623,24 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + graphemer@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== +gtoken@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-8.0.0.tgz#d67a0e346dd441bfb54ad14040ddc3b632886575" + integrity sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw== + dependencies: + gaxios "^7.0.0" + jws "^4.0.0" + has-bigints@^1.0.1, has-bigints@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" @@ -3480,6 +3673,11 @@ has-symbols@^1.0.2, has-symbols@^1.0.3: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== +has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + has-tostringtag@^1.0.0, has-tostringtag@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" @@ -3494,6 +3692,13 @@ hasown@^2.0.0: dependencies: function-bind "^1.1.2" +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + hast-util-from-dom@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/hast-util-from-dom/-/hast-util-from-dom-5.0.1.tgz#c3c92fbd8d4e1c1625edeb3a773952b9e4ad64a8" @@ -3647,6 +3852,14 @@ html-url-attributes@^3.0.0: resolved "https://registry.yarnpkg.com/html-url-attributes/-/html-url-attributes-3.0.1.tgz#83b052cd5e437071b756cd74ae70f708870c2d87" integrity sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ== +https-proxy-agent@^7.0.1: + version "7.0.6" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9" + integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw== + dependencies: + agent-base "^7.1.2" + debug "4" + iconv-lite@^0.6.2: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" @@ -4009,6 +4222,13 @@ jsesc@~0.5.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== +json-bigint@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1" + integrity sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ== + dependencies: + bignumber.js "^9.0.0" + json-buffer@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" @@ -4061,6 +4281,23 @@ jszip@^3.10.0: readable-stream "~2.3.6" setimmediate "^1.0.5" +jwa@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.1.tgz#bf8176d1ad0cd72e0f3f58338595a13e110bc804" + integrity sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg== + dependencies: + buffer-equal-constant-time "^1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.0.tgz#2d4e8cf6a318ffaa12615e9dec7e86e6c97310f4" + integrity sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg== + dependencies: + jwa "^2.0.0" + safe-buffer "^5.0.1" + jwt-decode@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-4.0.0.tgz#2270352425fd413785b2faf11f6e755c5151bd4b" @@ -4195,11 +4432,21 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +mantine-datatable@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/mantine-datatable/-/mantine-datatable-8.2.0.tgz#05ee4f8ed59a0138bff2b9aee8ecc36fbb34f448" + integrity sha512-dkdOnDw1DF9t5kxl4VEPM0baubNW2Tc+lvbc0bkVU1bNaDpXphSeoTGU6lai0sQ21hrKSMbfiK0ACczdrSOKyg== + markdown-table@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-3.0.4.tgz#fe44d6d410ff9d6f2ea1797a3f60aa4d2b631c2a" integrity sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw== +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + mdast-util-find-and-replace@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz#70a3174c894e14df722abf43bc250cbae44b11df" @@ -4788,6 +5035,11 @@ node-addon-api@^7.0.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558" integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ== +node-domexception@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + node-fetch@^1.0.1: version "1.7.3" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" @@ -4796,6 +5048,15 @@ node-fetch@^1.0.1: encoding "^0.1.11" is-stream "^1.0.1" +node-fetch@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.3.2.tgz#d1e889bacdf733b4ff3b2b243eb7a12866a0b78b" + integrity sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA== + dependencies: + data-uri-to-buffer "^4.0.0" + fetch-blob "^3.1.4" + formdata-polyfill "^4.0.10" + node-releases@^2.0.14: version "2.0.14" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" @@ -4811,6 +5072,11 @@ object-inspect@^1.13.1: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== +object-inspect@^1.13.3: + version "1.13.4" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" + integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== + object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -5159,6 +5425,13 @@ punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== +qs@^6.7.0: + version "6.14.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930" + integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w== + dependencies: + side-channel "^1.1.0" + qs@^6.9.1: version "6.11.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9" @@ -5220,7 +5493,7 @@ react-markdown@^9.0.0: unist-util-visit "^5.0.0" vfile "^6.0.0" -react-number-format@^5.4.3: +react-number-format@^5.4.4: version "5.4.4" resolved "https://registry.yarnpkg.com/react-number-format/-/react-number-format-5.4.4.tgz#d31f0e260609431500c8d3f81bbd3ae1fb7cacad" integrity sha512-wOmoNZoOpvMminhifQYiYSTCLUDOiUbBunrMrMjA+dV52sY+vck1S4UhR6PkgnoCquvvMSeJjErXZ4qSaWCliA== @@ -5238,10 +5511,10 @@ react-remove-scroll-bar@^2.3.7: react-style-singleton "^2.2.2" tslib "^2.0.0" -react-remove-scroll@^2.6.2: - version "2.6.3" - resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.6.3.tgz#df02cde56d5f2731e058531f8ffd7f9adec91ac2" - integrity sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ== +react-remove-scroll@^2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz#d2101d414f6d81d7d3bf033f3c1cb4785789f753" + integrity sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA== dependencies: react-remove-scroll-bar "^2.3.7" react-style-singleton "^2.2.3" @@ -5696,6 +5969,35 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +side-channel-list@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" + integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + +side-channel-map@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" + integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + side-channel-map "^1.0.1" + side-channel@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.5.tgz#9a84546599b48909fb6af1211708d23b1946221b" @@ -5706,6 +6008,17 @@ side-channel@^1.0.4: get-intrinsic "^1.2.4" object-inspect "^1.13.1" +side-channel@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" + integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + side-channel-list "^1.0.0" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" + simple-concat@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" @@ -5987,7 +6300,12 @@ tslib@^1.8.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0: +tslib@^2.0.0, tslib@^2.1.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + +tslib@^2.0.3: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== @@ -6018,7 +6336,7 @@ type-fest@^0.20.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== -type-fest@^4.27.0: +type-fest@^4.41.0: version "4.41.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58" integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== @@ -6195,6 +6513,11 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +url-template@^2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/url-template/-/url-template-2.0.8.tgz#fc565a3cccbff7730c775f5641f9555791439f21" + integrity sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw== + use-callback-ref@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.3.tgz#98d9fab067075841c5b2c6852090d5d0feabe2bf" @@ -6203,19 +6526,19 @@ use-callback-ref@^1.3.3: tslib "^2.0.0" use-composed-ref@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.3.0.tgz#3d8104db34b7b264030a9d916c5e94fbe280dbda" - integrity sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ== + version "1.4.0" + resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.4.0.tgz#09e023bf798d005286ad85cd20674bdf5770653b" + integrity sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w== use-isomorphic-layout-effect@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb" - integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA== + version "1.2.1" + resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz#2f11a525628f56424521c748feabc2ffcc962fce" + integrity sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA== use-latest@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/use-latest/-/use-latest-1.2.1.tgz#d13dfb4b08c28e3e33991546a2cee53e14038cf2" - integrity sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw== + version "1.3.0" + resolved "https://registry.yarnpkg.com/use-latest/-/use-latest-1.3.0.tgz#549b9b0d4c1761862072f0899c6f096eb379137a" + integrity sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ== dependencies: use-isomorphic-layout-effect "^1.1.1" @@ -6322,6 +6645,11 @@ web-namespaces@^2.0.0: resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-2.0.1.tgz#1010ff7c650eccb2592cebeeaf9a1b253fd40692" integrity sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ== +web-streams-polyfill@^3.0.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz#2073b91a2fdb1fbfbd401e7de0ac9f8214cecb4b" + integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw== + whatwg-fetch@>=0.10.0: version "3.6.20" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz#580ce6d791facec91d37c72890995a0b48d31c70"