diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/src/apps/admin/src/admin-app.routes.tsx b/src/apps/admin/src/admin-app.routes.tsx index de637b87f..1d6da0bd2 100644 --- a/src/apps/admin/src/admin-app.routes.tsx +++ b/src/apps/admin/src/admin-app.routes.tsx @@ -7,6 +7,9 @@ import { } from '~/libs/core' import { + aiReviewTemplatesRouteId, + aiReviewWorkflowsRouteId, + aiRouteId, billingAccountRouteId, defaultReviewersRouteId, gamificationAdminRouteId, @@ -173,6 +176,16 @@ const PaymentsPage: LazyLoadedComponent = lazyLoad( 'PaymentsPage', ) +const Ai: LazyLoadedComponent = lazyLoad(() => import('./ai/Ai')) +const AiReviewWorkflowsPage: LazyLoadedComponent = lazyLoad( + () => import('./ai/review-workflows/AiReviewWorkflowsPage'), + 'AiReviewWorkflowsPage', +) +const AiReviewTemplatesPage: LazyLoadedComponent = lazyLoad( + () => import('./ai/review-templates/AiReviewTemplatesPage'), + 'AiReviewTemplatesPage', +) + export const toolTitle: string = ToolTitle.admin export const adminRoutes: ReadonlyArray = [ @@ -418,6 +431,25 @@ export const adminRoutes: ReadonlyArray = [ rolesRequired: administratorOnlyRoles, route: paymentsRouteId, }, + // AI Module + { + children: [ + { + element: , + id: 'ai-review-workflows-page', + route: aiReviewWorkflowsRouteId, + }, + { + element: , + id: 'ai-review-templates-page', + route: aiReviewTemplatesRouteId, + }, + ], + element: , + id: aiRouteId, + rolesRequired: administratorOnlyRoles, + route: aiRouteId, + }, ], domain: AppSubdomain.admin, element: , diff --git a/src/apps/admin/src/ai/Ai.tsx b/src/apps/admin/src/ai/Ai.tsx new file mode 100644 index 000000000..572523285 --- /dev/null +++ b/src/apps/admin/src/ai/Ai.tsx @@ -0,0 +1,31 @@ +import { FC, useContext, useMemo } from 'react' +import { Outlet, Routes } from 'react-router-dom' + +import { routerContext, RouterContextData } from '~/libs/core' + +import { adminRoutes } from '../admin-app.routes' +import { aiRouteId } from '../config/routes.config' + +export const Ai: FC = () => { + const childRoutes = useChildRoutes() + + return ( + <> + + {childRoutes} + + ) +} + +function useChildRoutes(): Array | undefined { + const { getRouteElement }: RouterContextData = useContext(routerContext) + const childRoutes = useMemo( + () => adminRoutes[0].children + ?.find(r => r.id === aiRouteId) + ?.children?.map(getRouteElement), + [], // eslint-disable-line react-hooks/exhaustive-deps -- missing dependency: getRouteElement + ) + return childRoutes +} + +export default Ai diff --git a/src/apps/admin/src/ai/index.ts b/src/apps/admin/src/ai/index.ts new file mode 100644 index 000000000..7f855005e --- /dev/null +++ b/src/apps/admin/src/ai/index.ts @@ -0,0 +1 @@ +export { Ai } from './Ai' diff --git a/src/apps/admin/src/ai/review-templates/AiReviewTemplatesPage.module.scss b/src/apps/admin/src/ai/review-templates/AiReviewTemplatesPage.module.scss new file mode 100644 index 000000000..286cb8362 --- /dev/null +++ b/src/apps/admin/src/ai/review-templates/AiReviewTemplatesPage.module.scss @@ -0,0 +1,342 @@ +@import '@libs/ui/styles/includes'; +@import '../../lib/styles/includes'; + +.filtersWrapper { + display: flex; + align-items: center; + justify-content: space-between; + gap: $sp-4; + padding: $sp-4; + background-color: $black-5; + border-bottom: 1px solid $black-20; +} + +.filters { + display: flex; + align-items: center; + gap: $sp-4; + flex-wrap: nowrap; + + > *:not(button) { + min-width: 200px; + } +} + +@media (max-width: #{$mobile-max}) { + .filtersWrapper { + flex-direction: column; + align-items: stretch; + } + + .filters { + flex-direction: column; + align-items: stretch; + + > * { + min-width: auto; + } + } +} + +.templatesList { + display: flex; + flex-direction: column; + gap: $sp-3; + margin-bottom: $sp-4; +} + +.templateItem { + border: 1px solid $black-20; + border-radius: $sp-2; + overflow: hidden; +} + +.templateHeader { + display: flex; + flex-direction: column; + gap: $sp-2; + padding: $sp-3 $sp-4; + background-color: $black-5; + cursor: pointer; + transition: background-color 0.2s ease; + + &:hover { + background-color: $black-10; + } +} + +.templateHeaderTop { + display: flex; + align-items: center; + justify-content: space-between; + gap: $sp-3; +} + +.templateHeaderLeft { + display: flex; + align-items: center; + gap: $sp-2; + flex: 1; + min-width: 0; +} + +.templateHeaderRight { + display: flex; + align-items: center; + gap: $sp-2; + flex-shrink: 0; +} + +.editButton, +.deleteButton { + display: flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + padding: 0; + border: none; + border-radius: 50%; + background-color: transparent; + color: $black-40; + cursor: pointer; + transition: all 0.2s ease; + + svg { + width: 16px; + height: 16px; + } +} + +.editButton:hover { + background-color: rgba($blue-110, 0.1); + color: $blue-110; +} + +.deleteButton:hover { + background-color: rgba($red-100, 0.1); + color: $red-100; +} + +.templateDescription { + font-size: 13px; + color: $black-60; + padding-left: 20px; + line-height: 1.4; +} + +.templateMetas { + display: flex; + align-items: center; + gap: $sp-2; + padding-left: 20px; +} + +.expandIcon { + font-size: 10px; + color: $black-60; + width: 12px; + flex-shrink: 0; +} + +.templateName { + font-weight: 600; + font-size: 15px; + color: $black-100; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.templateMeta { + display: inline-flex; + align-items: center; + gap: $sp-1; + font-size: 12px; + font-weight: 500; + color: $purple-120; + background: linear-gradient(135deg, rgba($purple-25, 0.6), rgba($purple-25, 0.3)); + border: 1px solid rgba($purple-100, 0.2); + padding: 4px $sp-3; + border-radius: 16px; + white-space: nowrap; + + &::before { + content: '📂'; + font-size: 11px; + } +} + +.modeBadge { + display: inline-flex; + align-items: center; + gap: $sp-1; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + color: $tc-white; + background: linear-gradient(135deg, $turq-140, $turq-160); + padding: 4px $sp-3; + border-radius: 16px; + white-space: nowrap; + box-shadow: 0 1px 3px rgba($turq-160, 0.3); + + &::before { + content: '⚙️'; + font-size: 10px; + } +} + +.thresholdBadge { + display: inline-flex; + align-items: center; + gap: $sp-1; + font-size: 12px; + font-weight: 600; + color: $green-140; + background: linear-gradient(135deg, rgba($green-25, 0.8), rgba($green-25, 0.4)); + border: 1px solid rgba($green-100, 0.25); + padding: 4px $sp-3; + border-radius: 16px; + white-space: nowrap; + + &::before { + content: '✓'; + font-size: 11px; + font-weight: 700; + } +} + +.workflowCount { + font-size: 12px; + color: $black-60; + white-space: nowrap; +} + +.disabledBadge { + font-size: 10px; + font-weight: 600; + text-transform: uppercase; + color: $tc-white; + background-color: $red-100; + padding: 4px $sp-3; + border-radius: 16px; +} + +.toggleWrapper { + display: flex; + align-items: center; + gap: $sp-2; + cursor: pointer; + padding: 4px $sp-2; + border-radius: $sp-1; + transition: background-color 0.2s ease; + + &:hover { + background-color: rgba($black-100, 0.05); + } +} + +.toggleLabel { + font-size: 12px; + font-weight: 500; + color: $black-60; +} + +.workflowsTree { + padding: $sp-3 $sp-4; + background-color: $tc-white; + border-top: 1px solid $black-10; +} + +.workflowItem { + display: flex; + align-items: center; + justify-content: space-between; + gap: $sp-3; + padding: $sp-3; + margin-left: $sp-4; + cursor: pointer; + border-radius: $sp-1; + border: 1px solid transparent; + transition: all 0.2s ease; + + &:hover { + background-color: $black-5; + border-color: $black-20; + } + + &:not(:last-child) { + margin-bottom: $sp-2; + } +} + +.workflowLeft { + display: flex; + align-items: center; + gap: $sp-2; + flex: 1; + min-width: 0; +} + +.workflowRight { + display: flex; + align-items: center; + gap: $sp-3; + flex-shrink: 0; +} + +.treeLine { + font-family: monospace; + color: $black-40; + font-size: 14px; + flex-shrink: 0; +} + +.workflowNameWrap { + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; +} + +.workflowName { + font-size: 14px; + color: $link-blue-dark; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + &:hover { + text-decoration: underline; + } +} + +.workflowNameDisabled { + font-size: 14px; + color: $black-40; + text-decoration: line-through; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.workflowHint { + font-size: 11px; + color: $black-40; + font-style: italic; +} + +.workflowWeight { + font-size: 13px; + font-weight: 600; + color: $black-80; +} + +.workflowGating { + font-size: 12px; + color: $orange-140; +} + + diff --git a/src/apps/admin/src/ai/review-templates/AiReviewTemplatesPage.tsx b/src/apps/admin/src/ai/review-templates/AiReviewTemplatesPage.tsx new file mode 100644 index 000000000..101736764 --- /dev/null +++ b/src/apps/admin/src/ai/review-templates/AiReviewTemplatesPage.tsx @@ -0,0 +1,472 @@ +import { ChangeEvent, FC, useCallback, useEffect, useMemo, useState } from 'react' +import { toast } from 'react-toastify' +import _ from 'lodash' + +import { BaseModal, Button, FormToggleSwitch, IconOutline, InputSelect, InputSelectOption } from '~/libs/ui' + +import { ConfirmModal, PageWrapper, TableLoading, TableNoRecord } from '../../lib' +import { TableWrapper } from '../../lib/components/common/TableWrapper' +import { ChallengeTrack, ChallengeType } from '../../lib/models' +import { getChallengeTracks, getChallengeTypes } from '../../lib/services/challenge-management.service' +import { AiWorkflow } from '../../lib/services/ai-workflows.service' +import { + AiReviewTemplate, + AiReviewTemplatesFilter, + deleteAiReviewTemplate, + getAiReviewTemplates, + TemplateWorkflowItem, + updateAiReviewTemplate, +} from '../../lib/services/ai-templates.service' +import { WorkflowDetailsModal } from '../review-workflows/WorkflowDetailsModal' + +import { CreateTemplateModal } from './CreateTemplateModal' +import styles from './AiReviewTemplatesPage.module.scss' + +interface WorkflowItemProps { + item: TemplateWorkflowItem + onClick: (workflow: AiWorkflow) => void +} + +const WorkflowItem: FC = (props: WorkflowItemProps) => { + const workflow: AiWorkflow = props.item.workflow + + const handleClick = useCallback(() => { + props.onClick(workflow) + }, [props, workflow]) + + return ( +
+
+ └─ +
+ + {workflow.name} + + {workflow.disabled && ( + This workflow is disabled + )} +
+
+
+ {props.item.isGating && ( + Gating + )} + + {props.item.weightPercent} + % + +
+
+ ) +} + +interface TemplateItemProps { + template: AiReviewTemplate + onWorkflowClick: (workflow: AiWorkflow) => void + onEdit: (template: AiReviewTemplate) => void + onDelete: (template: AiReviewTemplate) => void + onToggleDisabled: (template: AiReviewTemplate) => void +} + +const TemplateItem: FC = (props: TemplateItemProps) => { + const [expanded, setExpanded] = useState(true) + const workflows = props.template.workflows || [] + + const handleToggleExpand = useCallback(() => { + setExpanded(prev => !prev) + }, []) + + const handleEditClick = useCallback((e: React.MouseEvent) => { + e.stopPropagation() + props.onEdit(props.template) + }, [props]) + + const handleDeleteClick = useCallback((e: React.MouseEvent) => { + e.stopPropagation() + props.onDelete(props.template) + }, [props]) + + const handleToggleClick = useCallback((e: React.MouseEvent) => { + e.stopPropagation() + props.onToggleDisabled(props.template) + }, [props]) + + return ( +
+
+
+
+ {expanded ? '▼' : '▶'} + + {props.template.title || props.template.id} + +
+
+ {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, + jsx-a11y/no-static-element-interactions */} +
+ Active + +
+ + +
+
+ {props.template.description && ( +
+ {props.template.description} +
+ )} +
+ + {props.template.challengeTrack} + {' / '} + {props.template.challengeType} + + {props.template.mode} + + Pass: + {' '} + {props.template.minPassingThreshold} + % + +
+
+ {expanded && workflows.length > 0 && ( +
+ {workflows.map(item => ( + + ))} +
+ )} +
+ ) +} + +export const AiReviewTemplatesPage: FC = () => { + const [isLoading, setIsLoading] = useState(true) + const [templates, setTemplates] = useState([]) + const [tracks, setTracks] = useState([]) + const [types, setTypes] = useState([]) + const [filter, setFilter] = useState({}) + const [detailModal, setDetailModal] = useState<{ open: boolean; workflow?: AiWorkflow }>({ + open: false, + }) + const [createModalOpen, setCreateModalOpen] = useState(false) + const [editTemplate, setEditTemplate] = useState(undefined) + const [deleteModal, setDeleteModal] = useState<{ open: boolean; template?: AiReviewTemplate }>({ + open: false, + }) + const [isDeleting, setIsDeleting] = useState(false) + const [toggleModal, setToggleModal] = useState<{ open: boolean; template?: AiReviewTemplate }>({ + open: false, + }) + const [isToggling, setIsToggling] = useState(false) + + const trackOptions: InputSelectOption[] = useMemo(() => { + const seen = new Set() + const options: InputSelectOption[] = [{ label: 'All Tracks', value: '' }] + for (const t of tracks) { + const trackValue: string = (t as ChallengeTrack & { track?: string }).track || t.name.toUpperCase() + if (!seen.has(trackValue)) { + seen.add(trackValue) + options.push({ label: t.name, value: trackValue }) + } + } + + return options + }, [tracks]) + + const typeOptions: InputSelectOption[] = useMemo(() => { + const seen = new Set() + const options: InputSelectOption[] = [{ label: 'All Types', value: '' }] + for (const t of types) { + if (!seen.has(t.name)) { + seen.add(t.name) + options.push({ label: t.name, value: t.name }) + } + } + + return options + }, [types]) + + useEffect(() => { + getChallengeTracks() + .then(setTracks) + .catch(() => setTracks([])) + getChallengeTypes() + .then(setTypes) + .catch(() => setTypes([])) + }, []) + + const loadTemplates = useCallback((filterParams: AiReviewTemplatesFilter) => { + setIsLoading(true) + getAiReviewTemplates(filterParams) + .then(data => { + setTemplates(data || []) + }) + .catch(() => { + setTemplates([]) + }) + .finally(() => setIsLoading(false)) + }, []) + + useEffect(() => { + loadTemplates(filter) + }, [filter, loadTemplates]) + + const handleTrackChange = useCallback((event: ChangeEvent) => { + setFilter(prev => ({ ...prev, challengeTrack: event.target.value || undefined })) + }, []) + + const handleTypeChange = useCallback((event: ChangeEvent) => { + setFilter(prev => ({ ...prev, challengeType: event.target.value || undefined })) + }, []) + + const handleReset = useCallback(() => { + setFilter({}) + }, []) + + const handleWorkflowClick = useCallback((workflow: AiWorkflow) => { + setDetailModal({ open: true, workflow }) + }, []) + + const handleCloseDetail = useCallback(() => { + setDetailModal({ open: false }) + }, []) + + const handleOpenCreateModal = useCallback(() => { + setEditTemplate(undefined) + setCreateModalOpen(true) + }, []) + + const handleCloseCreateModal = useCallback(() => { + setCreateModalOpen(false) + setEditTemplate(undefined) + }, []) + + const handleEditClick = useCallback((template: AiReviewTemplate) => { + setEditTemplate(template) + setCreateModalOpen(true) + }, []) + + const handleTemplateCreated = useCallback(() => { + loadTemplates(filter) + }, [filter, loadTemplates]) + + const handleDeleteClick = useCallback((template: AiReviewTemplate) => { + setDeleteModal({ open: true, template }) + }, []) + + const handleCloseDeleteModal = useCallback(() => { + setDeleteModal({ open: false }) + }, []) + + const handleConfirmDelete = useCallback(async () => { + if (!deleteModal.template) return + setIsDeleting(true) + try { + await deleteAiReviewTemplate(deleteModal.template.id) + setDeleteModal({ open: false }) + loadTemplates(filter) + } catch { + // Error handling can be added here + } finally { + setIsDeleting(false) + } + }, [deleteModal.template, filter, loadTemplates]) + + const handleToggleClick = useCallback((template: AiReviewTemplate) => { + setToggleModal({ open: true, template }) + }, []) + + const handleCloseToggleModal = useCallback(() => { + setToggleModal({ open: false }) + }, []) + + const handleConfirmToggle = useCallback(async () => { + if (!toggleModal.template) return + setIsToggling(true) + const newDisabledState = !toggleModal.template.disabled + try { + await updateAiReviewTemplate(toggleModal.template.id, { + disabled: newDisabledState, + }) + setTemplates(prev => prev.map(t => ( + t.id === toggleModal.template?.id + ? { ...t, disabled: newDisabledState } + : t + ))) + toast.success(`Template ${newDisabledState ? 'deactivated' : 'activated'} successfully`) + setToggleModal({ open: false }) + } catch (error) { + toast.error('Failed to update template') + } finally { + setIsToggling(false) + } + }, [toggleModal.template]) + + const hasFilters: boolean = !!filter.challengeTrack || !!filter.challengeType + + return ( + +
+
+ + + {hasFilters && ( +
+
+ + + + {isLoading && } + + {!isLoading && templates.length === 0 && ( + + )} + + {!isLoading && templates.length > 0 && ( +
+ {templates.map(template => ( + + ))} +
+ )} +
+ + + + + + + + + + {(errors.workflows?.message || errors.workflows?.root?.message) && ( +

+ {(errors.workflows?.message || errors.workflows?.root?.message) as string} +

+ )} + + {fields.map((field, index) => ( +
+ + }) { + const currentValue: string = controlProps.field.value + const currentOption: InputSelectOption | undefined = workflowOptions + .find(o => o.value === currentValue) + const options: InputSelectOption[] = currentOption + ? [currentOption, ...availableWorkflowOptions] + : availableWorkflowOptions + + return ( + + ) + }} + /> + + + + + }) { + return ( +
+ + Gating +
+ ) + }} + /> + + +
+ ))} + + {fields.length === 0 && ( +

No workflows added yet.

+ )} + + +
+ + +
+ + {isSubmitting && ( +
+ +
+ )} + + )} +
+ ) +} + +export default CreateTemplateModal diff --git a/src/apps/admin/src/ai/review-templates/index.ts b/src/apps/admin/src/ai/review-templates/index.ts new file mode 100644 index 000000000..7a9332eb1 --- /dev/null +++ b/src/apps/admin/src/ai/review-templates/index.ts @@ -0,0 +1 @@ +export { AiReviewTemplatesPage } from './AiReviewTemplatesPage' diff --git a/src/apps/admin/src/ai/review-workflows/AiReviewWorkflowsPage.module.scss b/src/apps/admin/src/ai/review-workflows/AiReviewWorkflowsPage.module.scss new file mode 100644 index 000000000..10418ab0e --- /dev/null +++ b/src/apps/admin/src/ai/review-workflows/AiReviewWorkflowsPage.module.scss @@ -0,0 +1,123 @@ +@import '@libs/ui/styles/includes'; + +.table { + margin-bottom: $sp-4; + + tbody tr:nth-child(odd) { + background-color: $black-5; + } +} + +.mobileTable { + width: 100%; + border-collapse: collapse; + margin-bottom: $sp-4; + + tbody tr { + td { + padding: $sp-2 $sp-3 !important; + border-radius: 0 !important; + background-color: $tc-white !important; + vertical-align: middle; + + &:first-child { + font-weight: 600; + font-size: 11px; + text-transform: uppercase; + color: $black-60; + white-space: nowrap; + width: 100px; + min-width: 100px; + max-width: 100px; + } + + &:last-child { + word-break: break-all; + overflow-wrap: anywhere; + text-align: right; + } + } + + // 5 fields per workflow, alternate every other workflow + &:nth-child(10n+1) td, + &:nth-child(10n+2) td, + &:nth-child(10n+3) td, + &:nth-child(10n+4) td, + &:nth-child(10n+5) td { + background-color: $black-5 !important; + } + } + + @media (max-width: 400px) { + tbody tr td { + padding: $sp-1 $sp-2 !important; + font-size: 13px; + + &:first-child { + font-size: 10px; + width: 85px; + min-width: 85px; + max-width: 85px; + } + } + } +} + +.link { + color: $link-blue-dark; + font-size: 14px; + font-weight: 400; + text-decoration: none; + cursor: pointer; + + &:hover { + text-decoration: underline; + } +} + +.cellText { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 250px; + + @media (max-width: 1050px) { + white-space: normal; + word-break: break-all; + overflow-wrap: anywhere; + max-width: none; + } +} + +.nameCell { + word-break: break-word; + white-space: normal; +} + +.nameLink { + background: none; + border: none; + padding: 0; + color: $link-blue-dark; + font-size: 14px; + font-weight: 400; + text-decoration: none; + cursor: pointer; + text-align: left; + word-break: break-word; + white-space: normal; + + &:hover { + text-decoration: underline; + } + + @media (max-width: 1050px) { + text-align: right; + display: block; + width: 100%; + } +} + +.toggle { + cursor: pointer; +} diff --git a/src/apps/admin/src/ai/review-workflows/AiReviewWorkflowsPage.tsx b/src/apps/admin/src/ai/review-workflows/AiReviewWorkflowsPage.tsx new file mode 100644 index 000000000..4e50753f1 --- /dev/null +++ b/src/apps/admin/src/ai/review-workflows/AiReviewWorkflowsPage.tsx @@ -0,0 +1,273 @@ +import { FC, useCallback, useEffect, useMemo, useState } from 'react' +import { toast } from 'react-toastify' + +import { EnvironmentConfig } from '~/config' +import { useWindowSize, WindowSize } from '~/libs/shared' +import { Table, TableColumn } from '~/libs/ui' +import FormToggleSwitch from '~/libs/ui/lib/components/form/form-groups/form-toggle-switch' + +import { ConfirmModal, PageWrapper, TableLoading, TableNoRecord } from '../../lib' +import { TableMobile } from '../../lib/components/common/TableMobile' +import { TableWrapper } from '../../lib/components/common/TableWrapper' +import { MobileTableColumn } from '../../lib/models' +import { AiWorkflow, getAiWorkflows, updateAiWorkflow } from '../../lib/services/ai-workflows.service' + +import { WorkflowDetailsModal } from './WorkflowDetailsModal' +import styles from './AiReviewWorkflowsPage.module.scss' + +function stopPropagation(e: React.MouseEvent): void { + e.stopPropagation() +} + +export const AiReviewWorkflowsPage: FC = () => { + const [isLoading, setIsLoading] = useState(true) + const [isUpdating, setIsUpdating] = useState(false) + const [workflows, setWorkflows] = useState([]) + const [confirmModal, setConfirmModal] = useState<{ open: boolean; workflow?: AiWorkflow }>({ + open: false, + }) + const [detailModal, setDetailModal] = useState<{ open: boolean; workflow?: AiWorkflow }>({ + open: false, + }) + const { width: screenWidth }: WindowSize = useWindowSize() + const isMobile = useMemo(() => screenWidth <= 1050, [screenWidth]) + + useEffect(() => { + setIsLoading(true) + getAiWorkflows() + .then(setWorkflows) + .finally(() => setIsLoading(false)) + }, []) + + const handleToggleClick = useCallback((workflow: AiWorkflow) => { + setConfirmModal({ open: true, workflow }) + }, []) + + const handleCloseConfirm = useCallback(() => { + setConfirmModal({ open: false }) + }, []) + + const handleNameClick = useCallback((workflow: AiWorkflow) => { + setDetailModal({ open: true, workflow }) + }, []) + + const handleCloseDetail = useCallback(() => { + setDetailModal({ open: false }) + }, []) + + const handleConfirmToggle = useCallback(async () => { + if (!confirmModal.workflow) { + return + } + + const newDisabledState = !confirmModal.workflow.disabled + + setIsUpdating(true) + try { + await updateAiWorkflow(confirmModal.workflow.id, { + disabled: newDisabledState, + }) + setWorkflows(prev => prev.map(w => ( + w.id === confirmModal.workflow?.id + ? { ...w, disabled: newDisabledState } + : w + ))) + toast.success(`Workflow ${newDisabledState ? 'deactivated' : 'activated'} successfully`) + } catch (error) { + toast.error('Failed to update workflow') + } finally { + setIsUpdating(false) + setConfirmModal({ open: false }) + } + }, [confirmModal.workflow]) + + const columns = useMemo[]>(() => [ + { + label: 'ID', + propertyName: 'id', + renderer: (data: AiWorkflow) => ( +
{data.id}
+ ), + type: 'element', + }, + { + defaultSortDirection: 'asc', + label: 'Active', + propertyName: 'disabled', + renderer: (data: AiWorkflow) => { + function onToggleChange(): void { + handleToggleClick(data) + } + + return ( +
+ +
+ ) + }, + type: 'element', + }, + { + defaultSortDirection: 'asc', + isDefaultSort: true, + label: 'Name', + propertyName: 'name', + renderer: (data: AiWorkflow) => { + function onNameClick(e: React.MouseEvent): void { + e.stopPropagation() + handleNameClick(data) + } + + return ( + + ) + }, + type: 'element', + }, + { + defaultSortDirection: 'asc', + label: 'Provider/LLM', + propertyName: 'llm.provider.name', + renderer: (data: AiWorkflow) => ( +
+ {data.llm?.provider?.name} + / + {data.llm?.name} +
+ ), + type: 'element', + }, + { + defaultSortDirection: 'asc', + label: 'Scorecard', + propertyName: 'scorecard.name', + renderer: (data: AiWorkflow) => { + if (!data.scorecard?.id) { + return {data.scorecard?.name || 'N/A'} + } + + return ( + + {data.scorecard.name} + + ) + }, + type: 'element', + }, + ], []) + + const columnsMobile = useMemo[][]>( + () => columns.map(column => { + if (column.label === 'Active') { + return [ + { + ...column, + className: '', + label: 'Active label', + mobileType: 'label', + renderer: () =>
Active:
, + type: 'element', + }, + { + ...column, + mobileType: 'last-value', + }, + ] + } + + return [ + { + ...column, + className: '', + label: `${column.label as string} label`, + mobileType: 'label', + renderer: () => ( +
+ {column.label as string} + : +
+ ), + type: 'element', + }, + { + ...column, + mobileType: 'last-value', + }, + ] + }), + [columns], + ) + + return ( + + {isLoading ? ( + + ) : ( + <> + {workflows.length === 0 ? ( + + ) : ( + + {isMobile ? ( + + ) : ( + + )} + + )} + + )} + +

+ Are you sure you want to + {' '} + {confirmModal.workflow?.disabled ? 'activate' : 'deactivate'} + {' '} + the workflow + {' '} + {confirmModal.workflow?.name} + ? +

+
+ + + ) +} + +export default AiReviewWorkflowsPage diff --git a/src/apps/admin/src/ai/review-workflows/WorkflowDetailsModal.module.scss b/src/apps/admin/src/ai/review-workflows/WorkflowDetailsModal.module.scss new file mode 100644 index 000000000..e984792c5 --- /dev/null +++ b/src/apps/admin/src/ai/review-workflows/WorkflowDetailsModal.module.scss @@ -0,0 +1,180 @@ +@import '@libs/ui/styles/includes'; + +.container { + display: flex; + flex-direction: column; + gap: $sp-6; +} + +.section { + display: flex; + flex-direction: column; + gap: $sp-3; +} + +.sectionTitle { + font-size: 14px; + font-weight: 700; + color: $black-100; + margin: 0; + padding-bottom: $sp-2; + border-bottom: 1px solid $black-20; +} + +.grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: $sp-3; + + @include ltemd { + grid-template-columns: 1fr; + } +} + +.field { + display: flex; + flex-direction: column; + gap: $sp-1; +} + +.label { + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + color: $black-60; +} + +.value { + font-size: 14px; + color: $black-100; + word-break: break-word; +} + +.link { + color: $link-blue-dark; + text-decoration: none; + font-size: 14px; + + &:hover { + text-decoration: underline; + } +} + +.descriptionField { + display: flex; + flex-direction: column; + gap: $sp-2; + margin-top: $sp-3; +} + +.descriptionValue { + font-size: 14px; + color: $black-100; + background-color: $black-5; + border-radius: $sp-2; + padding: $sp-4; + max-height: 300px; + overflow-y: auto; + + a { + color: $link-blue-dark; + text-decoration: none; + font-size: 14px; + + &:hover { + text-decoration: underline; + } + } + + // Markdown normalization + h1, h2, h3, h4, h5, h6 { + margin: $sp-3 0 $sp-2; + font-weight: 700; + + &:first-child { + margin-top: 0; + } + } + + h1 { font-size: 18px; } + h2 { font-size: 16px; } + h3 { font-size: 15px; } + h4, h5, h6 { font-size: 14px; } + + p { + margin: 0 0 $sp-2; + + &:last-child { + margin-bottom: 0; + } + } + + ul, ol { + margin: 0 0 $sp-2; + padding-left: $sp-5; + } + + li { + margin-bottom: $sp-1; + } + + table { + width: 100%; + border-collapse: collapse; + margin: $sp-2 0; + font-size: 13px; + + th, td { + border: 1px solid $black-20; + padding: $sp-2; + text-align: left; + } + + th { + background-color: $black-10; + font-weight: 600; + } + } + + code { + background-color: $black-10; + padding: 2px $sp-1; + border-radius: 3px; + font-family: monospace; + font-size: 13px; + } + + pre { + background-color: $black-10; + padding: $sp-3; + border-radius: $sp-1; + overflow-x: auto; + margin: $sp-2 0; + + code { + background: none; + padding: 0; + } + } + + blockquote { + border-left: 3px solid $black-40; + margin: $sp-2 0; + padding-left: $sp-3; + color: $black-80; + } + + hr { + border: none; + border-top: 1px solid $black-20; + margin: $sp-3 0; + } + + strong { + font-weight: 700; + } + + em { + font-style: italic; + } +} diff --git a/src/apps/admin/src/ai/review-workflows/WorkflowDetailsModal.tsx b/src/apps/admin/src/ai/review-workflows/WorkflowDetailsModal.tsx new file mode 100644 index 000000000..bc0bba7f8 --- /dev/null +++ b/src/apps/admin/src/ai/review-workflows/WorkflowDetailsModal.tsx @@ -0,0 +1,217 @@ +import { FC } from 'react' + +import { EnvironmentConfig } from '~/config' +import { BaseModal, Button } from '~/libs/ui' +import { MarkdownReview } from '~/apps/review/src/lib/components/MarkdownReview' + +import { AiWorkflow } from '../../lib/services/ai-workflows.service' + +import styles from './WorkflowDetailsModal.module.scss' + +interface SectionProps { + workflow: AiWorkflow +} + +const GeneralSection: FC = (props: SectionProps) => ( +
+

General

+
+
+ ID + {props.workflow.id} +
+
+ Name + {props.workflow.name} +
+
+ Status + + {props.workflow.disabled ? 'Inactive' : 'Active'} + +
+
+ Definition URL + + {props.workflow.defUrl ? ( + + {props.workflow.defUrl} + + ) : 'N/A'} + +
+
+ Git Workflow ID + {props.workflow.gitWorkflowId || 'N/A'} +
+
+ Git Owner/Repo + {props.workflow.gitOwnerRepo || 'N/A'} +
+
+
+ Description +
+ {props.workflow.description ? ( + + ) : 'N/A'} +
+
+
+) + +const LlmSection: FC = (props: SectionProps) => ( +
+

LLM Configuration

+
+
+ Provider + {props.workflow.llm?.provider?.name || 'N/A'} +
+
+ Model + {props.workflow.llm?.name || 'N/A'} +
+
+ LLM URL + + {props.workflow.llm?.url ? ( + + {props.workflow.llm.url} + + ) : 'N/A'} + +
+
+
+) + +const ScorecardSection: FC = (props: SectionProps) => ( +
+

Scorecard

+
+
+ Name + + {props.workflow.scorecard?.id ? ( + + {props.workflow.scorecard.name} + + ) : (props.workflow.scorecard?.name || 'N/A')} + +
+
+ Type + {props.workflow.scorecard?.type || 'N/A'} +
+
+ Status + {props.workflow.scorecard?.status || 'N/A'} +
+
+
+) + +interface MetadataSectionProps extends SectionProps { + createdAt: string + updatedAt: string +} + +const MetadataSection: FC = (props: MetadataSectionProps) => ( +
+

Metadata

+
+
+ Created At + {props.createdAt} +
+
+ Created By + {props.workflow.createdBy || 'N/A'} +
+
+ Updated At + {props.updatedAt} +
+
+ Updated By + {props.workflow.updatedBy || 'N/A'} +
+
+
+) + +interface WorkflowDetailsContentProps { + workflow: AiWorkflow + open: boolean + onClose: () => void +} + +const WorkflowDetailsContent: FC = ( + props: WorkflowDetailsContentProps, +) => { + const createdAtFormatted: string = props.workflow.createdAt + ? new Date(props.workflow.createdAt) + .toLocaleString() + : 'N/A' + + const updatedAtFormatted: string = props.workflow.updatedAt + ? new Date(props.workflow.updatedAt) + .toLocaleString() + : 'N/A' + + return ( + + )} + > +
+ + + + +
+
+ ) +} + +interface WorkflowDetailsModalProps { + workflow?: AiWorkflow + open: boolean + onClose: () => void +} + +export const WorkflowDetailsModal: FC = ( + props: WorkflowDetailsModalProps, +) => { + if (!props.workflow) { + return <> + } + + return ( + + ) +} diff --git a/src/apps/admin/src/ai/review-workflows/index.ts b/src/apps/admin/src/ai/review-workflows/index.ts new file mode 100644 index 000000000..7a149d4ff --- /dev/null +++ b/src/apps/admin/src/ai/review-workflows/index.ts @@ -0,0 +1 @@ +export { AiReviewWorkflowsPage } from './AiReviewWorkflowsPage' diff --git a/src/apps/admin/src/config/routes.config.ts b/src/apps/admin/src/config/routes.config.ts index a2ebf2790..2940f32cb 100644 --- a/src/apps/admin/src/config/routes.config.ts +++ b/src/apps/admin/src/config/routes.config.ts @@ -18,3 +18,6 @@ export const termsRouteId = 'terms' export const defaultReviewersRouteId = 'default-reviewers' export const platformRouteId = 'platform' export const paymentsRouteId = 'payments' +export const aiRouteId = 'ai' +export const aiReviewWorkflowsRouteId = 'review-workflows' +export const aiReviewTemplatesRouteId = 'review-templates' diff --git a/src/apps/admin/src/lib/components/common/DropdownMenu/DropdownMenu.module.scss b/src/apps/admin/src/lib/components/common/DropdownMenu/DropdownMenu.module.scss index 3291b7c1d..548a0d707 100644 --- a/src/apps/admin/src/lib/components/common/DropdownMenu/DropdownMenu.module.scss +++ b/src/apps/admin/src/lib/components/common/DropdownMenu/DropdownMenu.module.scss @@ -11,11 +11,13 @@ border-radius: 0 0 $sp-1 $sp-1; padding: $sp-2 0; max-height: 320px; - overflow: auto; + overflow-y: auto; + overflow-x: hidden; + min-width: max-content; :global { ul > li { text-align: left; - + white-space: nowrap; font-weight: normal; color: $black-100; padding: $sp-2 $sp-4; diff --git a/src/apps/admin/src/lib/components/common/Tab/config/system-admin-tabs-config.ts b/src/apps/admin/src/lib/components/common/Tab/config/system-admin-tabs-config.ts index 1aa72a376..dff10303a 100644 --- a/src/apps/admin/src/lib/components/common/Tab/config/system-admin-tabs-config.ts +++ b/src/apps/admin/src/lib/components/common/Tab/config/system-admin-tabs-config.ts @@ -3,6 +3,9 @@ import _ from 'lodash' import { TabsNavItem } from '~/libs/ui' import { isAdministrator } from '~/apps/admin/src/lib/utils/access' import { + aiReviewTemplatesRouteId, + aiReviewWorkflowsRouteId, + aiRouteId, billingAccountRouteId, defaultReviewersRouteId, gamificationAdminRouteId, @@ -83,6 +86,20 @@ export const SystemAdminTabsConfig: TabsNavItem[] = [ id: paymentsRouteId, title: 'Payments', }, + { + children: [ + { + id: `${aiRouteId}/${aiReviewWorkflowsRouteId}`, + title: 'AI Review Workflows', + }, + { + id: `${aiRouteId}/${aiReviewTemplatesRouteId}`, + title: 'AI Review Templates', + }, + ], + id: aiRouteId, + title: 'AI', + }, ] /** diff --git a/src/apps/admin/src/lib/services/ai-templates.service.ts b/src/apps/admin/src/lib/services/ai-templates.service.ts new file mode 100644 index 000000000..7c19e24af --- /dev/null +++ b/src/apps/admin/src/lib/services/ai-templates.service.ts @@ -0,0 +1,100 @@ +import { EnvironmentConfig } from '~/config' +import { xhrDeleteAsync, xhrGetAsync, xhrPostAsync, xhrPutAsync } from '~/libs/core' + +import { AiWorkflow } from './ai-workflows.service' + +export interface TemplateWorkflowItem { + id: string + workflowId: string + weightPercent: number + isGating: boolean + workflow: AiWorkflow +} + +export interface AiReviewTemplate { + id: string + title: string + description: string + challengeTrack: string + challengeType: string + version: number + minPassingThreshold: number + mode: string + autoFinalize: boolean + disabled: boolean + createdAt: string + updatedAt: string + workflows: TemplateWorkflowItem[] +} + +export interface AiReviewTemplatesFilter { + challengeTrack?: string + challengeType?: string +} + +export async function getAiReviewTemplates( + filter?: AiReviewTemplatesFilter, +): Promise { + const params = new URLSearchParams() + + if (filter?.challengeTrack) { + params.append('challengeTrack', filter.challengeTrack) + } + + if (filter?.challengeType) { + params.append('challengeType', filter.challengeType) + } + + const query: string = params.toString() + const url: string = `${EnvironmentConfig.API.V6}/ai-review/templates${query ? `?${query}` : ''}` + const response = await xhrGetAsync(url) + return response +} + +export interface CreateTemplateWorkflow { + workflowId: string + weightPercent: number + isGating: boolean +} + +export interface CreateAiReviewTemplateRequest { + challengeTrack: string + challengeType: string + title: string + description: string + minPassingThreshold: number + mode: string + autoFinalize: boolean + formula?: Record + disabled: boolean + workflows: CreateTemplateWorkflow[] +} + +export async function createAiReviewTemplate( + data: CreateAiReviewTemplateRequest, +): Promise { + const response = await xhrPostAsync( + `${EnvironmentConfig.API.V6}/ai-review/templates`, + data, + ) + return response +} + +export async function deleteAiReviewTemplate(id: string): Promise { + await xhrDeleteAsync(`${EnvironmentConfig.API.V6}/ai-review/templates/${id}`) +} + +export type UpdateAiReviewTemplateRequest = Partial< + Omit +> + +export async function updateAiReviewTemplate( + id: string, + data: UpdateAiReviewTemplateRequest, +): Promise { + const response = await xhrPutAsync( + `${EnvironmentConfig.API.V6}/ai-review/templates/${id}`, + data, + ) + return response +} diff --git a/src/apps/admin/src/lib/services/ai-workflows.service.ts b/src/apps/admin/src/lib/services/ai-workflows.service.ts index b960cf7cb..200006844 100644 --- a/src/apps/admin/src/lib/services/ai-workflows.service.ts +++ b/src/apps/admin/src/lib/services/ai-workflows.service.ts @@ -1,12 +1,73 @@ import { EnvironmentConfig } from '~/config' -import { xhrGetAsync } from '~/libs/core' +import { xhrGetAsync, xhrPatchAsync } from '~/libs/core' + +export interface LlmProvider { + id: string; + name: string; + createdAt: string; + createdBy: string; +} + +export interface Llm { + id: string; + providerId: string; + name: string; + description: string; + icon: string; + url: string; + createdAt: string; + createdBy: string; + provider: LlmProvider; +} + +export interface Scorecard { + id: string; + legacyId: string; + status: string; + type: string; + challengeTrack: string; + challengeType: string; + name: string; + version: string; + minScore: number; + minimumPassingScore: number; + maxScore: number; + createdAt: string; + createdBy: string; + updatedAt: string; + updatedBy: string; +} export interface AiWorkflow { id: string; name: string; + llmId: string; + description: string; + defUrl: string; + gitWorkflowId: string; + gitOwnerRepo: string; + scorecardId: string; + disabled: boolean; + createdAt: string; + createdBy: string; + updatedAt: string; + updatedBy: string; + llm: Llm; + scorecard: Scorecard; } export async function getAiWorkflows(): Promise { const response = await xhrGetAsync(`${EnvironmentConfig.API.V6}/workflows`) return response } + +export async function updateAiWorkflow( + id: string, + data: Partial>, +): Promise { + const response = await xhrPatchAsync>, AiWorkflow>( + `${EnvironmentConfig.API.V6}/workflows/${id}`, + data, + ) + return response +} diff --git a/src/apps/admin/src/platform/gamification-admin/src/game-lib/member-autocomplete/InputHandleAutocomplete.tsx b/src/apps/admin/src/platform/gamification-admin/src/game-lib/member-autocomplete/InputHandleAutocomplete.tsx index f0182f640..8ec042b09 100644 --- a/src/apps/admin/src/platform/gamification-admin/src/game-lib/member-autocomplete/InputHandleAutocomplete.tsx +++ b/src/apps/admin/src/platform/gamification-admin/src/game-lib/member-autocomplete/InputHandleAutocomplete.tsx @@ -81,7 +81,7 @@ const InputHandleAutocomplete: FC = (props: InputH hideInlineErrors={props.hideInlineErrors} type='text' > - className={styles.memberSelect} cacheOptions getOptionLabel={getUserProp('handle')} diff --git a/src/apps/engagements/src/components/assignment-card/AssignmentCard.tsx b/src/apps/engagements/src/components/assignment-card/AssignmentCard.tsx index 196c59286..bf04de309 100644 --- a/src/apps/engagements/src/components/assignment-card/AssignmentCard.tsx +++ b/src/apps/engagements/src/components/assignment-card/AssignmentCard.tsx @@ -15,7 +15,7 @@ import { import type { Engagement, EngagementAssignment } from '../../lib/models' import { formatCurrencyAmount, - formatStandardHoursPerWeek, + formatStandardHoursPerDay, } from '../../lib/utils/currency.utils' import { formatDate } from '../../lib/utils/date.utils' import { formatLocation } from '../../lib/utils/api.utils' @@ -152,10 +152,6 @@ const AssignmentCard: FC = (props: AssignmentCardProps) => () => normalizeStatusKey(assignment?.status), [assignment?.status], ) - const paymentLabel = useMemo( - () => formatCurrencyAmount(assignment?.agreementRate, FALLBACK_VALUE_LABEL), - [assignment?.agreementRate], - ) const ratePerHourLabel = useMemo( () => formatCurrencyAmount(assignment?.ratePerHour, FALLBACK_VALUE_LABEL), [assignment?.ratePerHour], @@ -168,9 +164,9 @@ const AssignmentCard: FC = (props: AssignmentCardProps) => () => formatDurationMonths(assignment?.durationMonths), [assignment?.durationMonths], ) - const standardHoursPerWeekLabel = useMemo( - () => formatStandardHoursPerWeek(assignment?.standardHoursPerWeek, FALLBACK_VALUE_LABEL), - [assignment?.standardHoursPerWeek], + const standardHoursPerDayLabel = useMemo( + () => formatStandardHoursPerDay(assignment?.standardHoursPerDay, FALLBACK_VALUE_LABEL), + [assignment?.standardHoursPerDay], ) const assignmentStatus = assignment?.status?.toLowerCase() const showAssignedActions = assignmentStatus === 'assigned' @@ -253,11 +249,10 @@ const AssignmentCard: FC = (props: AssignmentCardProps) =>
- {`Std hrs / week: ${standardHoursPerWeekLabel}`} + {`Std hrs / day: ${standardHoursPerDayLabel}`}
- - {`Rate / week: ${paymentLabel}`} + {`Payment cycle: ${assignment?.paymentCycle ?? 'TBD'}`}
diff --git a/src/apps/engagements/src/components/assignment-offer-modal/AssignmentOfferModal.tsx b/src/apps/engagements/src/components/assignment-offer-modal/AssignmentOfferModal.tsx index b1c943639..f3a61b5a7 100644 --- a/src/apps/engagements/src/components/assignment-offer-modal/AssignmentOfferModal.tsx +++ b/src/apps/engagements/src/components/assignment-offer-modal/AssignmentOfferModal.tsx @@ -5,7 +5,7 @@ import { BaseModal, Button } from '~/libs/ui' import type { Engagement, EngagementAssignment } from '../../lib/models' import { formatCurrencyAmount, - formatStandardHoursPerWeek, + formatStandardHoursPerDay, } from '../../lib/utils/currency.utils' import { formatDate } from '../../lib/utils/date.utils' @@ -63,9 +63,9 @@ const AssignmentOfferModal: FC = ( ? 'Review the details below before rejecting this offer.' : 'Review the details below before accepting this offer.' - const agreementRateLabel = useMemo( - () => formatCurrencyAmount(assignment.agreementRate, FALLBACK_LABEL), - [assignment.agreementRate], + const paymentCycleLabel = useMemo( + () => assignment.paymentCycle ?? FALLBACK_LABEL, + [assignment.paymentCycle], ) const startDateLabel = useMemo( () => formatAssignmentDate(assignment.startDate), @@ -79,9 +79,9 @@ const AssignmentOfferModal: FC = ( () => formatCurrencyAmount(assignment.ratePerHour, FALLBACK_LABEL), [assignment.ratePerHour], ) - const standardHoursPerWeekLabel = useMemo( - () => formatStandardHoursPerWeek(assignment.standardHoursPerWeek, FALLBACK_LABEL), - [assignment.standardHoursPerWeek], + const standardHoursPerDayLabel = useMemo( + () => formatStandardHoursPerDay(assignment.standardHoursPerDay, FALLBACK_LABEL), + [assignment.standardHoursPerDay], ) const otherRemarksLabel = useMemo( () => formatRemarks(assignment.otherRemarks), @@ -135,12 +135,12 @@ const AssignmentOfferModal: FC = ( {ratePerHourLabel}
- Standard hours per week - {standardHoursPerWeekLabel} + Standard hours per day + {standardHoursPerDayLabel}
- Assignment rate per week - {agreementRateLabel} + Payment Cycle + {paymentCycleLabel}
Other remarks diff --git a/src/apps/engagements/src/lib/models/Engagement.model.ts b/src/apps/engagements/src/lib/models/Engagement.model.ts index dc88a8103..de9f9de42 100644 --- a/src/apps/engagements/src/lib/models/Engagement.model.ts +++ b/src/apps/engagements/src/lib/models/Engagement.model.ts @@ -15,8 +15,10 @@ export interface EngagementAssignment { status?: string termsAccepted?: boolean agreementRate?: string + paymentCycle?: string ratePerHour?: string standardHoursPerWeek?: number + standardHoursPerDay?: number durationMonths?: number otherRemarks?: string startDate?: string diff --git a/src/apps/engagements/src/lib/services/engagements.service.ts b/src/apps/engagements/src/lib/services/engagements.service.ts index e86af1e35..93e024814 100644 --- a/src/apps/engagements/src/lib/services/engagements.service.ts +++ b/src/apps/engagements/src/lib/services/engagements.service.ts @@ -29,7 +29,9 @@ interface BackendEngagementAssignment { termsAccepted?: boolean | null agreementRate?: string | number | null ratePerHour?: string | number | null + standardHoursPerDay?: number | string | null standardHoursPerWeek?: number | string | null + paymentCycle?: string durationMonths?: number | string | null otherRemarks?: string | null startDate?: string | null @@ -212,6 +214,7 @@ const normalizeAssignments = (assignments?: BackendEngagementAssignment[]): Enga return assignments.map(assignment => { const ratePerHour = normalizeEnumValue(assignment.ratePerHour) const parsedRatePerHour = normalizePositiveNumericValue(assignment.ratePerHour) + const standardHoursPerDay = normalizePositiveNumericValue(assignment.standardHoursPerDay) const standardHoursPerWeek = normalizePositiveNumericValue(assignment.standardHoursPerWeek) const agreementRate = normalizeEnumValue(assignment.agreementRate) ?? ( @@ -230,7 +233,9 @@ const normalizeAssignments = (assignments?: BackendEngagementAssignment[]): Enga memberHandle: withDefault('', assignment.memberHandle), memberId: withDefault('', assignment.memberId), otherRemarks: normalizeEnumValue(assignment.otherRemarks), + paymentCycle: assignment.paymentCycle, ratePerHour, + standardHoursPerDay, standardHoursPerWeek, startDate: normalizeEnumValue(assignment.startDate), status: normalizeAssignmentStatus(assignment.status), diff --git a/src/apps/engagements/src/lib/utils/currency.utils.ts b/src/apps/engagements/src/lib/utils/currency.utils.ts index 145607818..194a48ea2 100644 --- a/src/apps/engagements/src/lib/utils/currency.utils.ts +++ b/src/apps/engagements/src/lib/utils/currency.utils.ts @@ -63,7 +63,7 @@ export const formatCurrencyAmount = ( * @param fallback Label shown when the value is absent or invalid. * @returns Human-readable weekly hours label. */ -export const formatStandardHoursPerWeek = ( +export const formatStandardHoursPerDay = ( value?: string | number | null, fallback = 'TBD', ): string => { diff --git a/src/apps/engagements/src/lib/utils/index.ts b/src/apps/engagements/src/lib/utils/index.ts index 2fc1ffd2b..cd6d693a7 100644 --- a/src/apps/engagements/src/lib/utils/index.ts +++ b/src/apps/engagements/src/lib/utils/index.ts @@ -7,7 +7,7 @@ export { formatLocation } from './api.utils' export { truncateText } from './application.utils' export { formatCurrencyAmount, - formatStandardHoursPerWeek, + formatStandardHoursPerDay, normalizePositiveNumericValue, } from './currency.utils' export { formatDate } from './date.utils' diff --git a/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryCard/ChallengeHistoryCard.tsx b/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryCard/ChallengeHistoryCard.tsx index 2ecf88826..c737a699a 100644 --- a/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryCard/ChallengeHistoryCard.tsx +++ b/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryCard/ChallengeHistoryCard.tsx @@ -14,6 +14,7 @@ interface ChallengeHistoryCardProps { } const ChallengeHistoryCard: FC = props => { + const challengeTitle = props.challenge.challengeName || `Challenge ${props.challenge.challengeId}` const rating = props.challenge.newRating ?? props.challenge.rating const placement = props.challenge.placement @@ -27,7 +28,7 @@ const ChallengeHistoryCard: FC = props => {
- {props.challenge.challengeName} + {challengeTitle}
diff --git a/src/apps/profiles/src/member-profile/tc-achievements/sub-track-view/SubTrackView.tsx b/src/apps/profiles/src/member-profile/tc-achievements/sub-track-view/SubTrackView.tsx index 74c0d835e..afda7afcc 100644 --- a/src/apps/profiles/src/member-profile/tc-achievements/sub-track-view/SubTrackView.tsx +++ b/src/apps/profiles/src/member-profile/tc-achievements/sub-track-view/SubTrackView.tsx @@ -34,21 +34,6 @@ const SubTrackView: FC = props => { ] }, [props.profile.handle, statsRoute, trackData?.name, trackData?.subTracks]) - const summaryTrackData = useMemo(() => { - const supportsHistoryBackedSummary = ['DATA_SCIENCE', 'DEVELOP'].includes(String(subTrackData?.parentTrack)) - - if (!supportsHistoryBackedSummary || trackHistory.length === 0) { - return subTrackData - } - - return { - ...subTrackData, - challenges: trackHistory.length, - submissions: trackHistory.length, - wins: trackHistory.filter(challenge => challenge.placement === 1).length, - } - }, [subTrackData, trackHistory]) - return (!trackData || isEmpty(subTrackData)) ? props.renderDefault() : (
= props => { title={subTrackLabelToHumanName(subTrackData.name)} backAction={backRoute} closeAction={statsRoute(props.profile.handle)} - trackData={summaryTrackData} + trackData={subTrackData} > {subTrackData.name === 'SRM' ? ( diff --git a/src/apps/reports/src/pages/bulk-member-lookup/BulkMemberLookupPage.module.scss b/src/apps/reports/src/pages/bulk-member-lookup/BulkMemberLookupPage.module.scss index 8bf6bb896..5de227b0e 100644 --- a/src/apps/reports/src/pages/bulk-member-lookup/BulkMemberLookupPage.module.scss +++ b/src/apps/reports/src/pages/bulk-member-lookup/BulkMemberLookupPage.module.scss @@ -6,7 +6,6 @@ .instructions { color: #565a5f; - max-width: 720px; } .uploadSection { diff --git a/src/apps/reports/src/pages/reports/ReportsPage.module.scss b/src/apps/reports/src/pages/reports/ReportsPage.module.scss index 4cfbe966b..4b6c4e110 100644 --- a/src/apps/reports/src/pages/reports/ReportsPage.module.scss +++ b/src/apps/reports/src/pages/reports/ReportsPage.module.scss @@ -11,13 +11,13 @@ } .billingDefaultWindowNote { - display: inline-block; - margin-top: 6px; - padding: 2px 8px; + margin: 8px 0 0; + padding: 8px 12px; border-radius: 4px; background: #fff4d6; color: #5f4a00; font-weight: 500; + max-width: 720px; } .selectors { diff --git a/src/apps/reports/src/pages/reports/ReportsPage.tsx b/src/apps/reports/src/pages/reports/ReportsPage.tsx index 2011aafb8..a31638598 100644 --- a/src/apps/reports/src/pages/reports/ReportsPage.tsx +++ b/src/apps/reports/src/pages/reports/ReportsPage.tsx @@ -52,7 +52,8 @@ const BILLING_ACCOUNTS_REPORT_PATH = '/sfdc/billing-accounts' const SFDC_PAYMENTS_REPORT_PATH = '/sfdc/payments' const BILLING_ACCOUNTS_REPORT_DEFINITION: ReportDefinition = { description: - 'View SFDC payments across all billing accounts by default. Optionally filter by billing account ID and dates.', + `View SFDC payments across all billing accounts by + default. Optionally filter by billing account ID, dates, or category.`, method: 'GET', name: 'Billing Accounts', parameters: [ @@ -621,6 +622,7 @@ const ReportActions = (props: ReportActionsProps): JSX.Element => { } type SelectedReportSectionProps = { + descriptionNote?: JSX.Element renderParameterInput: (parameter: ReportParameter) => JSX.Element reportActions: JSX.Element selectedReport?: ReportDefinition @@ -640,6 +642,7 @@ const SelectedReportSection = (props: SelectedReportSectionProps): JSX.Element = {props.selectedReport.description}
)} + {props.descriptionNote}
{formatMethod(props.selectedReport.method)} {' '} @@ -1104,22 +1107,12 @@ const ReportsPageContent: FC = props => { )}
{pageTitle} -

- {activeTab === 'reports' - ? 'Select a base path to view available reports. Choose a report, ' - + 'fill required parameters, and download JSON or CSV from the reports API.' - : ( - <> - {'Payments load for all billing accounts by default. Optionally narrow by billing ' - + 'account ID, dates, or category, then click View. Open a billing account profile ' - + 'from the Billing account ID column in the table. '} - - If no dates are specified, records from the past 45 days are displayed - by default. - - - )} -

+ {activeTab === 'reports' && ( +

+ Select a base path to view available reports. Choose a report, fill required + parameters, and download JSON or CSV from the reports API. +

+ )} {isLoading ? (
@@ -1168,6 +1161,12 @@ const ReportsPageContent: FC = props => { ) : ( + If no dates are specified, records from the past 45 days are + displayed by default. +

+ )} renderParameterInput={renderParameterInput} reportActions={billingAccountReportActions} selectedReport={BILLING_ACCOUNTS_REPORT_DEFINITION} diff --git a/src/apps/review/src/lib/components/AiReviewsTable/AiReviewsTable.module.scss b/src/apps/review/src/lib/components/AiReviewsTable/AiReviewsTable.module.scss index 8e48c1306..c375f2ea7 100644 --- a/src/apps/review/src/lib/components/AiReviewsTable/AiReviewsTable.module.scss +++ b/src/apps/review/src/lib/components/AiReviewsTable/AiReviewsTable.module.scss @@ -185,4 +185,35 @@ margin-bottom: $sp-1; } } +} + + +.notesBanner { + display: flex; + align-items: flex-start; + gap: 8px; + padding: 12px 16px; + background: #e8f4fd; + border: 1px solid #b3d9f7; + border-radius: 4px; + margin-bottom: 12px; + color: #1a6fa8; +} + +.notesTitle { + font-weight: 600; + margin-bottom: 4px; +} + +.notesMessage { + font-size: 13px; + line-height: 1.4; + margin-top: 4px; + white-space: pre-wrap; +} + +.escalationNotes { + margin-top: 8px; + padding-top: 8px; + border-top: 1px solid rgba(255,255,255,0.3); } \ No newline at end of file diff --git a/src/apps/review/src/lib/components/AiReviewsTable/AiReviewsTable.tsx b/src/apps/review/src/lib/components/AiReviewsTable/AiReviewsTable.tsx index 087c45651..2c249d7bd 100644 --- a/src/apps/review/src/lib/components/AiReviewsTable/AiReviewsTable.tsx +++ b/src/apps/review/src/lib/components/AiReviewsTable/AiReviewsTable.tsx @@ -29,6 +29,7 @@ import { AiReviewConfigWorkflow, AiReviewDecision, AiReviewDecisionBreakdownWorkflow, + AiReviewDecisionEscalation, BackendSubmission, ChallengeDetailContextModel, } from '../../models' @@ -49,7 +50,7 @@ interface AiReviewerRow { reviewDate?: string run?: Pick score?: number - status?: 'failed' | 'failed-score' | 'passed' | 'pending' + status?: 'failed' | 'failed-score' | 'passed' | 'pending' | 'cancelled' title: string weight?: number workflowId?: string @@ -63,11 +64,15 @@ function normalizeStatus( runStatus?: string | null, score?: number | null, minScore?: number, -): 'failed' | 'failed-score' | 'passed' | 'pending' { +): 'failed' | 'failed-score' | 'passed' | 'pending' | 'cancelled' { if (!runStatus) { return 'pending' } + if (runStatus === AiWorkflowRunStatusEnum.CANCELLED) { + return 'cancelled' + } + if (aiRunInProgress({ status: runStatus as AiWorkflowRunStatusEnum })) { return 'pending' } @@ -111,6 +116,42 @@ function getDecisionBySubmission( return decisions[submissionId] } +/** + * Builds a list of human-readable note strings from escalations. + * Used to display notes for Escalating, Approving/Rejecting, and Unlocking actions. + */ +function buildDecisionNotes( + escalations?: AiReviewDecisionEscalation[], + resourceMemberIdMapping?: Record, +): string[] { + const parts: string[] = [] + + const getMemberHandle = (memberId?: string | null): string => { + if (!memberId || !resourceMemberIdMapping) return '' + const resource = resourceMemberIdMapping[memberId] + return resource?.memberHandle || '' + } + + escalations?.forEach(esc => { + if (esc.escalationNotes) { + const by = esc.createdBy ? ` (by ${getMemberHandle(esc.createdBy)})` : '' + parts.push(`Escalation Note${by}: ${esc.escalationNotes}`) + } + + if (esc.approverNotes) { + const by = esc.updatedBy ? ` (by ${getMemberHandle(esc.updatedBy)})` : '' + const prefix = esc.status === 'APPROVED' + ? 'Approval Note' + : esc.status === 'REJECTED' + ? 'Rejection Note' + : 'Approver Note' + parts.push(`${prefix}${by}: ${esc.approverNotes}`) + } + }) + + return parts +} + // eslint-disable-next-line complexity const AiReviewsTable: FC = props => { const { runs, isLoading }: AiWorkflowRunsResponse = useFetchAiWorkflowsRuns(props.submission.id) @@ -280,7 +321,6 @@ const AiReviewsTable: FC = props => { : `This submission is failed because ${hasSubmitterRole ? 'your' : 'the'} Overall Score is below minimum threshold.` - // Message text varies by role const roleBasedText = hasSubmitterRole ? 'Improve your submission and resubmit.' : '' @@ -300,9 +340,64 @@ const AiReviewsTable: FC = props => { return 'Submission Locked - This submission won\'t be reviewed during the Review Phase.' }, [currentDecision?.submissionLocked, hasSubmitterRole]) + const resourceMemberIdMapping = challengeDetailContext.resourceMemberIdMapping + + /** + * Builds the list of notes from escalations (escalationNotes, approverNotes). + * These are shown to Copilot/Manager/Admin only (not to submitters) so they + * can see why a submission was escalated or approved/rejected. + */ + const decisionNotes = useMemo((): string[] => { + if (!currentDecision || hasSubmitterRole) return [] + + return buildDecisionNotes(currentDecision.escalations, resourceMemberIdMapping) + }, [currentDecision, hasSubmitterRole, resourceMemberIdMapping]) + + const hasDecisionNotes = decisionNotes.length > 0 + + /** + * Notes panel shown when: + * - submission is UNLOCKED (HUMAN_OVERRIDE, not locked) — shows unlock/escalation notes + * - submission is still LOCKED — appended inside the locked banner + */ + const notesPanel = ( + <> + {/* Unlocked submission: show notes banner */} + {currentDecision?.status === 'HUMAN_OVERRIDE' + && !currentDecision?.submissionLocked + && hasDecisionNotes && ( +
+ +
+
Submission Unlocked
+ {decisionNotes.map((note, i) => ( + // eslint-disable-next-line react/no-array-index-key +
{note}
+ ))} +
+
+ )} + + {/* Locked submission with escalation notes: append inside locked banner area */} + {currentDecision?.submissionLocked && hasDecisionNotes && ( +
+ +
+
Review Activity Notes
+ {decisionNotes.map((note, i) => ( + // eslint-disable-next-line react/no-array-index-key +
{note}
+ ))} +
+
+ )} + + ) + if (isTablet) { return (
+ {/* Locked banner */} {currentDecision?.submissionLocked && lockMessage && (
@@ -313,6 +408,9 @@ const AiReviewsTable: FC = props => {
)} + {/* Notes panel: escalation / approval / unlock notes for Copilot/Manager/Admin */} + {notesPanel} + {!reviewerRows.length && loading && (
Loading...
)} @@ -414,6 +512,7 @@ const AiReviewsTable: FC = props => { return (
+ {/* Locked banner */} {currentDecision?.submissionLocked && lockMessage && (
@@ -426,6 +525,9 @@ const AiReviewsTable: FC = props => {
)} + {/* Notes panel: escalation / approval / unlock notes for Copilot/Manager/Admin */} + {notesPanel} +
diff --git a/src/apps/review/src/lib/components/AiReviewsTable/AiWorkflowRunStatus.tsx b/src/apps/review/src/lib/components/AiReviewsTable/AiWorkflowRunStatus.tsx index 976d3079d..310d2b484 100644 --- a/src/apps/review/src/lib/components/AiReviewsTable/AiWorkflowRunStatus.tsx +++ b/src/apps/review/src/lib/components/AiReviewsTable/AiWorkflowRunStatus.tsx @@ -8,21 +8,24 @@ import StatusLabel from './StatusLabel' interface AiWorkflowRunStatusProps { run?: Pick - status?: 'passed' | 'pending' | 'failed-score' | 'failed' | 'human-override' + status?: 'passed' | 'pending' | 'failed-score' | 'failed' | 'cancelled' | 'human-override' score?: number hideLabel?: boolean showScore?: boolean action?: ReactNode } -const aiRunStatus = (run: Pick): string => { +const aiRunStatus = ( + run: Pick, +): 'pending' | 'failed' | 'cancelled' | 'passed' | 'failed-score' => { const isInProgress = aiRunInProgress(run) const isFailed = aiRunFailed(run) + const isCancelled = run.status === 'CANCELLED' const isPassing = ( run.status === 'SUCCESS' && run.score >= (run.workflow.scorecard?.minimumPassingScore ?? 0) ) - return isInProgress ? 'pending' : isFailed ? 'failed' : ( + return isInProgress ? 'pending' : isCancelled ? 'cancelled' : isFailed ? 'failed' : ( isPassing ? 'passed' : 'failed-score' ) } @@ -85,6 +88,16 @@ export const AiWorkflowRunStatus: FC = props => { action={props.action} /> )} + {displayStatus === 'cancelled' && ( + } + hideLabel={props.hideLabel} + status='failed' + label='Cancelled' + score={score} + action={props.action} + /> + )} {displayStatus === 'human-override' && ( } diff --git a/src/apps/review/src/lib/components/ChallengeDetailsContent/TabContentReview.tsx b/src/apps/review/src/lib/components/ChallengeDetailsContent/TabContentReview.tsx index 2d6e7c532..fd108a356 100644 --- a/src/apps/review/src/lib/components/ChallengeDetailsContent/TabContentReview.tsx +++ b/src/apps/review/src/lib/components/ChallengeDetailsContent/TabContentReview.tsx @@ -24,7 +24,7 @@ import { ReviewResult, SubmissionInfo, } from '../../models' -import { hasIsLatestFlag } from '../../utils' +import { hasIsLatestFlag, isMarathonMatchChallenge } from '../../utils' import { TableAppeals } from '../TableAppeals' import { TableAppealsForSubmitter } from '../TableAppealsForSubmitter' import { TableAppealsResponse } from '../TableAppealsResponse' @@ -86,7 +86,15 @@ const parseScoreValue = (value: unknown): number | undefined => { return undefined } -const resolveSubmissionReviewScore = (submission: SubmissionInfo): number | undefined => { +const resolveSubmissionReviewScore = ( + submission: SubmissionInfo, + preferAggregateScore: boolean, +): number | undefined => { + const aggregateScore = parseScoreValue(submission.aggregateScore) + if (preferAggregateScore && aggregateScore !== undefined) { + return aggregateScore + } + const reviewResultScores = Array.isArray(submission.reviews) ? submission.reviews .map(review => parseScoreValue(review?.score)) @@ -98,7 +106,6 @@ const resolveSubmissionReviewScore = (submission: SubmissionInfo): number | unde return total / reviewResultScores.length } - const aggregateScore = parseScoreValue(submission.aggregateScore) if (aggregateScore !== undefined) { return aggregateScore } @@ -122,10 +129,13 @@ type SubmissionScoreEntry = { submission: SubmissionInfo } -const sortSubmissionsByReviewScoreDesc = (rows: SubmissionInfo[]): SubmissionInfo[] => { +const sortSubmissionsByReviewScoreDesc = ( + rows: SubmissionInfo[], + preferAggregateScore: boolean, +): SubmissionInfo[] => { const entries: SubmissionScoreEntry[] = rows.map((submission, index) => ({ index, - score: resolveSubmissionReviewScore(submission), + score: resolveSubmissionReviewScore(submission, preferAggregateScore), submission, })) @@ -227,6 +237,10 @@ export const TabContentReview: FC = (props: Props) => { () => challengeInfo?.submissions ?? [], [challengeInfo?.submissions], ) + const useAggregateReviewScore = useMemo( + () => isMarathonMatchChallenge(challengeInfo), + [challengeInfo], + ) const myOwnedMemberIds = useMemo>( () => { const ids = new Set() @@ -736,15 +750,15 @@ export const TabContentReview: FC = (props: Props) => { ) const reviewerRowsForReviewTab = useMemo( () => (shouldSortReviewTabByScore - ? sortSubmissionsByReviewScoreDesc(filteredReviews) + ? sortSubmissionsByReviewScoreDesc(filteredReviews, useAggregateReviewScore) : filteredReviews), - [filteredReviews, shouldSortReviewTabByScore], + [filteredReviews, shouldSortReviewTabByScore, useAggregateReviewScore], ) const submitterRowsForReviewTab = useMemo( () => (shouldSortReviewTabByScore - ? sortSubmissionsByReviewScoreDesc(filteredSubmitterReviews) + ? sortSubmissionsByReviewScoreDesc(filteredSubmitterReviews, useAggregateReviewScore) : filteredSubmitterReviews), - [filteredSubmitterReviews, shouldSortReviewTabByScore], + [filteredSubmitterReviews, shouldSortReviewTabByScore, useAggregateReviewScore], ) const hideHandleColumn = props.isActiveChallenge && actionChallengeRole === REVIEWER diff --git a/src/apps/review/src/lib/components/ChallengeDetailsContent/TabContentSubmissions.spec.tsx b/src/apps/review/src/lib/components/ChallengeDetailsContent/TabContentSubmissions.spec.tsx new file mode 100644 index 000000000..481e63317 --- /dev/null +++ b/src/apps/review/src/lib/components/ChallengeDetailsContent/TabContentSubmissions.spec.tsx @@ -0,0 +1,299 @@ +/* eslint-disable import/no-extraneous-dependencies, ordered-imports/ordered-imports */ +import { act } from 'react' +import type { PropsWithChildren } from 'react' +import { + fireEvent, + render, + screen, + waitFor, +} from '@testing-library/react' + +import type { + BackendSubmission, + ChallengeDetailContextModel, + ChallengeInfo, + ReviewAppContextModel, +} from '../../models' +import { + ChallengeDetailContext, + ReviewAppContext, +} from '../../contexts' +import { reprocessTopgearSubmission } from '../../services' + +import { TabContentSubmissions } from './TabContentSubmissions' + +jest.mock('~/config', () => ({ + EnvironmentConfig: { + REVIEW: { + PROFILE_PAGE_URL: 'https://profiles.example.com', + }, + }, +}), { virtual: true }) + +jest.mock('~/libs/core', () => ({ + getRatingColor: jest.fn() + .mockReturnValue('#000000'), + UserRole: { + administrator: 'administrator', + projectManager: 'projectManager', + }, + xhrGetAsync: jest.fn(), +}), { virtual: true }) + +jest.mock('../../contexts', () => { + const React: typeof import('react') = jest.requireActual('react') + + return { + ChallengeDetailContext: React.createContext({}), + ReviewAppContext: React.createContext({}), + } +}) + +jest.mock('react-toastify', () => ({ + toast: { + error: jest.fn(), + success: jest.fn(), + }, +})) + +jest.mock('~/apps/admin/src/lib', () => ({ + TableLoading: () =>
Loading
, +}), { virtual: true }) + +jest.mock('~/apps/admin/src/lib/components/common/TableMobile', () => ({ + TableMobile: () =>
Mobile table
, +}), { virtual: true }) + +jest.mock('~/apps/admin/src/lib/utils', () => ({ + handleError: jest.fn(), +}), { virtual: true }) + +jest.mock('~/libs/shared', () => ({ + copyTextToClipboard: () => Promise.resolve(), + useWindowSize: () => ({ + height: 800, + width: 1200, + }), +}), { virtual: true }) + +jest.mock('~/libs/ui', () => ({ + IconOutline: { + DocumentDuplicateIcon: () => , + RefreshIcon: () => , + }, + Table: (props: { + columns: Array<{ + propertyName?: string + renderer?: (row: BackendSubmission, rows: BackendSubmission[]) => JSX.Element + }> + data: BackendSubmission[] + }) => ( +
+ + {props.data.map(row => ( + + {props.columns.map((column, index) => ( + + ))} + + ))} + +
+ {column.renderer + ? column.renderer(row, props.data) + : row[column.propertyName as keyof BackendSubmission] as string} +
+ ), + Tooltip: (props: PropsWithChildren) => <>{props.children}, +}), { virtual: true }) + +jest.mock('../../hooks', () => ({ + useRolePermissions: () => ({ + actionChallengeRole: undefined, + canManageCompletedReviews: true, + canViewAllSubmissions: true, + hasCopilotRole: false, + hasReviewerRole: false, + hasSubmitterRole: false, + isAdmin: true, + isCopilotWithReviewerAssignments: false, + isProjectManager: false, + ownedMemberIds: new Set(), + }), +})) + +jest.mock('../../hooks/useSubmissionDownloadAccess', () => ({ + useSubmissionDownloadAccess: () => ({ + currentMemberId: 'admin-user', + getRestrictionMessageForMember: () => undefined, + isSubmissionDownloadRestricted: false, + isSubmissionDownloadRestrictedForMember: () => false, + isSubmissionPhaseOpen: false, + restrictionMessage: '', + shouldRestrictSubmitterToOwnSubmission: false, + }), +})) + +jest.mock('../../services', () => ({ + canReprocessTopgearSubmission: () => true, + reprocessTopgearSubmission: jest.fn(), +})) + +jest.mock('../CollapsibleAiReviewsRow', () => ({ + CollapsibleAiReviewsRow: () =>
AI reviews
, +})) + +jest.mock('../ConfirmModal', () => ({ + ConfirmModal: (props: PropsWithChildren<{ + action?: string + cancelText?: string + onClose: () => void + onConfirm: () => void + open: boolean + title: string + }>) => ( + props.open ? ( +
+ {props.children} + + +
+ ) : undefined + ), +})) + +jest.mock('../SubmissionHistoryModal', () => ({ + SubmissionHistoryModal: () => undefined, +})) + +jest.mock('../TableNoRecord', () => ({ + TableNoRecord: (props: { message?: string }) =>
{props.message}
, +})) + +jest.mock('../TableWrapper', () => ({ + TableWrapper: (props: PropsWithChildren<{ className?: string }>) => ( +
{props.children}
+ ), +})) + +const mockedReprocessTopgearSubmission = reprocessTopgearSubmission as jest.Mock + +const submission = { + challengeId: 'challenge-1', + id: 'submission-1', + isFileSubmission: false, + memberId: 'member-1', + review: [], + reviewSummation: [], + submittedDate: '2026-05-01T00:00:00.000Z', + url: 'https://example.com/submission', +} as unknown as BackendSubmission + +const challengeInfo = { + id: 'challenge-1', + metadata: [], + phases: [], + status: 'Completed', + submissions: [], + track: { + id: 'track-1', + name: 'Development', + }, + type: { + id: 'type-1', + name: 'Topgear Task', + }, +} as unknown as ChallengeInfo + +const challengeDetailContextValue = { + aiReviewDecisionsBySubmissionId: {}, + challengeId: 'challenge-1', + challengeInfo, + challengeSubmissions: [submission], + hasChallengeScopedFetchError: false, + isLoadingAiReviewConfig: false, + isLoadingAiReviewDecisions: false, + isLoadingChallengeInfo: false, + isLoadingChallengeResources: false, + isLoadingChallengeSubmissions: false, + myResources: [], + myRoles: [], + registrants: [], + resourceMemberIdMapping: {}, + resources: [], + retryChallengeScopedFetches: jest.fn(), + reviewers: [], +} as ChallengeDetailContextModel + +const reviewAppContextValue = { + cancelLoadChallengeRelativeInfos: jest.fn(), + challengeRelativeInfosMapping: {}, + loadChallengeRelativeInfos: jest.fn(), + loginUserInfo: { + roles: ['administrator'], + userId: 123, + }, +} as ReviewAppContextModel + +function renderSubmissions(): ReturnType { + return render( + + + + + , + ) +} + +describe('TabContentSubmissions', () => { + beforeEach(() => { + jest.clearAllMocks() + mockedReprocessTopgearSubmission.mockResolvedValue('ok') + }) + + it('confirms before reprocessing a Topgear submission', async () => { + renderSubmissions() + + fireEvent.click(screen.getByRole('button', { name: 'Reprocess Topgear submission' })) + + expect(screen.getByRole('dialog', { name: 'Reprocess Topgear Submission' })) + .toBeTruthy() + expect(screen.getByText('Are you sure you want to reprocess this Topgear submission?')) + .toBeTruthy() + expect(mockedReprocessTopgearSubmission) + .not + .toHaveBeenCalled() + + fireEvent.click(screen.getByRole('button', { name: 'No' })) + + expect(screen.queryByRole('dialog', { name: 'Reprocess Topgear Submission' })) + .toBeNull() + expect(mockedReprocessTopgearSubmission) + .not + .toHaveBeenCalled() + + fireEvent.click(screen.getByRole('button', { name: 'Reprocess Topgear submission' })) + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: 'Yes' })) + }) + + await waitFor(() => { + expect(mockedReprocessTopgearSubmission) + .toHaveBeenCalledWith({ + submission, + submissionInfo: expect.objectContaining({ + id: 'submission-1', + }), + }) + }) + }) +}) diff --git a/src/apps/review/src/lib/components/ChallengeDetailsContent/TabContentSubmissions.tsx b/src/apps/review/src/lib/components/ChallengeDetailsContent/TabContentSubmissions.tsx index 2d8a4e668..536480f4e 100644 --- a/src/apps/review/src/lib/components/ChallengeDetailsContent/TabContentSubmissions.tsx +++ b/src/apps/review/src/lib/components/ChallengeDetailsContent/TabContentSubmissions.tsx @@ -51,6 +51,7 @@ import { canReprocessTopgearSubmission, reprocessTopgearSubmission, } from '../../services' +import { ConfirmModal } from '../ConfirmModal' import { canDownloadSubmissionFromSubmissionsTab } from './submissionDownloadPermissions' import styles from './TabContentSubmissions.module.scss' @@ -87,6 +88,10 @@ export const TabContentSubmissions: FC = props => { const { challengeInfo, registrants }: ChallengeDetailContextModel = useContext(ChallengeDetailContext) const [isReprocessingSubmission, setIsReprocessingSubmission] = useState({}) + const [ + pendingReprocessSubmissionId, + setPendingReprocessSubmissionId, + ] = useState(undefined) const canShowTopgearReprocess = useMemo( () => canReprocessTopgearSubmission(challengeInfo, isAdmin), @@ -233,11 +238,29 @@ export const TabContentSubmissions: FC = props => { return } + setPendingReprocessSubmissionId(submissionId) + }, + [], + ) + + const closeReprocessConfirmation = useCallback((): void => { + setPendingReprocessSubmissionId(undefined) + }, []) + + const handleConfirmReprocessSubmission = useCallback( + async (): Promise => { + const submissionId = pendingReprocessSubmissionId + if (!submissionId) { + closeReprocessConfirmation() + return + } + const submission = submissionMetaById.get(submissionId) if (!submission) { toast.error('Submission could not be found for reprocess', { toastId: `topgear-submission-reprocess-${submissionId}`, }) + closeReprocessConfirmation() return } @@ -254,6 +277,7 @@ export const TabContentSubmissions: FC = props => { toast.success('Reprocess submission request sent', { toastId: `topgear-submission-reprocess-${submissionId}`, }) + closeReprocessConfirmation() } catch (error) { handleError(error as Error) } finally { @@ -264,6 +288,8 @@ export const TabContentSubmissions: FC = props => { } }, [ + closeReprocessConfirmation, + pendingReprocessSubmissionId, submissionInfoById, submissionMetaById, ], @@ -601,6 +627,22 @@ export const TabContentSubmissions: FC = props => { getSubmissionMeta={resolveSubmissionMeta} aiReviewers={props.aiReviewers} /> + +
+ Are you sure you want to reprocess this Topgear submission? +
+
) } diff --git a/src/apps/review/src/lib/components/CollapsibleAiReviewsRow/CollapsibleAiReviewsRow.tsx b/src/apps/review/src/lib/components/CollapsibleAiReviewsRow/CollapsibleAiReviewsRow.tsx index 084022e09..5b5aa9011 100644 --- a/src/apps/review/src/lib/components/CollapsibleAiReviewsRow/CollapsibleAiReviewsRow.tsx +++ b/src/apps/review/src/lib/components/CollapsibleAiReviewsRow/CollapsibleAiReviewsRow.tsx @@ -7,6 +7,7 @@ import { IconOutline, Tooltip } from '~/libs/ui' import { AiReviewsTable, AiWorkflowRunStatus } from '../AiReviewsTable' import { AiReviewDecision, + AiReviewDecisionEscalation, AiReviewDecisionStatus, BackendSubmission, ChallengeDetailContextModel, @@ -14,6 +15,7 @@ import { import { ChallengeDetailContext } from '../../contexts' import { AiScoreFormulaTooltip } from '../AiScoreFormulaTooltip' import { formatScore } from '../AiScoreFormulaTooltip/AiScoreFormulaTooltip' +import { useRolePermissions, UseRolePermissionsResult } from '../../hooks' import styles from './CollapsibleAiReviewsRow.module.scss' @@ -50,12 +52,52 @@ export function normalizeDecisionStatus( return 'pending' } +/** + * Builds a multi-line tooltip string from escalation notes and approver notes. + * Returns undefined if there are no notes at all. + * Used to show Copilot/Manager/Admin why a submission was escalated or + * approved/rejected. + */ +function buildNotesTooltip( + escalations?: AiReviewDecisionEscalation[], + resourceMemberIdMapping?: Record, +): string | undefined { + const parts: string[] = [] + + const getMemberHandle = (memberId?: string | null): string => { + if (!memberId || !resourceMemberIdMapping) return '' + const resource = resourceMemberIdMapping[memberId] + return resource?.memberHandle || '' + } + + escalations?.forEach(esc => { + if (esc.escalationNotes) { + const by = esc.createdBy ? ` (by ${getMemberHandle(esc.createdBy)})` : '' + parts.push(`Escalation Note${by}: ${esc.escalationNotes}`) + } + + if (esc.approverNotes) { + const by = esc.updatedBy ? ` (by ${getMemberHandle(esc.updatedBy)})` : '' + const prefix = esc.status === 'APPROVED' + ? 'Approval Note' + : esc.status === 'REJECTED' + ? 'Rejection Note' + : 'Approver Note' + parts.push(`${prefix}${by}: ${esc.approverNotes}`) + } + }) + + return parts.length ? parts.join('\n') : undefined +} + const CollapsibleAiReviewsRow: FC = props => { const challengeDetailContext: ChallengeDetailContextModel = useContext(ChallengeDetailContext) const aiReviewConfig: ChallengeDetailContextModel['aiReviewConfig'] = challengeDetailContext.aiReviewConfig const aiReviewDecisionsBySubmissionId: ChallengeDetailContextModel['aiReviewDecisionsBySubmissionId'] = challengeDetailContext.aiReviewDecisionsBySubmissionId + const { hasSubmitterRole }: UseRolePermissionsResult = useRolePermissions() + const aiReviewersCount = useMemo(() => { const reviewersCount = props.aiReviewers.length || aiReviewConfig?.workflows?.length || 0 return reviewersCount + 1 @@ -71,6 +113,19 @@ const CollapsibleAiReviewsRow: FC = props => { [currentDecision?.status], ) + const resourceMemberIdMapping = challengeDetailContext.resourceMemberIdMapping + + /** + * Builds the tooltip text for the notes icon shown next to the status label. + * Only shown to Copilot/Manager/Admin (not submitters). + * Covers: escalation notes and approval/rejection notes. + */ + const notesTooltip = useMemo((): string | undefined => { + if (hasSubmitterRole || !currentDecision) return undefined + + return buildNotesTooltip(currentDecision.escalations, resourceMemberIdMapping) + }, [currentDecision, hasSubmitterRole, resourceMemberIdMapping]) + const [isOpen, setIsOpen] = useState(props.defaultOpen ?? false) const [portalContainer, setPortalContainer] = useState(undefined) const wrapperRef = useRef(null) @@ -146,6 +201,29 @@ const CollapsibleAiReviewsRow: FC = props => { {currentDecision && (
+ {/* + * Notes icon: shown only to Copilot/Manager/Admin (not submitters) + * when there are escalation notes, approval/rejection notes, + * or an unlock reason to display. + */} + {notesTooltip && ( + + {notesTooltip.split('\n') + .map((line, i) => ( + // eslint-disable-next-line react/no-array-index-key +
{line}
+ ))} +
+ )} + triggerOn='hover' + > + + + + + )} )} diff --git a/src/apps/review/src/lib/components/TableReview/TableReview.tsx b/src/apps/review/src/lib/components/TableReview/TableReview.tsx index 076201921..b9ea03825 100644 --- a/src/apps/review/src/lib/components/TableReview/TableReview.tsx +++ b/src/apps/review/src/lib/components/TableReview/TableReview.tsx @@ -42,6 +42,7 @@ import { import { aggregateSubmissionReviews, challengeHasSubmissionLimit, + isMarathonMatchChallenge, isReviewPhase, isReviewPhaseCurrentlyOpen, refreshChallengeReviewData, @@ -76,7 +77,12 @@ import type { SubmissionReviewerRow, SubmissionRow, } from '../common/types' -import { buildSubmissionReviewerRows, resolveSubmissionReviewResult } from '../common/reviewResult' +import { + buildSubmissionReviewerRows, + getSubmissionReviewerRowReview, + isSubmissionReviewerActionRow, + resolveSubmissionReviewResult, +} from '../common/reviewResult' import { shouldIncludeInReviewPhase } from '../../utils/reviewPhaseGuards' import { CollapsibleAiReviewsRow } from '../CollapsibleAiReviewsRow' @@ -193,6 +199,10 @@ export const TableReview: FC = (props: TableReviewProps) => { () => challengeHasSubmissionLimit(challengeInfo), [challengeInfo], ) + const useAggregateReviewScore = useMemo( + () => isMarathonMatchChallenge(challengeInfo), + [challengeInfo], + ) const submissionMetaById = useMemo>( () => createSubmissionMetaMap(filteredChallengeSubmissions, reviewPhaseDatas), @@ -470,8 +480,9 @@ export const TableReview: FC = (props: TableReviewProps) => { canDisplayScores: () => true, canViewScorecard: true, isAppealsTab: false, + useAggregateScore: useAggregateReviewScore, }), - [], + [useAggregateReviewScore], ) const { canViewAllSubmissions }: UseRolePermissionsResult = useRolePermissions() @@ -532,11 +543,10 @@ export const TableReview: FC = (props: TableReviewProps) => { const renderActionsCell = useCallback<(submission: SubmissionReviewerRow) => JSX.Element>(( submission: SubmissionReviewerRow, ) => { - const reviews = submission.aggregated?.reviews ?? [] - const myReviewDetail = reviews.find(review => { - const resourceId = review.reviewInfo?.resourceId ?? review.resourceId - return resourceId ? myReviewerResourceIds.has(resourceId) : false - }) + const rowReviewDetail = getSubmissionReviewerRowReview(submission) + const myReviewDetail = isSubmissionReviewerActionRow(submission, myReviewerResourceIds) + ? rowReviewDetail + : undefined const actionEntries: Array<{ element: JSX.Element; wrapperKey: string }> = [] const appendAction = (element: JSX.Element | undefined, fallbackKey: string): void => { @@ -820,10 +830,13 @@ export const TableReview: FC = (props: TableReviewProps) => { } appendAction(buildPrimaryAction(), 'primary') - appendAction(buildEscalateAction(), 'escalate') - appendAction(buildVerifyAction(), 'verify') - appendAction(buildUnlockAction(), 'unlock') - appendAction(buildHistoryAction(), 'history') + if (submission.isFirstReviewerRow) { + appendAction(buildEscalateAction(), 'escalate') + appendAction(buildVerifyAction(), 'verify') + appendAction(buildUnlockAction(), 'unlock') + appendAction(buildHistoryAction(), 'history') + } + appendAction(buildReopenAction(), 'reopen') if (!actionEntries.length) { @@ -967,6 +980,7 @@ export const TableReview: FC = (props: TableReviewProps) => { const result = resolveSubmissionReviewResult(row, { minimumPassingScoreByScorecardId, + preferAggregateScore: useAggregateReviewScore, }) if (result === 'PASS') { return ( @@ -995,13 +1009,7 @@ export const TableReview: FC = (props: TableReviewProps) => { className: styles.textBlue, columnId: 'actions', label: 'Actions', - renderer: (row: SubmissionReviewerRow) => ( - row.isFirstReviewerRow ? renderActionsCell(row) : ( - - -- - - ) - ), + renderer: (row: SubmissionReviewerRow) => renderActionsCell(row), type: 'element', }) } @@ -1050,6 +1058,7 @@ export const TableReview: FC = (props: TableReviewProps) => { openReopenDialog, challengeInfo, pendingReopen, + useAggregateReviewScore, ]) const columnsMobile = useMemo[][]>( diff --git a/src/apps/review/src/lib/components/TableReviewForSubmitter/TableReviewForSubmitter.tsx b/src/apps/review/src/lib/components/TableReviewForSubmitter/TableReviewForSubmitter.tsx index bc30121d3..bd6aad00d 100644 --- a/src/apps/review/src/lib/components/TableReviewForSubmitter/TableReviewForSubmitter.tsx +++ b/src/apps/review/src/lib/components/TableReviewForSubmitter/TableReviewForSubmitter.tsx @@ -44,6 +44,7 @@ import { getSubmissionHistoryKey, isAppealsPhase, isAppealsResponsePhase, + isMarathonMatchChallenge, } from '../../utils' import type { AggregatedSubmissionReviews } from '../../utils' import { @@ -173,6 +174,10 @@ export const TableReviewForSubmitter: FC = (props: () => challengeHasSubmissionLimit(challengeInfo), [challengeInfo], ) + const useAggregateReviewScore = useMemo( + () => isMarathonMatchChallenge(challengeInfo), + [challengeInfo], + ) const submissionMetaById = useMemo>(() => { const map = new Map() @@ -426,6 +431,7 @@ export const TableReviewForSubmitter: FC = (props: canDisplayScores, canViewScorecard: isChallengeCompleted || isOwnedSubmission, isAppealsTab: false, + useAggregateScore: useAggregateReviewScore, } return renderReviewScoreCell(submission, scoreConfig) @@ -444,6 +450,7 @@ export const TableReviewForSubmitter: FC = (props: const result = resolveSubmissionReviewResult(submission, { minimumPassingScoreByScorecardId, + preferAggregateScore: useAggregateReviewScore, }) if (result === 'PASS') { return ( @@ -489,6 +496,7 @@ export const TableReviewForSubmitter: FC = (props: canDisplayScores, canViewScorecard: isChallengeCompleted || isOwnedSubmission, isAppealsTab: false, + useAggregateScore: useAggregateReviewScore, } return renderScoreCell(submission, submission.reviewerIndex, scoreConfig) @@ -585,6 +593,7 @@ export const TableReviewForSubmitter: FC = (props: restrictToLatest, shouldShowHistoryActions, shouldRestrictSubmitterToOwnSubmission, + useAggregateReviewScore, ]) const columnsMobile = useMemo[][]>( diff --git a/src/apps/review/src/lib/components/common/TableColumnRenderers.spec.tsx b/src/apps/review/src/lib/components/common/TableColumnRenderers.spec.tsx new file mode 100644 index 000000000..53f2f9a38 --- /dev/null +++ b/src/apps/review/src/lib/components/common/TableColumnRenderers.spec.tsx @@ -0,0 +1,99 @@ +/* eslint-disable import/no-extraneous-dependencies, ordered-imports/ordered-imports */ +import { render, screen } from '@testing-library/react' + +import type { SubmissionReviewerRow } from './types' +import { + renderReviewScoreCell, + renderScoreCell, +} from './TableColumnRenderers' + +jest.mock('~/config', () => ({ + EnvironmentConfig: { + REVIEW: { + PROFILE_PAGE_URL: 'https://profiles.test', + }, + }, +}), { virtual: true }) + +jest.mock('~/libs/core', () => ({ + getRatingColor: () => '#2a2a2a', +}), { virtual: true }) + +jest.mock('~/libs/shared', () => ({ + copyTextToClipboard: jest.fn(), +}), { virtual: true }) + +jest.mock('~/libs/ui', () => { + const React = jest.requireActual('react') + + return { + IconOutline: { + DocumentDuplicateIcon: () => React.createElement('span'), + }, + Tooltip: (props: { children: React.ReactNode }) => ( + React.createElement(React.Fragment, undefined, props.children) + ), + } +}, { virtual: true }) + +const buildMarathonSubmission = (): SubmissionReviewerRow => { + const submission = { + aggregateScore: 88, + id: 'submission-1', + memberId: 'member-1', + } + + return { + ...submission, + aggregated: { + averageFinalScore: 0, + averageFinalScoreDisplay: '0.00', + id: submission.id, + reviews: [ + { + finalScore: 0, + reviewId: 'review-1', + status: 'COMPLETED', + }, + ], + submission, + }, + isFirstReviewerRow: true, + isLastReviewerRow: true, + reviewerIndex: 0, + } +} + +describe('TableColumnRenderers marathon score display', () => { + it('renders the aggregate score instead of the scorecard average for Review Score', () => { + render(renderReviewScoreCell(buildMarathonSubmission(), { + canDisplayScores: () => true, + canViewScorecard: true, + isAppealsTab: false, + useAggregateScore: true, + })) + + expect(screen.getByText('88.00')) + .toBeTruthy() + expect(screen.queryByText('0.00')) + .toBeNull() + }) + + it('renders aggregate Score as plain text without a scorecard link', () => { + const rendered: ReturnType = render(renderScoreCell( + buildMarathonSubmission(), + 0, + { + canDisplayScores: () => true, + canViewScorecard: true, + isAppealsTab: false, + useAggregateScore: true, + }, + )) + + expect(screen.getByText('88.00')) + .toBeTruthy() + expect(rendered.container.querySelector('a')) + .toBeNull() + }) +}) diff --git a/src/apps/review/src/lib/components/common/TableColumnRenderers.tsx b/src/apps/review/src/lib/components/common/TableColumnRenderers.tsx index aee6218ef..05863f444 100644 --- a/src/apps/review/src/lib/components/common/TableColumnRenderers.tsx +++ b/src/apps/review/src/lib/components/common/TableColumnRenderers.tsx @@ -223,6 +223,7 @@ export function renderReviewScoreCell( const { canDisplayScores, canViewScorecard, + useAggregateScore, viewOwnScorecardTooltip = VIEW_OWN_SCORECARD_TOOLTIP, }: ScoreVisibilityConfig = configWithDefaults @@ -234,6 +235,18 @@ export function renderReviewScoreCell( ) } + if (useAggregateScore) { + const aggregateScoreDisplay = formatScoreDisplay(submission.aggregateScore) + + return aggregateScoreDisplay ? ( + {aggregateScoreDisplay} + ) : ( + + -- + + ) + } + const aggregatedReviews = submission.aggregated?.reviews ?? [] const hasIncompleteReview = aggregatedReviews.some(review => { const statusRaw = review.reviewInfo?.status ?? review.status @@ -529,14 +542,13 @@ export function renderScoreCell( const { canDisplayScores, canViewScorecard, + useAggregateScore, viewOwnScorecardTooltip = VIEW_OWN_SCORECARD_TOOLTIP, getReviewUrl, canRespondToAppeals, }: ScoreVisibilityConfig = configWithDefaults - const reviewDetail = submission.aggregated?.reviews?.[reviewIndex] - - if (!reviewDetail) { + if (!canDisplayScores(submission)) { return ( -- @@ -544,10 +556,25 @@ export function renderScoreCell( ) } - const reviewInfo = reviewDetail.reviewInfo - const reviewId = reviewInfo?.id ?? reviewDetail.reviewId + if (useAggregateScore) { + const isFirstSubmissionRow = !('isFirstReviewerRow' in submission) + || submission.isFirstReviewerRow !== false + const aggregateScoreDisplay = isFirstSubmissionRow + ? formatScoreDisplay(submission.aggregateScore) + : undefined - if (!reviewId) { + return aggregateScoreDisplay ? ( + {aggregateScoreDisplay} + ) : ( + + -- + + ) + } + + const reviewDetail = submission.aggregated?.reviews?.[reviewIndex] + + if (!reviewDetail) { return ( -- @@ -555,7 +582,10 @@ export function renderScoreCell( ) } - if (!canDisplayScores(submission)) { + const reviewInfo = reviewDetail.reviewInfo + const reviewId = reviewInfo?.id ?? reviewDetail.reviewId + + if (!reviewId) { return ( -- diff --git a/src/apps/review/src/lib/components/common/reviewResult.spec.ts b/src/apps/review/src/lib/components/common/reviewResult.spec.ts index 2926b111c..4a417e4c1 100644 --- a/src/apps/review/src/lib/components/common/reviewResult.spec.ts +++ b/src/apps/review/src/lib/components/common/reviewResult.spec.ts @@ -1,7 +1,16 @@ import type { ReviewInfo } from '../../models/ReviewInfo.model' -import type { AggregatedReviewDetail, SubmissionRow } from './types' -import { resolveSubmissionReviewResult } from './reviewResult' +import type { + AggregatedReviewDetail, + AggregatedSubmissionReviews, + SubmissionRow, +} from './types' +import { + buildSubmissionReviewerRows, + getSubmissionReviewerRowReview, + isSubmissionReviewerActionRow, + resolveSubmissionReviewResult, +} from './reviewResult' const DEFAULT_PASSING_SCORE = 80 @@ -39,6 +48,34 @@ const buildReviewInfo = ( ...overrides, }) +const createSubmissionRow = (): SubmissionRow => { + const aggregated: AggregatedSubmissionReviews = { + id: 'submission-1', + reviews: [ + { + resourceId: 'other-reviewer-resource', + reviewerHandle: 'taasintake300', + reviewId: 'other-review', + }, + { + resourceId: 'copilot-reviewer-resource', + reviewerHandle: 'TCConnCopilot', + reviewId: 'copilot-review', + }, + ], + submission: { + id: 'submission-1', + memberId: 'submitter-1', + }, + } + + return { + aggregated, + id: 'submission-1', + memberId: 'submitter-1', + } +} + describe('resolveSubmissionReviewResult', () => { it('returns undefined when any reviewer is still pending in a multi-review setup', () => { const submission = buildSubmission([ @@ -164,4 +201,44 @@ describe('resolveSubmissionReviewResult', () => { expect(result) .toBe('PASS') }) + + it('prefers the summation aggregate score when requested', () => { + const submission = { + ...buildSubmission([ + { + finalScore: 0, + resourceId: 'res-1', + status: 'COMPLETED', + }, + ], 0), + aggregateScore: 88, + isPassingReview: true, + } + + const result = resolveSubmissionReviewResult(submission, { + defaultMinimumPassingScore: DEFAULT_PASSING_SCORE, + preferAggregateScore: true, + }) + + expect(result) + .toBe('PASS') + }) +}) + +describe('reviewResult row helpers', () => { + it('marks only the matching reviewer row as action-eligible when the current reviewer is not first', () => { + const rows = buildSubmissionReviewerRows([createSubmissionRow()]) + const currentReviewerResourceIds = new Set(['copilot-reviewer-resource']) + + expect(rows) + .toHaveLength(2) + expect(getSubmissionReviewerRowReview(rows[0])?.reviewId) + .toBe('other-review') + expect(getSubmissionReviewerRowReview(rows[1])?.reviewId) + .toBe('copilot-review') + expect(isSubmissionReviewerActionRow(rows[0], currentReviewerResourceIds)) + .toBe(false) + expect(isSubmissionReviewerActionRow(rows[1], currentReviewerResourceIds)) + .toBe(true) + }) }) diff --git a/src/apps/review/src/lib/components/common/reviewResult.ts b/src/apps/review/src/lib/components/common/reviewResult.ts index b39cde595..8d1f019e4 100644 --- a/src/apps/review/src/lib/components/common/reviewResult.ts +++ b/src/apps/review/src/lib/components/common/reviewResult.ts @@ -301,14 +301,46 @@ const extractMinimumPassingScoreFromMetadata = (metadata: unknown): number | und } /* eslint-enable complexity, no-continue */ -const resolveReviewScore = (submission: SubmissionRow): number | undefined => { +/** + * Resolves the review summation aggregate score from a submission row. + * + * @param submission - Submission row that may include a Review API summation aggregate score. + * @returns Finite aggregate score, or undefined when no score is available. + */ +const resolveAggregateScore = (submission: SubmissionRow): number | undefined => { + const aggregateScore = submission.aggregateScore + if (typeof aggregateScore === 'number' && Number.isFinite(aggregateScore)) { + return aggregateScore + } + + return undefined +} + +/** + * Resolves the score used for pass/fail decisions. + * + * @param submission - Submission row containing review and summation scores. + * @param preferAggregateScore - True when summation scores should take priority over scorecard scores. + * @returns Finite score for outcome evaluation, or undefined when no score is available. + */ +const resolveReviewScore = ( + submission: SubmissionRow, + preferAggregateScore: boolean, +): number | undefined => { + if (preferAggregateScore) { + const aggregateScore = resolveAggregateScore(submission) + if (aggregateScore !== undefined) { + return aggregateScore + } + } + const aggregatedAverage = submission.aggregated?.averageFinalScore if (typeof aggregatedAverage === 'number' && Number.isFinite(aggregatedAverage)) { return aggregatedAverage } - const aggregateScore = submission.aggregateScore - if (typeof aggregateScore === 'number' && Number.isFinite(aggregateScore)) { + const aggregateScore = resolveAggregateScore(submission) + if (aggregateScore !== undefined) { return aggregateScore } @@ -431,6 +463,7 @@ const resolveForcedOutcome = ( } export interface ResolveSubmissionReviewResultOptions { + preferAggregateScore?: boolean minimumPassingScoreByScorecardId?: Map defaultMinimumPassingScore?: number } @@ -440,6 +473,7 @@ export function resolveSubmissionReviewResult( options: ResolveSubmissionReviewResultOptions = {}, ): ReviewOutcome | undefined { const { + preferAggregateScore = false, minimumPassingScoreByScorecardId, defaultMinimumPassingScore, }: ResolveSubmissionReviewResultOptions = options @@ -461,7 +495,7 @@ export function resolveSubmissionReviewResult( defaultMinimumPassingScore, ) - const reviewScore = resolveReviewScore(submission) + const reviewScore = resolveReviewScore(submission, preferAggregateScore) const scoreOutcome = resolveScoreOutcome(reviewScore, minimumPassingScore) @@ -502,3 +536,34 @@ export function buildSubmissionReviewerRows( return rows } + +/** + * Resolves the aggregated review detail represented by a flattened reviewer row. + * + * @param submission - Flattened reviewer row from the submissions table. + * @returns The aggregated review detail at the row's reviewer index, or undefined when unavailable. + * @throws This helper does not throw. + */ +export function getSubmissionReviewerRowReview( + submission: SubmissionReviewerRow, +): AggregatedReviewDetail | undefined { + return submission.aggregated?.reviews?.[submission.reviewerIndex] +} + +/** + * Determines whether reviewer-owned actions should render on a flattened reviewer row. + * + * @param submission - Flattened reviewer row from the submissions table. + * @param reviewerResourceIds - Resource IDs assigned to the current reviewer. + * @returns True when the row's review belongs to one of the current reviewer's resources. + * @throws This helper does not throw. + */ +export function isSubmissionReviewerActionRow( + submission: SubmissionReviewerRow, + reviewerResourceIds: Set, +): boolean { + const reviewDetail = getSubmissionReviewerRowReview(submission) + const resourceId = reviewDetail?.reviewInfo?.resourceId ?? reviewDetail?.resourceId + + return resourceId ? reviewerResourceIds.has(resourceId) : false +} diff --git a/src/apps/review/src/lib/components/common/types.ts b/src/apps/review/src/lib/components/common/types.ts index 1fd272157..7ec65271f 100644 --- a/src/apps/review/src/lib/components/common/types.ts +++ b/src/apps/review/src/lib/components/common/types.ts @@ -84,6 +84,8 @@ export interface ScoreVisibilityConfig { canDisplayScores: (submission: SubmissionRow) => boolean /** Whether the viewer can navigate to the detailed scorecard for the submission. */ canViewScorecard: boolean + /** True when the table should render the submission aggregate score instead of scorecard links. */ + useAggregateScore?: boolean /** Tooltip message shown when scorecard access is restricted. */ viewOwnScorecardTooltip?: string /** True when the parent table is rendered within the appeals tab. */ diff --git a/src/apps/review/src/lib/utils/challenge.spec.ts b/src/apps/review/src/lib/utils/challenge.spec.ts index 64ee4197d..b6c48380a 100644 --- a/src/apps/review/src/lib/utils/challenge.spec.ts +++ b/src/apps/review/src/lib/utils/challenge.spec.ts @@ -6,6 +6,7 @@ import { findPhaseByTabLabel, hasPendingApprovalReview, isFirst2FinishChallenge, + isMarathonMatchChallenge, type PhaseLike, resolveFirst2FinishIterativeSubmissionIds, shouldAllowWinnersTabForPastChallenge, @@ -50,6 +51,29 @@ const createBackendPhase = ( }) describe('challenge phase tab helpers', () => { + it('recognizes Marathon Match challenges from type metadata', () => { + expect(isMarathonMatchChallenge({ + type: { + abbreviation: 'MM', + id: 'type-1', + name: 'Marathon Match', + }, + typeId: 'type-1', + })) + .toBe(true) + }) + + it('recognizes Marathon Match challenges from the canonical type id', () => { + expect(isMarathonMatchChallenge({ + type: { + id: 'type-1', + name: 'Code', + }, + typeId: '929bc408-9cf2-4b3e-ba71-adfbf693046c', + })) + .toBe(true) + }) + it('preserves the incoming phase order', () => { const phases: PhaseLike[] = [ createPhase('1', 'Registration', '2025-01-01T00:00:00Z'), diff --git a/src/apps/review/src/lib/utils/challenge.ts b/src/apps/review/src/lib/utils/challenge.ts index a4e45352f..df58c44b9 100644 --- a/src/apps/review/src/lib/utils/challenge.ts +++ b/src/apps/review/src/lib/utils/challenge.ts @@ -276,6 +276,10 @@ function normalizeChallengeKey(value?: string): string { .replace(/[^a-z0-9]/g, '') } +const MARATHON_MATCH_TYPE_IDS = new Set([ + '929bc408-9cf2-4b3e-ba71-adfbf693046c', +]) + /** * Check whether a challenge should follow First2Finish-specific UI rules. * @@ -291,6 +295,27 @@ export function isFirst2FinishChallenge( return typeName === 'first2finish' || trackName === 'first2finish' } +/** + * Check whether a challenge should follow Marathon Match-specific score display rules. + * + * @param challengeInfo - Challenge metadata containing type name, abbreviation, and type ID. + * @returns True when the type metadata identifies the challenge as Marathon Match. + */ +export function isMarathonMatchChallenge( + challengeInfo?: Pick, +): boolean { + const typeName = normalizeChallengeKey(challengeInfo?.type?.name) + const typeAbbreviation = normalizeChallengeKey(challengeInfo?.type?.abbreviation) + const typeId = (challengeInfo?.typeId ?? '') + .trim() + .toLowerCase() + + return typeName === 'marathonmatch' + || typeAbbreviation === 'mm' + || typeAbbreviation === 'marathonmatch' + || MARATHON_MATCH_TYPE_IDS.has(typeId) +} + function normalizeEntityId(value: unknown): string | undefined { if (value === undefined || value === null) { return undefined diff --git a/src/apps/wallet-admin/src/home/tabs/payments/Payments.module.scss b/src/apps/wallet-admin/src/home/tabs/payments/Payments.module.scss index 25d4e4412..be8082ccd 100644 --- a/src/apps/wallet-admin/src/home/tabs/payments/Payments.module.scss +++ b/src/apps/wallet-admin/src/home/tabs/payments/Payments.module.scss @@ -25,12 +25,22 @@ border-radius: 4px; margin-top: $sp-4; - .roleViewToggle { + .paymentListingNavRow { display: flex; - align-items: center; + align-items: flex-end; + justify-content: space-between; gap: $sp-4; flex-wrap: wrap; - margin-bottom: $sp-6; + margin-bottom: $sp-4; + border-bottom: 1px solid $black-20; + } + + .roleViewToggle { + display: flex; + align-items: center; + flex-shrink: 0; + margin-left: auto; + margin-bottom: $sp-1; } .roleViewButtons { @@ -62,6 +72,33 @@ color: white; } + .paymentListingTabs { + display: flex; + align-items: flex-end; + gap: $sp-6; + } + + .paymentListingTab { + border: none; + background: transparent; + padding: $sp-2 0; + margin-bottom: -1px; + cursor: pointer; + color: $black-60; + font-weight: 600; + border-bottom: 2px solid transparent; + + &:focus-visible { + outline: 2px solid $turq-160; + outline-offset: 2px; + } + } + + .paymentListingTabActive { + color: $turq-160; + border-bottom-color: $turq-160; + } + .centered { height: 200px; display: flex; diff --git a/src/apps/wallet-admin/src/home/tabs/payments/PaymentsListView.spec.tsx b/src/apps/wallet-admin/src/home/tabs/payments/PaymentsListView.spec.tsx index b73f60a1d..a048c0a6e 100644 --- a/src/apps/wallet-admin/src/home/tabs/payments/PaymentsListView.spec.tsx +++ b/src/apps/wallet-admin/src/home/tabs/payments/PaymentsListView.spec.tsx @@ -8,6 +8,8 @@ import { waitFor, } from '@testing-library/react' +import { toPaymentTypeFilterValues } from '~/libs/shared/lib/utils/payment-type-filter.utils' + import { editPayment, getMemberHandle, getPayments } from '../../../lib/services/wallet' import PaymentsListView from './PaymentsListView' @@ -209,6 +211,49 @@ const paymentsResponse = { ], } +const TOPCODER_TAB_CATEGORIES = [ + 'TASK_PAYMENT', + 'CONTEST_PAYMENT', + 'COPILOT_PAYMENT', + 'REVIEW_BOARD_PAYMENT', + 'ENGAGEMENT_PAYMENT', +] + +const TOPCODER_TAB_TYPE_FILTER_VALUES = toPaymentTypeFilterValues(TOPCODER_TAB_CATEGORIES) +const APPROVER_TYPE_FILTER_VALUES = ['Task', 'Engagement'] + +function getApproverDefaultDateRangeForTest(): { dateFrom: string, dateTo: string } { + const dateTo = new Date() + const dateFrom = new Date() + dateFrom.setMonth(dateFrom.getMonth() - 3) + + const format = (date: Date): string => { + const y = date.getFullYear() + const mo = String(date.getMonth() + 1) + .padStart(2, '0') + const day = String(date.getDate()) + .padStart(2, '0') + + return `${y}-${mo}-${day}` + } + + return { + dateFrom: format(dateFrom), + dateTo: format(dateTo), + } +} + +const ALL_STATUS_FILTER_VALUES = [ + 'OWED', + 'ON_HOLD_ADMIN', + 'ON_HOLD', + 'PAID', + 'CANCELLED', + 'PROCESSING', + 'FAILED', + 'RETURNED', +] + describe('PaymentsListView', () => { beforeEach(() => { mockFilterBar.mockClear() @@ -222,6 +267,8 @@ describe('PaymentsListView', () => { }) it('defaults the approver view to the On Hold (Admin) status filter and both allowed categories', async () => { + const approverDates = getApproverDefaultDateRangeForTest() + render( { expect(mockedGetPayments) .toHaveBeenLastCalledWith(10, 0, { categories: ['TASK_PAYMENT', 'ENGAGEMENT_PAYMENT'], - date: ['last30days'], + dateFrom: [approverDates.dateFrom], + dateTo: [approverDates.dateTo], status: ['ON_HOLD_ADMIN'], }) expect(mockFilterBar) .toHaveBeenCalled() expect(mockFilterBar.mock.calls.at(-1)?.[0].selectedValueOverrides) .toEqual(expect.objectContaining({ - status: 'ON_HOLD_ADMIN', + category: APPROVER_TYPE_FILTER_VALUES, + dateFrom: approverDates.dateFrom, + dateTo: approverDates.dateTo, + status: ['ON_HOLD_ADMIN'], })) }) @@ -260,10 +311,39 @@ describe('PaymentsListView', () => { expect(mockFilterBar.mock.calls.at(-1)?.[0].selectedValueOverrides) .toEqual(expect.objectContaining({ category: 'TAAS_PAYMENT', + status: ALL_STATUS_FILTER_VALUES, })) }) + it('reflects taas admin draft date filters in FilterBar overrides', async () => { + render( + , + ) + + await screen.findByText('Member earnings will appear here.') + + const filterBarProps = mockFilterBar.mock.calls.at(-1)?.[0] + + act(() => { + filterBarProps.onFilterChange('dateFrom', ['2026-04-30']) + filterBarProps.onFilterChange('dateTo', ['2026-05-25']) + }) + + await waitFor(() => { + expect(mockFilterBar.mock.calls.at(-1)?.[0].selectedValueOverrides) + .toEqual(expect.objectContaining({ + category: 'TAAS_PAYMENT', + dateFrom: '2026-04-30', + dateTo: '2026-05-25', + })) + }) + }) + it('applies the default approver status after switching from admin view', async () => { + const approverDates = getApproverDefaultDateRangeForTest() + render( { await screen.findByText('Member earnings will appear here.') expect(mockedGetPayments) - .toHaveBeenLastCalledWith(10, 0, {}) + .toHaveBeenLastCalledWith(10, 0, { + categories: TOPCODER_TAB_CATEGORIES, + }) fireEvent.click(screen.getByRole('button', { name: 'Approver View' })) @@ -282,13 +364,17 @@ describe('PaymentsListView', () => { expect(mockedGetPayments) .toHaveBeenLastCalledWith(10, 0, { categories: ['TASK_PAYMENT', 'ENGAGEMENT_PAYMENT'], - date: ['last30days'], + dateFrom: [approverDates.dateFrom], + dateTo: [approverDates.dateTo], status: ['ON_HOLD_ADMIN'], }) expect(mockFilterBar.mock.calls.at(-1)?.[0].selectedValueOverrides) .toEqual(expect.objectContaining({ - status: 'ON_HOLD_ADMIN', + category: APPROVER_TYPE_FILTER_VALUES, + dateFrom: approverDates.dateFrom, + dateTo: approverDates.dateTo, + status: ['ON_HOLD_ADMIN'], })) }) @@ -302,16 +388,19 @@ describe('PaymentsListView', () => { await screen.findByText('Member earnings will appear here.') expect(mockedGetPayments) - .toHaveBeenLastCalledWith(10, 0, {}) - expect(mockFilterBar.mock.calls.at(-1)?.[0].selectedValueOverrides) - .toEqual({ - category: 'all', - date: 'all', - status: 'all', + .toHaveBeenLastCalledWith(10, 0, { + categories: TOPCODER_TAB_CATEGORIES, }) + expect(mockFilterBar.mock.calls.at(-1)?.[0].selectedValueOverrides) + .toEqual(expect.objectContaining({ + category: TOPCODER_TAB_TYPE_FILTER_VALUES, + status: ALL_STATUS_FILTER_VALUES, + })) }) it('lets an explicit status filter override the default approver status', async () => { + const approverDates = getApproverDefaultDateRangeForTest() + render( { await screen.findByText('No payments match your filters.') await act(async () => { - mockFilterBar.mock.calls.at(-1)?.[0].onFilterChange('status', ['PAID']) + const filterBarProps = mockFilterBar.mock.calls.at(-1)?.[0] + filterBarProps.onFilterChange('status', ['PAID']) + filterBarProps.onApplyFilters() }) await waitFor(() => { expect(mockedGetPayments) .toHaveBeenLastCalledWith(10, 0, { categories: ['TASK_PAYMENT', 'ENGAGEMENT_PAYMENT'], - date: ['last30days'], + dateFrom: [approverDates.dateFrom], + dateTo: [approverDates.dateTo], status: ['PAID'], }) }) expect(mockFilterBar.mock.calls.at(-1)?.[0].selectedValueOverrides) .toEqual(expect.objectContaining({ - status: 'PAID', + category: APPROVER_TYPE_FILTER_VALUES, + dateFrom: approverDates.dateFrom, + dateTo: approverDates.dateTo, + status: ['PAID'], })) }) @@ -349,7 +444,9 @@ describe('PaymentsListView', () => { await screen.findByText('Member earnings will appear here.') await act(async () => { - mockFilterBar.mock.calls.at(-1)?.[0].onFilterChange('status', ['PAID']) + const filterBarProps = mockFilterBar.mock.calls.at(-1)?.[0] + filterBarProps.onFilterChange('status', ['PAID']) + filterBarProps.onApplyFilters() }) await waitFor(() => { @@ -363,7 +460,7 @@ describe('PaymentsListView', () => { expect(mockFilterBar.mock.calls.at(-1)?.[0].selectedValueOverrides) .toEqual(expect.objectContaining({ category: 'TAAS_PAYMENT', - status: 'PAID', + status: ['PAID'], })) }) @@ -431,7 +528,7 @@ describe('PaymentsListView', () => { }) }) - it('includes the topgear payment type in the category filter options', async () => { + it('scopes the type filter to topcoder categories and lists Topgear winnings on its own tab', async () => { render( { const filterProps = mockFilterBar.mock.calls.at(-1)?.[0] const typeFilter = filterProps.filters.find((filter: any) => filter.key === 'category') - expect(typeFilter.options.some((option: any) => ( - option.value === 'TOPGEAR_PAYMENT' && option.label === 'Topgear Payment' - ))) + expect(typeFilter.options.some((option: any) => option.value === 'TOPGEAR_PAYMENT')) + .toBe(false) + expect(typeFilter.options.some((option: any) => option.value === 'Task')) .toBe(true) + expect(typeFilter.options.find((option: any) => option.value === 'Task')?.label) + .toBe('Task Payment') + + expect(screen.getByRole('tab', { name: 'Topgear' })) + .toBeTruthy() + + fireEvent.click(screen.getByRole('tab', { name: 'Topgear' })) + + await waitFor(() => { + expect(mockedGetPayments) + .toHaveBeenLastCalledWith(10, 0, { + category: ['TOPGEAR_PAYMENT'], + }) + }) }) }) diff --git a/src/apps/wallet-admin/src/home/tabs/payments/PaymentsListView.tsx b/src/apps/wallet-admin/src/home/tabs/payments/PaymentsListView.tsx index ece7f1f47..e36868a6f 100644 --- a/src/apps/wallet-admin/src/home/tabs/payments/PaymentsListView.tsx +++ b/src/apps/wallet-admin/src/home/tabs/payments/PaymentsListView.tsx @@ -7,6 +7,10 @@ import React, { FC, useCallback, useEffect } from 'react' import { Collapsible, ConfirmModal, InputText, LoadingCircles } from '~/libs/ui' import { UserProfile } from '~/libs/core' import { downloadBlob } from '~/libs/shared' +import { + toPaymentTypeCategories, + toPaymentTypeFilterValues, +} from '~/libs/shared/lib/utils/payment-type-filter.utils' import { editPayment, exportSearchResults, getMemberHandle, getPayments } from '../../../lib/services/wallet' import { Winning, WinningDetail, WinningsType } from '../../../lib/models/WinningDetail' @@ -29,7 +33,99 @@ const approverAllowedCategories = [taskPaymentCategory, engagementPaymentCategor const taasPaymentCategory = 'TAAS_PAYMENT' const topgearPaymentCategory = 'TOPGEAR_PAYMENT' const defaultPageSize = 10 -const approverDefaultDateFilter = 'last30days' +function formatIsoDateOnly(date: Date): string { + const y = date.getFullYear() + const mo = String(date.getMonth() + 1) + .padStart(2, '0') + const day = String(date.getDate()) + .padStart(2, '0') + + return `${y}-${mo}-${day}` +} + +function getApproverDefaultDateRange(): { dateFrom: string, dateTo: string } { + const dateTo = new Date() + const dateFrom = new Date() + dateFrom.setMonth(dateFrom.getMonth() - 3) + + return { + dateFrom: formatIsoDateOnly(dateFrom), + dateTo: formatIsoDateOnly(dateTo), + } +} + +type PaymentListingTab = 'topcoder' | 'topgear' | 'taas' + +const TOPCODER_PAYMENT_CATEGORIES: ReadonlyArray = [ + 'TASK_PAYMENT', + 'CONTEST_PAYMENT', + 'COPILOT_PAYMENT', + 'REVIEW_BOARD_PAYMENT', + 'ENGAGEMENT_PAYMENT', +] + +const STATUS_FILTER_OPTIONS: { label: string, value: string }[] = [ + { label: 'Owed', value: 'OWED' }, + { label: 'On Hold (Admin)', value: 'ON_HOLD_ADMIN' }, + { label: 'On Hold (Member)', value: 'ON_HOLD' }, + { label: 'Paid', value: 'PAID' }, + { label: 'Cancelled', value: 'CANCELLED' }, + { label: 'Processing', value: 'PROCESSING' }, + { label: 'Failed', value: 'FAILED' }, + { label: 'Returned', value: 'RETURNED' }, +] + +const ALL_STATUS_FILTER_VALUES: ReadonlyArray = STATUS_FILTER_OPTIONS.map(option => option.value) + +const TOPCODER_TYPE_FILTER_OPTIONS: { label: string, value: string }[] = [ + { label: 'Task Payment', value: 'Task' }, + { label: 'Contest Payment', value: 'Contest' }, + { label: 'Copilot Payment', value: 'Copilot' }, + { label: 'Review Board Payment', value: 'Review Board' }, + { label: 'Engagement Payment', value: 'Engagement' }, +] + +const APPROVER_TYPE_FILTER_OPTIONS: { label: string, value: string }[] = [ + { label: 'Task Payments', value: 'Task' }, + { label: 'Engagement Payments', value: 'Engagement' }, +] + +function normalizeFilterRecord(filters: Record): Record { + return Object.fromEntries( + Object.entries(filters) + .map(([key, values]) => [key, [...values].sort()]) + .filter(([, values]) => values.length > 0), + ) +} + +function areFilterRecordsEqual( + left: Record, + right: Record, +): boolean { + const normalizedLeft = normalizeFilterRecord(left) + const normalizedRight = normalizeFilterRecord(right) + const keys = Array.from(new Set([ + ...Object.keys(normalizedLeft), + ...Object.keys(normalizedRight), + ])) + + return keys.every(key => { + const leftValues = normalizedLeft[key] ?? [] + const rightValues = normalizedRight[key] ?? [] + if (leftValues.length !== rightValues.length) { + return false + } + + return leftValues.every((value, index) => value === rightValues[index]) + }) +} + +function withoutFilterKey(filters: Record, key: string): Record { + const nextFilters = { ...filters } + delete nextFilters[key] + + return nextFilters +} interface PaymentsListViewProps { profile: UserProfile @@ -212,21 +308,30 @@ const PaymentsListView: FC = (props: PaymentsListViewProp const restrictedCategory = isWiproTaasAdmin && !hasPaymentAdminRole ? taasPaymentCategory : undefined const restrictedDefaultStatus = isApproverView ? restrictedRoleDefaultStatus : undefined const isRestrictedApproverView = isApproverView - const [filters, setFilters] = React.useState>({}) + const [draftFilters, setDraftFilters] = React.useState>({}) + const [submittedFilters, setSubmittedFilters] = React.useState>({}) + const [paymentListingTab, setPaymentListingTab] = React.useState('topcoder') + + const showPaymentListingTabs = !isApproverView + && !(restrictedCategory && !hasPaymentAdminRole) + + React.useEffect(() => { + if (restrictedCategory && !hasPaymentAdminRole) { + setPaymentListingTab('taas') + } + }, [restrictedCategory, hasPaymentAdminRole]) // eslint-disable-next-line complexity const appliedFilters = React.useMemo>(() => { - // Strip 'all' sentinel values — never forward them to the API const activeFilters = Object.fromEntries( - Object.entries(filters) - .filter(([, v]) => v.length > 0 && v[0] !== 'all'), + Object.entries(submittedFilters) + .filter(([, v]) => v.length > 0 && !(v.length === 1 && v[0] === 'all')), ) if (restrictedCategory) { - // WiproTaasAdmin scoped to a single category let statusFilter: Record = {} - if (filters.status && filters.status[0] !== 'all') { - statusFilter = { status: activeFilters.status } + if (submittedFilters.status?.length) { + statusFilter = { status: submittedFilters.status } } return { @@ -237,85 +342,220 @@ const PaymentsListView: FC = (props: PaymentsListViewProp } if (isApproverView) { - // Payment Approver: restrict to allowed categories, default status ON_HOLD_ADMIN let statusFilter: Record = {} - if (filters.status && filters.status[0] !== 'all') { - statusFilter = { status: activeFilters.status } - } else if (!filters.status && restrictedDefaultStatus) { + if (submittedFilters.status?.length) { + statusFilter = { status: submittedFilters.status } + } else if (restrictedDefaultStatus) { statusFilter = { status: [restrictedDefaultStatus] } } - let dateFilter: Record = {} - if (filters.date && filters.date[0] !== 'all') { - dateFilter = { date: activeFilters.date } - } else if (!filters.date) { - dateFilter = { date: [approverDefaultDateFilter] } - } + const approverDefaultDates = getApproverDefaultDateRange() + const dateFrom = submittedFilters.dateFrom?.[0] ?? approverDefaultDates.dateFrom + const dateTo = submittedFilters.dateTo?.[0] ?? approverDefaultDates.dateTo - let categoryFilter: Record = {} - if ( - activeFilters.category - && approverAllowedCategories.includes(activeFilters.category[0]) - ) { - categoryFilter = { category: activeFilters.category } - } else if (!filters.category || filters.category[0] === 'all') { - categoryFilter = { categories: ([] as string[]).concat(approverAllowedCategories) } - } + const pickedApproverTypes = toPaymentTypeCategories(submittedFilters.category ?? []) + .filter(c => approverAllowedCategories.includes(c)) + const categories = pickedApproverTypes.length > 0 + ? pickedApproverTypes + : [...approverAllowedCategories] const rest = { ...activeFilters } delete rest.category + delete rest.date + delete rest.status + delete rest.dateFrom + delete rest.dateTo return { ...rest, - ...categoryFilter, + categories, ...statusFilter, - ...dateFilter, + dateFrom: [dateFrom], + dateTo: [dateTo], } } - return activeFilters - }, [filters, restrictedCategory, restrictedDefaultStatus, isApproverView]) + const base = { ...activeFilters } + delete base.category - const hasActiveFilters = React.useMemo( - () => Object.entries(appliedFilters) - .some(([key, value]) => key !== 'category' && key !== 'categories' && value.length > 0), - [appliedFilters], - ) - const selectedValueOverrides = React.useMemo>(() => { - if (restrictedCategory) { - const statusOverride = filters.status?.[0] !== 'all' ? filters.status?.[0] : undefined + const allStatusesCount = STATUS_FILTER_OPTIONS.length + const statusSelection = submittedFilters.status ?? [] + const statusIsFullSelection = statusSelection.length === 0 + || statusSelection.length === allStatusesCount + + if (statusIsFullSelection) { + delete base.status + } + + if (paymentListingTab === 'topgear') { + return { + ...base, + category: [topgearPaymentCategory], + } + } + if (paymentListingTab === 'taas') { return { + ...base, + category: [taasPaymentCategory], + } + } + + const pickedTopcoderTypes = toPaymentTypeCategories(submittedFilters.category ?? []) + .filter(c => TOPCODER_PAYMENT_CATEGORIES.includes(c)) + const categories = pickedTopcoderTypes.length > 0 + ? pickedTopcoderTypes + : [...TOPCODER_PAYMENT_CATEGORIES] + + return { + ...base, + categories, + } + }, [ + submittedFilters, + restrictedCategory, + restrictedDefaultStatus, + isApproverView, + paymentListingTab, + ]) + + const hasPendingFilterChanges = React.useMemo( + () => !areFilterRecordsEqual(draftFilters, submittedFilters), + [draftFilters, submittedFilters], + ) + + const hasActiveFilters = React.useMemo(() => { + const approverDefaultDates = isApproverView ? getApproverDefaultDateRange() : undefined + + return Object.entries(appliedFilters) + .some(([key, value]) => { + if (key === 'category' || key === 'categories') { + return false + } + + if (approverDefaultDates && key === 'dateFrom' && value[0] === approverDefaultDates.dateFrom) { + return false + } + + if (approverDefaultDates && key === 'dateTo' && value[0] === approverDefaultDates.dateTo) { + return false + } + + return value.length > 0 + }) + }, [appliedFilters, isApproverView]) + // eslint-disable-next-line complexity + const selectedValueOverrides = React.useMemo>(() => { + if (restrictedCategory) { + const overrides: Record = { category: restrictedCategory, - ...(statusOverride ? { status: statusOverride } : {}), + status: draftFilters.status !== undefined + ? draftFilters.status + : [...ALL_STATUS_FILTER_VALUES], + } + + if (draftFilters.dateFrom !== undefined) { + overrides.dateFrom = draftFilters.dateFrom[0] ?? '' } + + if (draftFilters.dateTo !== undefined) { + overrides.dateTo = draftFilters.dateTo[0] ?? '' + } + + return overrides } if (isApproverView) { - const statusOverride = filters.status?.[0] !== 'all' ? filters.status?.[0] : undefined + const approverDefaultDates = getApproverDefaultDateRange() return { - ...(statusOverride ? { status: statusOverride } : {}), + ...(draftFilters.status !== undefined + ? { status: draftFilters.status } + : { status: [restrictedDefaultStatus ?? 'ON_HOLD_ADMIN'] }), + ...(draftFilters.category !== undefined + ? { category: toPaymentTypeFilterValues(draftFilters.category) } + : { category: toPaymentTypeFilterValues([...approverAllowedCategories]) }), + dateFrom: draftFilters.dateFrom !== undefined + ? draftFilters.dateFrom[0] + : approverDefaultDates.dateFrom, + dateTo: draftFilters.dateTo !== undefined + ? draftFilters.dateTo[0] + : approverDefaultDates.dateTo, } } - return {} as Record - }, [filters.status, restrictedCategory, isApproverView]) + const overrides: Record = {} + + if (draftFilters.status !== undefined) { + overrides.status = draftFilters.status + } + + if (draftFilters.category !== undefined) { + overrides.category = toPaymentTypeFilterValues(draftFilters.category) + } - const defaultDropdownValues = React.useMemo>(() => { - const defaults: Record = {} + if (draftFilters.dateFrom?.[0]) { + overrides.dateFrom = draftFilters.dateFrom[0] + } - if (!restrictedCategory) { - defaults.category = filters.category?.[0] ?? 'all' + if (draftFilters.dateTo?.[0]) { + overrides.dateTo = draftFilters.dateTo[0] } - defaults.date = filters.date?.[0] ?? (isApproverView ? approverDefaultDateFilter : 'all') + return overrides + }, [ + draftFilters.status, + draftFilters.category, + draftFilters.dateFrom, + draftFilters.dateTo, + restrictedCategory, + isApproverView, + restrictedDefaultStatus, + ]) + + const defaultDropdownValues = React.useMemo((): Record => { + if (restrictedCategory) { + return {} + } - // Fall back to the restricted default if no filter is applied - defaults.status = filters.status?.[0] ?? (restrictedDefaultStatus || 'all') + if (isApproverView) { + const approverDefaultDates = getApproverDefaultDateRange() + + const approverDefaults: Record = { + category: draftFilters.category !== undefined + ? toPaymentTypeFilterValues(draftFilters.category) + : toPaymentTypeFilterValues([...approverAllowedCategories]), + dateFrom: draftFilters.dateFrom !== undefined + ? draftFilters.dateFrom[0] + : approverDefaultDates.dateFrom, + dateTo: draftFilters.dateTo !== undefined + ? draftFilters.dateTo[0] + : approverDefaultDates.dateTo, + status: draftFilters.status !== undefined + ? draftFilters.status + : [restrictedDefaultStatus ?? 'ON_HOLD_ADMIN'], + } + return approverDefaults + } - return defaults - }, [filters.category, filters.date, filters.status, restrictedCategory, restrictedDefaultStatus, isApproverView]) + const topcoderDefaults: Record = { + category: draftFilters.category !== undefined + ? toPaymentTypeFilterValues(draftFilters.category) + : toPaymentTypeFilterValues([...TOPCODER_PAYMENT_CATEGORIES]), + status: draftFilters.status !== undefined + ? draftFilters.status + : [...ALL_STATUS_FILTER_VALUES], + } + return topcoderDefaults + }, [ + draftFilters.category, + draftFilters.dateFrom, + draftFilters.dateTo, + draftFilters.status, + restrictedCategory, + restrictedDefaultStatus, + isApproverView, + ]) const [pagination, setPagination] = React.useState({ currentPage: 1, pageSize: defaultPageSize, @@ -502,6 +742,7 @@ const PaymentsListView: FC = (props: PaymentsListViewProp } setPaymentRoleView(nextView) + setPaymentListingTab('topcoder') setBulkAuditNote('') setSelectedPaymentAction(undefined) setSelectedPayments({}) @@ -509,14 +750,29 @@ const PaymentsListView: FC = (props: PaymentsListViewProp ...prev, currentPage: 1, })) - setFilters(prev => { - const nextFilters = { ...prev } - delete nextFilters.category - - return nextFilters - }) + setDraftFilters(prev => withoutFilterKey( + withoutFilterKey(withoutFilterKey(withoutFilterKey(prev, 'category'), 'date'), 'dateFrom'), + 'dateTo', + )) + setSubmittedFilters(prev => withoutFilterKey( + withoutFilterKey(withoutFilterKey(withoutFilterKey(prev, 'category'), 'date'), 'dateFrom'), + 'dateTo', + )) }, [paymentRoleView]) + const onPaymentListingTabChange = useCallback((tab: PaymentListingTab) => { + setPaymentListingTab(tab) + setBulkAuditNote('') + setSelectedPaymentAction(undefined) + setSelectedPayments({}) + setPagination(prev => ({ + ...prev, + currentPage: 1, + })) + setDraftFilters(prev => withoutFilterKey(prev, 'category')) + setSubmittedFilters(prev => withoutFilterKey(prev, 'category')) + }, []) + /** * Applies the selected approver action to the current payment selection. * @@ -578,6 +834,111 @@ const PaymentsListView: FC = (props: PaymentsListViewProp await fetchWinnings() }, [selectedPayments, fetchWinnings]) + const listingFilters = React.useMemo((): Filter[] => { + const pageSizeFilter: Filter = { + key: 'pageSize', + label: 'Payments per page', + options: [ + { label: '10', value: '10' }, + { label: '50', value: '50' }, + { label: '100', value: '100' }, + ], + type: 'dropdown', + } + + const handleFilter: Filter = { + key: 'winnerIds', + label: 'Handle/Username', + type: 'member_autocomplete', + } + + const statusFilter: Filter = { + key: 'status', + label: 'Status', + options: STATUS_FILTER_OPTIONS, + type: 'multi_dropdown', + } + + if (isWiproTaasAdmin && !hasPaymentAdminRole) { + return [ + handleFilter, + statusFilter, + { + key: 'dateFrom', + label: 'Date from', + type: 'date', + }, + { + key: 'dateTo', + label: 'Date to', + type: 'date', + }, + pageSizeFilter, + ] + } + + if (isApproverView) { + return [ + handleFilter, + statusFilter, + { + displayValueInTrigger: true, + key: 'category', + label: 'Payment Type', + options: APPROVER_TYPE_FILTER_OPTIONS, + type: 'multi_dropdown', + }, + { + key: 'dateFrom', + label: 'Date from', + type: 'date', + }, + { + key: 'dateTo', + label: 'Date to', + type: 'date', + }, + pageSizeFilter, + ] + } + + const filtersOut: Filter[] = [ + handleFilter, + statusFilter, + ] + + if (paymentListingTab === 'topcoder') { + filtersOut.push({ + displayValueInTrigger: true, + key: 'category', + label: 'Type', + options: TOPCODER_TYPE_FILTER_OPTIONS, + type: 'multi_dropdown', + }) + } + + filtersOut.push( + { + key: 'dateFrom', + label: 'Date from', + type: 'date', + }, + { + key: 'dateTo', + label: 'Date to', + type: 'date', + }, + pageSizeFilter, + ) + + return filtersOut + }, [ + hasPaymentAdminRole, + isApproverView, + isWiproTaasAdmin, + paymentListingTab, + ]) + const selectedPaymentActions = selectedPaymentsCount > 0 ? [ { @@ -603,29 +964,56 @@ const PaymentsListView: FC = (props: PaymentsListViewProp isCollapsed={props.isCollapsed} onToggle={props.onToggle} > - {canToggleRoleView && ( -
- Role: -
- - {isPaymentApprover && ( - - )} -
+ {([ + { id: 'topcoder' as const, label: 'Topcoder' }, + { id: 'topgear' as const, label: 'Topgear' }, + { id: 'taas' as const, label: 'TaaS' }, + ]).map(tab => ( + + ))} +
+ )} + {canToggleRoleView && ( +
+
+ + {isPaymentApprover && ( + + )} +
+
+ )} )} = (props: PaymentsListViewProp toast.success('Download complete', { position: toast.POSITION.BOTTOM_RIGHT }) }} selectedValueOverrides={{ ...defaultDropdownValues, ...selectedValueOverrides }} - filters={[ - { - key: 'winnerIds', - label: 'Username/Handle', - type: 'member_autocomplete', - }, - { - key: 'status', - label: 'Status', - options: [ - { - label: 'All', - value: 'all', - }, - { - label: 'Owed', - value: 'OWED', - }, - { - label: 'On Hold (Admin)', - value: 'ON_HOLD_ADMIN', - }, - { - label: 'On Hold (Member)', - value: 'ON_HOLD', - }, - { - label: 'Paid', - value: 'PAID', - }, - { - label: 'Cancelled', - value: 'CANCELLED', - }, - { - label: 'Processing', - value: 'PROCESSING', - }, - { - label: 'Failed', - value: 'FAILED', - }, - { - label: 'Returned', - value: 'RETURNED', - }, - ], - type: 'dropdown', - }, - ...(isWiproTaasAdmin && !hasPaymentAdminRole ? [] : isApproverView ? [ - { - key: 'category', - label: 'Payment Type', - options: [ - { - label: 'All', - value: 'all', - }, - { - label: 'Task Payments', - value: taskPaymentCategory, - }, - { - label: 'Engagement Payments', - value: engagementPaymentCategory, - }, - ], - type: 'dropdown', - }, - ] as Filter[] : [ - { - key: 'category', - label: 'Type', - options: [ - { - label: 'All', - value: 'all', - }, - { - label: 'Task Payment', - value: 'TASK_PAYMENT', - }, - { - label: 'Contest Payment', - value: 'CONTEST_PAYMENT', - }, - { - label: 'Copilot Payment', - value: 'COPILOT_PAYMENT', - }, - { - label: 'Review Board Payment', - value: 'REVIEW_BOARD_PAYMENT', - }, - { - label: 'Engagement Payment', - value: 'ENGAGEMENT_PAYMENT', - }, - { - label: 'TaaS Payment', - value: 'TAAS_PAYMENT', - }, - { - label: 'Topgear Payment', - value: topgearPaymentCategory, - }, - ], - type: 'dropdown', - }, - ] as Filter[]), - { - key: 'date', - label: 'Date', - options: [ - { - label: 'Last 7 days', - value: 'last7days', - }, - { - label: 'Last 30 days', - value: 'last30days', - }, - { - label: 'All', - value: 'all', - }, - ], - type: 'dropdown', - }, - { - key: 'pageSize', - label: 'Payments per page', - options: [ - { - label: '10', - value: '10', - }, - { - label: '50', - value: '50', - }, - { - label: '100', - value: '100', - }, - ], - type: 'dropdown', - }, - ]} + filters={listingFilters} + hasPendingChanges={hasPendingFilterChanges} onFilterChange={(key: string, value: string[]) => { - const newPagination = { - ...pagination, - currentPage: 1, - } - if (key === 'pageSize') { - newPagination.pageSize = parseInt(value[0], 10) - } - - setPagination(newPagination) - - setFilters(prev => ({ + setDraftFilters(prev => ({ ...prev, - [key]: value, // store 'all' explicitly; appliedFilters strips it before the API call + [key]: value, })) - setSelectedPayments({}) + }} + onApplyFilters={() => { + setDraftFilters(currentDraft => { + const nextPageSize = currentDraft.pageSize?.[0] + ? parseInt(currentDraft.pageSize[0], 10) + : pagination.pageSize + + setSubmittedFilters(currentDraft) + setPagination(prev => ({ + ...prev, + currentPage: 1, + pageSize: nextPageSize, + })) + setSelectedPayments({}) + + return currentDraft + }) }} onResetFilters={() => { setPagination({ @@ -815,7 +1062,8 @@ const PaymentsListView: FC = (props: PaymentsListViewProp currentPage: 1, pageSize: defaultPageSize, }) - setFilters({}) + setDraftFilters({}) + setSubmittedFilters({}) setSelectedPayments({}) }} /> @@ -868,10 +1116,11 @@ const PaymentsListView: FC = (props: PaymentsListViewProp content: ( setConfirmFlow(undefined)} /> ), showButtons: false, - title: 'Payment Details', + title: 'PAYMENT DETAILS', }) }} /> @@ -928,7 +1177,7 @@ const PaymentsListView: FC = (props: PaymentsListViewProp )} {confirmFlow && ( void; + onApplyFilters?: () => void; onResetFilters?: () => void; onExport?: () => void; selectedCount?: number; onBulkClick?: () => void; selectionActions?: FilterBarSelectionAction[]; - selectedValueOverrides?: Record; + selectedValueOverrides?: Record; hasActiveFilters?: boolean; + hasPendingChanges?: boolean; +} + +function parseIsoDateOnly(value: string | undefined): Date | undefined { + if (!value) { + return undefined + } + + const [y, m, d] = value.split('-') + .map(part => parseInt(part, 10)) + if (!y || !m || !d) { + return undefined + } + + return new Date(y, m - 1, d) +} + +function formatIsoDateOnly(date: Date | null): string[] { + if (!date) { + return [] + } + + const y = date.getFullYear() + const mo = String(date.getMonth() + 1) + .padStart(2, '0') + const day = String(date.getDate()) + .padStart(2, '0') + + return [`${y}-${mo}-${day}`] } const FilterBar: React.FC = (props: FilterBarProps) => { @@ -62,11 +99,33 @@ const FilterBar: React.FC = (props: FilterBarProps) => { }] : []) + useEffect(() => { + props.filters.forEach(filter => { + if (filter.type !== 'multi_dropdown') { + return + } + + const override = props.selectedValueOverrides?.[filter.key] + const next = Array.isArray(override) + ? override + : (override ? [override] : []) + setSelectedValue(prev => { + const cur = (prev.get(filter.key) as string[] | undefined) ?? [] + if (cur.length === next.length && cur.every((v, i) => v === next[i])) { + return prev + } + + return new Map(prev.set(filter.key, next)) + }) + }) + }, [props.filters, props.selectedValueOverrides]) + const renderDropdown = (index: number, filter: Filter): JSX.Element => ( ) => { @@ -76,16 +135,67 @@ const FilterBar: React.FC = (props: FilterBarProps) => { name={filter.key} label={filter.label} dirty + displayValueInTrigger={filter.displayValueInTrigger} placeholder={filter.label} /> ) + const renderMultiDropdown = (index: number, filter: Filter): JSX.Element => { + const baseOptions = filter.options ?? [] + const override = props.selectedValueOverrides?.[filter.key] + const valuesFromParent: string[] = Array.isArray(override) + ? override as string[] + : (override ? [override as string] : (selectedValue.get(filter.key) as string[] | undefined) ?? []) + + return ( + { + setSelectedValue(new Map(selectedValue.set(filter.key, values))) + props.onFilterChange(filter.key, values) + }} + /> + ) + } + + const renderDate = (index: number, filter: Filter): JSX.Element => { + const override = props.selectedValueOverrides?.[filter.key] + const iso = typeof override === 'string' + ? override + : undefined + + return ( +
+ { + props.onFilterChange(filter.key, formatIsoDateOnly(date)) + }} + isClearable + /> +
+ ) + } + const renderMemberAutoComplete = (index: number, filter: Filter): JSX.Element => ( - ) => { selectedMembers.current = event setSelectedValue(new Map(selectedValue.set(filter.key, event))) @@ -96,75 +206,109 @@ const FilterBar: React.FC = (props: FilterBarProps) => { /> ) + const renderFilterControl = (index: number, filter: Filter): JSX.Element => { + if (filter.type === 'dropdown') { + return renderDropdown(index, filter) + } + + if (filter.type === 'multi_dropdown') { + return renderMultiDropdown(index, filter) + } + + if (filter.type === 'date') { + return renderDate(index, filter) + } + + if (filter.type === 'member_autocomplete') { + return renderMemberAutoComplete(index, filter) + } + + return ( + ) => { + setSelectedValue(new Map(selectedValue.set(filter.key, event.target.value))) + props.onFilterChange(filter.key, [event.target.value]) + }} + /> + ) + } + + const firstFilter = props.filters[0] + const restFilters = props.filters.slice(1) + return (
-
- {props.filters.slice(0, 1) - .map((options, index) => ( -
- {options.type === 'dropdown' && renderDropdown(index, options)} - {options.type === 'input' && ( - ) => { - setSelectedValue(new Map(selectedValue.set(options.key, event.target.value))) - props.onFilterChange(options.key, [event.target.value]) - }} - /> - )} - {options.type === 'member_autocomplete' && renderMemberAutoComplete(index, options)} +
+ {firstFilter && ( +
+
+ {renderFilterControl(0, firstFilter)}
- ))} -
-
-
- {props.filters.slice(1) - .map((options, index) => ( -
- {options.type === 'dropdown' && renderDropdown(index + 1, options)} - {options.type === 'input' && ( - ) => { - setSelectedValue(new Map(selectedValue.set(options.key, event.target.value))) - props.onFilterChange(options.key, [event.target.value]) - }} - /> - )} - {options.type === 'member_autocomplete' && renderMemberAutoComplete(index + 1, options)} -
- ))} +
+ )} + {restFilters.length > 0 && ( +
+ {restFilters.map((filter, index) => ( +
+ {renderFilterControl(index + 1, filter)} +
+ ))} +
+ )} +
+ {props.showExportButton && ( +
+
- {props.showExportButton && ( - + + +
+ {menuIsVisible && ( +
) { + event.preventDefault() + event.stopPropagation() + }} + onClick={function onClick(event: MouseEvent) { + event.stopPropagation() + }} + > +
+ +
+ {props.options.map(option => ( +
+ toggleOption(option.value)} + /> +
+ ))} +
+ )} +
+
+ + ) +} + +export default FilterCheckboxMultiselect diff --git a/src/apps/wallet-admin/src/lib/components/filter-bar/FilterHandleAutocomplete.module.scss b/src/apps/wallet-admin/src/lib/components/filter-bar/FilterHandleAutocomplete.module.scss new file mode 100644 index 000000000..9d5fac8a6 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/filter-bar/FilterHandleAutocomplete.module.scss @@ -0,0 +1,19 @@ +@import '@libs/ui/styles/includes'; + +.memberSelect { + color: $black-60; + width: 100%; + + [class*="-indicatorSeparator"] { + display: none; + } +} + +.searchIndicator { + color: $turq-160; + padding: 0; + + svg { + @include icon-size(14); + } +} diff --git a/src/apps/wallet-admin/src/lib/components/filter-bar/FilterHandleAutocomplete.tsx b/src/apps/wallet-admin/src/lib/components/filter-bar/FilterHandleAutocomplete.tsx new file mode 100644 index 000000000..4f3cdb032 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/filter-bar/FilterHandleAutocomplete.tsx @@ -0,0 +1,120 @@ +import { get } from 'lodash' +import { FC, FocusEvent, useMemo } from 'react' +import { components, MultiValue, StylesConfig } from 'react-select' +import AsyncSelect from 'react-select/async' +import classNames from 'classnames' + +import { IconOutline, InputWrapper } from '~/libs/ui' +import { + membersAutocompete, + MembersAutocompeteResult, +} from '~/apps/admin/src/platform/gamification-admin/src/game-lib' + +import styles from './FilterHandleAutocomplete.module.scss' + +interface FilterHandleAutocompleteProps { + readonly className?: string + readonly dirty?: boolean + readonly disabled?: boolean + readonly hideInlineErrors?: boolean + readonly label?: string | JSX.Element + readonly name: string + readonly onBlur?: (event: FocusEvent) => void + readonly onChange: (newValue: Array) => void + readonly placeholder?: string + readonly tabIndex: number + readonly value?: Array +} + +const SearchDropdownIndicator: FC = (indicatorProps: any) => ( + + + +) + +const FilterHandleAutocomplete: FC = (props: FilterHandleAutocompleteProps) => { + const customStyles: StylesConfig = useMemo(() => ({ + control: provided => ({ + ...provided, + border: 'none', + boxShadow: 'none', + minHeight: 0, + }), + input: provided => ({ + ...provided, + color: 'inherit', + fontSize: 16, + }), + multiValue: provided => ({ + ...provided, + borderRadius: 50, + }), + multiValueLabel: provided => ({ + ...provided, + fontSize: 12, + }), + option: provided => ({ + ...provided, + borderBottom: '1px solid #E9E9E9', + color: 'inherit', + fontSize: 16, + fontWeight: 400, + padding: 16, + }), + placeholder: provided => ({ + ...provided, + color: 'inherit', + fontSize: 16, + fontWeight: 400, + }), + valueContainer: provided => ({ + ...provided, + padding: 0, + }), + }), []) + + const selectComponents = useMemo(() => ({ + DropdownIndicator: SearchDropdownIndicator, + }), []) + + function getUserProp(key: string): (d: unknown) => string { + return d => get(d, key) + } + + function handleChange(newValue: MultiValue): void { + props.onChange(newValue as Array) + } + + return ( + + + className={classNames(styles.memberSelect, props.className)} + cacheOptions + components={selectComponents} + getOptionLabel={getUserProp('handle')} + getOptionValue={getUserProp('userId')} + isMulti + key={props.value?.length} + loadOptions={membersAutocompete} + styles={customStyles} + placeholder={props.placeholder} + onBlur={props.onBlur} + onChange={handleChange} + value={props.value} + isDisabled={props.disabled} + /> + + ) +} + +export default FilterHandleAutocomplete diff --git a/src/apps/wallet-admin/src/lib/components/payment-view/PaymentAgreementBanner.tsx b/src/apps/wallet-admin/src/lib/components/payment-view/PaymentAgreementBanner.tsx new file mode 100644 index 000000000..15be974fd --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/payment-view/PaymentAgreementBanner.tsx @@ -0,0 +1,78 @@ +import { FC } from 'react' +import classNames from 'classnames' + +import { IconOutline } from '~/libs/ui' + +import { PaymentAgreementSummary } from '../../models/WinningDetail' + +import { + formatAgreementBreakdown, + formatAgreementCurrencyAmount, + formatAgreementDifferenceLabel, + formatAgreementExpectedAmount, +} from './payment-view.utils' +import styles from './PaymentView.module.scss' + +interface PaymentAgreementBannerProps { + readonly summary: PaymentAgreementSummary +} + +const PaymentAgreementBanner: FC = ( + props: PaymentAgreementBannerProps, +) => { + const breakdown = formatAgreementBreakdown(props.summary) + const differenceLabel = formatAgreementDifferenceLabel(props.summary) + + return ( +
+
+ {props.summary.status === 'match' && ( + + )} + {props.summary.status === 'under' && ( + + )} + {props.summary.status === 'over' && ( + + )} +
+
+ + {props.summary.status === 'match' + ? 'PAYMENT MATCHES AGREEMENT' + : 'PAYMENT DOESN\'T MATCH AGREEMENT'} + +

+ Expected: + {' '} + {formatAgreementExpectedAmount(props.summary)} + {' '} + ( + {breakdown} + ) +

+

+ Actual: + {' '} + {formatAgreementCurrencyAmount(props.summary.actualAmount)} + {props.summary.status !== 'match' && ( + <> + {' '} + ( + {differenceLabel} + ) + + )} +

+
+
+ ) +} + +export default PaymentAgreementBanner diff --git a/src/apps/wallet-admin/src/lib/components/payment-view/PaymentDetailsSummaryRow.tsx b/src/apps/wallet-admin/src/lib/components/payment-view/PaymentDetailsSummaryRow.tsx new file mode 100644 index 000000000..49d158af3 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/payment-view/PaymentDetailsSummaryRow.tsx @@ -0,0 +1,102 @@ +import { FC } from 'react' +import classNames from 'classnames' + +import { formatCurrencyAmount, formatOptionalText } from './payment-view.utils' +import styles from './PaymentView.module.scss' + +interface PaymentDetailsSummaryRowProps { + readonly approverLabel?: string + readonly budgetApproverHandle?: string + readonly columns?: 2 | 4 | 5 + readonly creatorLabel?: string + readonly handle: string + readonly paymentAmount: number + readonly paymentApproverHandle?: string + readonly paymentCreatorHandle?: string + readonly secondaryApproverLabel?: string + readonly isPoints?: boolean +} + +const DEFAULT_CREATOR_LABEL = 'Payment Creator' +const DEFAULT_APPROVER_LABEL = 'Payment Approver' + +const PaymentDetailsSummaryRow: FC = ( + props: PaymentDetailsSummaryRowProps, +) => { + const columns = props.columns ?? 4 + const usesCompactSummary = columns === 2 + const usesBudgetApproverColumn = columns === 5 + const usesBudgetApproverLabel = props.approverLabel === 'Budget Approver' + + if (usesCompactSummary) { + return ( +
+
+ Handle + {props.handle} +
+
+ {props.isPoints ? 'Points' : 'Payment'} + + {props.isPoints + ? String(props.paymentAmount) + : formatCurrencyAmount(props.paymentAmount)} + +
+
+ ) + } + + return ( +
+
+ Handle + {props.handle} +
+
+ {props.isPoints ? 'Points' : 'Payment'} + + {props.isPoints + ? String(props.paymentAmount) + : formatCurrencyAmount(props.paymentAmount)} + +
+
+ + {props.creatorLabel ?? DEFAULT_CREATOR_LABEL} + + + {formatOptionalText(props.paymentCreatorHandle)} + +
+
+ + {props.approverLabel ?? DEFAULT_APPROVER_LABEL} + + + {formatOptionalText( + usesBudgetApproverColumn || usesBudgetApproverLabel + ? props.budgetApproverHandle + : props.paymentApproverHandle, + )} + +
+ {usesBudgetApproverColumn && ( +
+ + {props.secondaryApproverLabel ?? DEFAULT_APPROVER_LABEL} + + + {formatOptionalText(props.paymentApproverHandle)} + +
+ )} +
+ ) +} + +export default PaymentDetailsSummaryRow diff --git a/src/apps/wallet-admin/src/lib/components/payment-view/PaymentDetailsTabs.tsx b/src/apps/wallet-admin/src/lib/components/payment-view/PaymentDetailsTabs.tsx new file mode 100644 index 000000000..91d116cd0 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/payment-view/PaymentDetailsTabs.tsx @@ -0,0 +1,37 @@ +/* eslint-disable react/jsx-no-bind */ +import { FC } from 'react' +import classNames from 'classnames' + +import styles from './PaymentView.module.scss' + +export interface PaymentDetailsTabOption { + id: string + label: string +} + +interface PaymentDetailsTabsProps { + readonly activeTabId: string + readonly onTabChange: (tabId: string) => void + readonly tabs: ReadonlyArray +} + +const PaymentDetailsTabs: FC = (props: PaymentDetailsTabsProps) => ( +
+ {props.tabs.map(tab => ( + + ))} +
+) + +export default PaymentDetailsTabs diff --git a/src/apps/wallet-admin/src/lib/components/payment-view/PaymentView.module.scss b/src/apps/wallet-admin/src/lib/components/payment-view/PaymentView.module.scss index 59db5e6f0..5164bd802 100644 --- a/src/apps/wallet-admin/src/lib/components/payment-view/PaymentView.module.scss +++ b/src/apps/wallet-admin/src/lib/components/payment-view/PaymentView.module.scss @@ -1,65 +1,311 @@ +@import '@libs/ui/styles/includes'; + +$payment-field-label-color: #2a2a2a; + .formContainer { display: flex; flex-direction: column; + gap: 20px; + max-width: 100%; + min-width: 0; } -.infoGroup { +.summaryRow { display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 20px; - margin-bottom: 20px; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 16px; +} + +.summaryRowTwo { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.summaryRowFive { + gap: 12px; + grid-template-columns: repeat(5, minmax(0, 1fr)); + + .summaryValue { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } } -.infoItem { +.summaryItem { display: flex; flex-direction: column; - gap: 2px; + gap: 4px; + min-width: 0; } -.label { - font-weight: bold; - margin-bottom: 5px; +.summaryLabel { + color: $payment-field-label-color; + font-size: 16px; + font-weight: 700; } -.inputGroup { +.summaryValue { + color: $black-100; + font-size: 16px; + font-weight: 400; +} + +.agreementBanner { + align-items: center; + border-radius: 4px; + display: grid; + gap: 12px; + grid-template-columns: auto 1fr; + padding: 12px 16px; +} + +.agreementBannerMatch { + background: #defbe6; + color: #0e6027; +} + +.agreementBannerUnder { + background: #fcf4d6; + color: #684e00; +} + +.agreementBannerOver { + background: #fff0f7; + color: #8c384c; +} + +.agreementBannerIconColumn { + align-self: center; + display: flex; + flex-shrink: 0; + justify-content: center; +} + +.agreementBannerIcon { + color: currentColor; + flex-shrink: 0; + width: 26px; + + svg { + @include icon-size(18); + } +} + +.agreementBannerContent { display: flex; flex-direction: column; - gap: 20px; + gap: 4px; + min-width: 0; } -.section { - border-top: 1px solid #e6e6e6; - padding-top: 20px; +.agreementBannerTitle { + font-size: 14px; + font-weight: 700; + letter-spacing: 0.02em; } -.sectionTitle { - font-size: 18px; - margin: 0 0 16px; +.agreementBannerLine { + font-size: 14px; + margin: 0; + + strong { + font-weight: 700; + } } -.sectionGrid { - display: grid; +.tabList { + border-bottom: 1px solid $black-20; + display: flex; + gap: 24px; +} + +.tabButton { + background: transparent; + border: none; + color: $black-60; + cursor: pointer; + font-size: 16px; + font-weight: 600; + margin-bottom: -1px; + padding: 8px 0; +} + +.tabButtonActive { + border-bottom: 2px solid $turq-160; + color: $black-100; +} + +.tabPanel { + max-width: 100%; + min-height: 180px; + min-width: 0; +} + +.tabPanelContent { + display: flex; + flex-direction: column; gap: 20px; - grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); } -.helperText { - color: #5b5b5b; +.detailsGrid { + display: grid; + gap: 20px 32px; + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.infoItem, +.infoItemFull { + display: flex; + flex-direction: column; + gap: 4px; +} + +.infoItemFull { + grid-column: 1 / -1; +} + +.label { + color: $payment-field-label-color; + font-size: 16px; + font-weight: 700; +} + +.value, +.linkValue { + color: $black-100; + font-size: 16px; margin: 0; } +.linkValue { + color: $link-blue-dark; + text-decoration: none; + word-break: break-word; + + &:hover { + text-decoration: underline; + } +} + .remarksValue { + color: $black-100; + font-size: 16px; + margin: 0; + overflow-wrap: anywhere; + white-space: pre-wrap; +} + +.helperText { + color: $black-60; margin: 0; +} + +.auditTableWrap { + max-width: 100%; + min-width: 0; + width: 100%; +} + +.auditGridHeader, +.auditGridRow { + column-gap: 0; + display: grid; + grid-template-columns: minmax(108px, 17%) minmax(96px, 15%) minmax(0, 1fr) minmax(96px, 22%); + width: 100%; +} + +.auditGridHeader { + margin-bottom: 4px; +} + +.auditGridHeadCell { + color: $payment-field-label-color; + font-size: 12px; + font-weight: 700; + letter-spacing: 0.02em; + min-width: 0; + padding: 0 16px 12px; + text-align: left; + text-transform: uppercase; +} + +.auditGridCell { + color: $black-100; + font-size: 14px; + line-height: 1.4; + min-width: 0; overflow-wrap: anywhere; + padding: 16px; + white-space: normal; + word-break: break-word; +} + +.auditGridRowOdd .auditGridCell { + background-color: #f4f4f4; +} + +.auditGridRowEven .auditGridCell { + background-color: #fff; +} + +.auditGridRowOdd .auditGridCell:first-child, +.auditGridRowEven .auditGridCell:first-child { + border-bottom-left-radius: 8px; + border-top-left-radius: 8px; +} + +.auditGridRowOdd .auditGridCell:last-child, +.auditGridRowEven .auditGridCell:last-child { + border-bottom-right-radius: 8px; + border-top-right-radius: 8px; +} + +.auditGridActionCell { + hyphens: auto; + overflow-wrap: anywhere; + white-space: normal; + word-break: break-word; + + strong { + font-weight: 700; + white-space: normal; + } +} + +.externalTransactionGroup { + display: flex; + flex-direction: column; + gap: 16px; + margin-bottom: 16px; +} + +.externalTransactionCollapsible { + border: 1px solid $black-20; + border-radius: 4px; +} + +.externalTransactionContent { + padding: 12px 16px; +} + +.externalTransactionPre { + font-size: 12px; + margin: 0; + overflow-x: auto; white-space: pre-wrap; } -.container { - margin: 8px 0; +.footer { + display: flex; + justify-content: center; + padding-top: 8px; +} - .content { - display: flex; - flex-direction: column; - margin-bottom: 0; +@media (max-width: 768px) { + .summaryRow, + .summaryRowTwo, + .summaryRowFive, + .detailsGrid { + grid-template-columns: 1fr; } } diff --git a/src/apps/wallet-admin/src/lib/components/payment-view/PaymentView.spec.tsx b/src/apps/wallet-admin/src/lib/components/payment-view/PaymentView.spec.tsx index 351ba2813..97bb368be 100644 --- a/src/apps/wallet-admin/src/lib/components/payment-view/PaymentView.spec.tsx +++ b/src/apps/wallet-admin/src/lib/components/payment-view/PaymentView.spec.tsx @@ -6,15 +6,20 @@ import { waitFor, within, } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { Winning } from '../../models/WinningDetail' import { + fetchAuditLogs, + fetchChallengePaymentSummary, fetchWinningPaymentDetails, + getMemberHandle, } from '../../services/wallet' import { PaymentView } from '.' jest.mock('../../services/wallet', () => ({ fetchAuditLogs: jest.fn(), + fetchChallengePaymentSummary: jest.fn(), fetchPayoutAuditLogs: jest.fn(), fetchWinningPaymentDetails: jest.fn(), getMemberHandle: jest.fn(), @@ -29,15 +34,13 @@ jest.mock('~/libs/ui', () => ({ {props.label} ), - Collapsible: (props: { - children: React.ReactNode - header: React.ReactNode - }) => ( -
- {props.header} - {props.children} -
- ), + IconOutline: { + CheckIcon: () => check-icon, + }, + IconSolid: { + ExclamationIcon: () => exclamation-icon, + XCircleIcon: () => x-icon, + }, }), { virtual: true }) jest.mock('~/config/environments/default.env', () => ({ @@ -55,6 +58,12 @@ jest.mock('~/config', () => ({ const mockedFetchWinningPaymentDetails = ( fetchWinningPaymentDetails as jest.MockedFunction ) +const mockedFetchChallengePaymentSummary = ( + fetchChallengePaymentSummary as jest.MockedFunction +) +const mockedFetchAuditLogs = fetchAuditLogs as jest.MockedFunction +const mockedGetMemberHandle = getMemberHandle as jest.MockedFunction + const expectedWorkManagerLink = 'https://challenges.example.com/projects/project-789/engagements/engagement-456/assignments' + '?assignmentId=assignment-123' @@ -63,27 +72,27 @@ const expectedProjectLink describe('PaymentView', () => { const payment: Winning = { - createDate: 'Mar 21, 2026', + createDate: '15/5/2026', currency: 'USD', - datePaid: '-', + datePaid: '30/5/2026', description: 'Wipro - US Foods - Week Ending: Mar 21, 2026', details: [{ currency: 'USD', datePaid: '', - grossAmount: '463.75', + grossAmount: '2000', id: 'payment-1', installmentNumber: 1, status: 'OWED', - totalAmount: '463.75', + totalAmount: '2000', }], externalId: 'assignment-123', - grossAmount: '$463.75', - grossAmountNumber: 463.75, - handle: 'vikashchaudhary26', + grossAmount: '$2,000.00', + grossAmountNumber: 2000, + handle: 'disnadiji', id: 'winning-1', - releaseDate: 'Apr 11, 2026', - releaseDateObj: new Date('2026-04-11T00:00:00.000Z'), - status: 'Owed', + releaseDate: '15/5/2026', + releaseDateObj: new Date('2026-05-15T00:00:00.000Z'), + status: 'On Hold (Admin)', type: 'engagement payment', } @@ -92,60 +101,49 @@ describe('PaymentView', () => { engagementDetails: { assignmentId: 'assignment-123', billingStartDate: '2026-02-16T00:00:00.000Z', - durationMonths: 3, + durationMonths: 12, engagementId: 'engagement-456', - engagementTitle: 'Snowflake Developer - Vikash', - otherRemarks: 'Complete onboarding within the first week. Docs: https://google.com', + paymentApproverHandle: 'TonyJ', + paymentCycle: 'weekly', projectId: 'project-789', - projectName: 'Wipro - US Foods', - ratePerHour: '10.60', - standardHoursPerWeek: 43.75, + projectName: 'Test Project Engagement BA', + ratePerHour: '50', + standardHoursPerDay: 8, }, - paymentCreatorHandle: 'copilot-manager', + paymentCreatorHandle: 'wendell', workLog: { - hoursWorked: 43.75, - remarks: 'Completed sprint support and bug triage. Reference: https://example.com/worklog', + hoursWorked: 40, + remarks: 'Member worked 40 hours this week.', }, }) + mockedFetchAuditLogs.mockResolvedValue([]) + mockedFetchChallengePaymentSummary.mockResolvedValue({}) + mockedGetMemberHandle.mockResolvedValue(new Map()) }) afterEach(() => { jest.clearAllMocks() }) - it('renders engagement details and a work-manager assignee link for engagement payments', async () => { - render() + it('renders engagement payment details with tabs and agreement match banner', async () => { + render() await waitFor(() => { expect(mockedFetchWinningPaymentDetails) .toHaveBeenCalledWith(payment) }) - expect(await screen.findByRole('heading', { name: 'Engagement Details' })) + expect(await screen.findByText('PAYMENT MATCHES AGREEMENT')) .toBeTruthy() - await waitFor(() => { - expect(screen.getAllByText('43.75')) - .toHaveLength(2) - }) - expect(await screen.findByText(/Completed sprint support and bug triage\./)) + expect(screen.getByText('disnadiji')) .toBeTruthy() - - const workLogHeading = await screen.findByRole('heading', { - name: 'Work Log / Manager Inputs', - }) - const workLogSection = workLogHeading.parentElement - - if (!workLogSection) { - throw new Error('Expected work log section to be rendered.') - } - - expect(screen.getAllByText('Payment Creator')) - .toHaveLength(1) - expect(within(workLogSection) - .getByText('Payment Creator')) + expect(screen.getByText('wendell')) .toBeTruthy() - expect(within(workLogSection) - .getByText('copilot-manager')) + expect(screen.getByText('Payment Approver')) + .toBeTruthy() + expect(screen.getByText('TonyJ')) + .toBeTruthy() + expect(screen.getByRole('tab', { name: 'Engagement Details' })) .toBeTruthy() const descriptionLink = await screen.findByRole('link', { @@ -155,72 +153,336 @@ describe('PaymentView', () => { expect(descriptionLink.getAttribute('href')) .toBe(expectedWorkManagerLink) + await userEvent.click(screen.getByRole('tab', { name: 'Engagement Details' })) + const projectLink = await screen.findByRole('link', { - name: 'Wipro - US Foods', + name: 'Test Project Engagement BA', }) expect(projectLink.getAttribute('href')) .toBe(expectedProjectLink) - expect(projectLink.getAttribute('target')) - .toBe('_blank') + expect(screen.getByText('Weekly')) + .toBeTruthy() + expect(screen.getByText('8')) + .toBeTruthy() + + await userEvent.click(screen.getByRole('tab', { name: 'Work Log' })) + + expect(await screen.findByText('Member worked 40 hours this week.')) + .toBeTruthy() + expect(screen.getAllByText('Payment Creator')) + .toHaveLength(1) + expect(screen.queryByText('Payment Approver')) + .not.toBeNull() + }) + + it('renders task payment details with five-column summary and task details tab', async () => { + const taskPayment: Winning = { + ...payment, + description: 'Build a responsive front-end interface for General Electric.', + externalId: 'challenge-uuid-1', + type: 'task payment', + } + + mockedFetchChallengePaymentSummary.mockResolvedValue({ + budgetApproverHandle: 'kartik', + }) + + mockedFetchWinningPaymentDetails.mockResolvedValue({ + paymentCreatorHandle: 'chAVGA5xBDH6Gxz1JMJ67Np7CBSAtoaL@clients', + taskDetails: { + paymentApproverHandle: 'TonyJ', + paymentCreatorHandle: 'wendell', + projectId: '42', + projectName: 'General Electric Front End Task', + taskDescription: 'Deliver clean, production-ready HTML, CSS, and JavaScript.', + }, + }) + + render() - const remarksLink = await screen.findByRole('link', { - name: 'https://google.com', + await waitFor(() => { + expect(mockedFetchWinningPaymentDetails) + .toHaveBeenCalledWith(taskPayment) }) - expect(remarksLink.getAttribute('href')) - .toBe('https://google.com') - expect(remarksLink.getAttribute('target')) - .toBe('_blank') + await waitFor(() => { + expect(mockedFetchChallengePaymentSummary) + .toHaveBeenCalledWith('challenge-uuid-1') + }) - const workLogRemarksLink = await screen.findByRole('link', { - name: 'https://example.com/worklog', + expect(screen.getByText('Task Creator')) + .toBeTruthy() + expect(screen.getByText('Budget Approver')) + .toBeTruthy() + expect(screen.getByText('Payment Approver')) + .toBeTruthy() + expect(screen.queryByText('Payment Creator')) + .toBeNull() + expect(await screen.findByText('wendell')) + .toBeTruthy() + expect(screen.queryByText('chAVGA5xBDH6Gxz1JMJ67Np7CBSAtoaL@clients')) + .toBeNull() + expect(await screen.findByText('kartik')) + .toBeTruthy() + expect(await screen.findByText('TonyJ')) + .toBeTruthy() + expect(screen.queryByText('PAYMENT MATCHES AGREEMENT')) + .toBeNull() + expect(await screen.findByRole('tab', { name: 'Task Details' })) + .toBeTruthy() + + await waitFor(() => { + expect(screen.queryByText('Loading task details...')) + .toBeNull() }) - expect(workLogRemarksLink.getAttribute('href')) - .toBe('https://example.com/worklog') - expect(workLogRemarksLink.getAttribute('target')) - .toBe('_blank') + await userEvent.click(screen.getByRole('tab', { name: 'Task Details' })) + + const taskDetailsPanel = screen.getByRole('tabpanel') + + expect(within(taskDetailsPanel) + .getByText('Task Creator')) + .toBeTruthy() + expect(within(taskDetailsPanel) + .getByText('wendell')) + .toBeTruthy() + expect(within(taskDetailsPanel) + .getByText('Project Name')) + .toBeTruthy() + expect(within(taskDetailsPanel) + .getByText('Task Description')) + .toBeTruthy() + expect(within(taskDetailsPanel) + .getByText( + 'Deliver clean, production-ready HTML, CSS, and JavaScript.', + )) + .toBeTruthy() + const projectLink = within(taskDetailsPanel) + .getByRole('link', { + name: 'General Electric Front End Task', + }) + expect(projectLink.getAttribute('href')) + .toBe('https://challenges.example.com/projects/42') }) - it('renders task details section for task payments', async () => { + it('does not show task payment approver from challenge when finance omits it', async () => { const taskPayment: Winning = { ...payment, - description: 'Build a cool widget for the dashboard', + description: 'Pay period test task - 1st Place', externalId: 'challenge-uuid-1', type: 'task payment', } + mockedFetchChallengePaymentSummary.mockResolvedValue({ + budgetApproverHandle: 'mess', + paymentApproverHandle: 'mess', + }) + mockedFetchWinningPaymentDetails.mockResolvedValue({ taskDetails: { - paymentApproverHandle: 'approver-handle', - paymentCreatorHandle: 'task-creator', + paymentCreatorHandle: 'TCConnCopilot', projectId: '42', - projectName: 'My Awesome Project', + projectName: 'General Electric Front End Task', }, }) render() await waitFor(() => { - expect(mockedFetchWinningPaymentDetails) - .toHaveBeenCalledWith(taskPayment) + expect(mockedFetchChallengePaymentSummary) + .toHaveBeenCalledWith('challenge-uuid-1') + }) + + const paymentApproverLabel = await screen.findByText('Payment Approver') + const summaryPaymentApprover = paymentApproverLabel.closest('[class*="summaryItem"]') + + await waitFor(() => { + expect(summaryPaymentApprover?.textContent) + .not.toContain('mess') + }) + }) + + it('renders contest payment details with challenge creator and budget approver labels', async () => { + const contestPayment: Winning = { + ...payment, + description: 'Test Contest Task BA - Week Ending: May 02, 2026', + externalId: 'contest-challenge-1', + type: 'contest payment', + } + + mockedFetchChallengePaymentSummary.mockResolvedValue({ + budgetApproverHandle: 'kartik', + creatorHandle: 'wendell', + }) + + render() + + await waitFor(() => { + expect(mockedFetchChallengePaymentSummary) + .toHaveBeenCalledWith('contest-challenge-1') }) - expect(await screen.findByRole('heading', { name: 'Task Details' })) + expect(mockedFetchWinningPaymentDetails) + .not.toHaveBeenCalled() + + expect(screen.getByText('Challenge Creator')) + .toBeTruthy() + expect(screen.getByText('Budget Approver')) .toBeTruthy() + expect(screen.queryByText('Payment Creator')) + .toBeNull() + expect(screen.queryByText('Payment Approver')) + .toBeNull() + expect(await screen.findByText('wendell')) + .toBeTruthy() + expect(await screen.findByText('kartik')) + .toBeTruthy() + expect(screen.queryByText('PAYMENT MATCHES AGREEMENT')) + .toBeNull() + expect(screen.queryByRole('tab', { name: 'Engagement Details' })) + .toBeNull() + + const descriptionLink = await screen.findByRole('link', { + name: 'Test Contest Task BA - Week Ending: May 02, 2026', + }) - expect(await screen.findByRole('heading', { name: 'Task Details' })) + expect(descriptionLink.getAttribute('href')) + .toBe('https://www.example.com/challenges/contest-challenge-1') + + await userEvent.click(screen.getByRole('tab', { name: 'Audit History' })) + + await waitFor(() => { + expect(mockedFetchAuditLogs) + .toHaveBeenCalledWith(contestPayment.id) + }) + }) + + it.each([ + ['copilot payment', 'copilot-challenge-1', 'Copilot challenge payment'], + ['review board payment', 'review-board-1', 'Review board challenge payment'], + ])('renders %s with challenge payment summary and tabs', async (paymentType, externalId, description) => { + const styledPayment: Winning = { + ...payment, + description, + externalId, + type: paymentType, + } + + mockedFetchChallengePaymentSummary.mockResolvedValue({ + budgetApproverHandle: 'kartik', + creatorHandle: 'wendell', + }) + + render() + + await waitFor(() => { + expect(mockedFetchChallengePaymentSummary) + .toHaveBeenCalledWith(externalId) + }) + + expect(mockedFetchWinningPaymentDetails) + .not.toHaveBeenCalled() + + expect(screen.getByText('Challenge Creator')) + .toBeTruthy() + expect(screen.getByText('Budget Approver')) .toBeTruthy() - expect(await screen.findByText('task-creator')) + expect(await screen.findByText('wendell')) .toBeTruthy() - expect(await screen.findByText('approver-handle')) + expect(await screen.findByText('kartik')) .toBeTruthy() + expect(screen.queryByRole('tab', { name: 'Task Details' })) + .toBeNull() - const projectLink = await screen.findByRole('link', { name: 'My Awesome Project' }) - expect(projectLink.getAttribute('href')) - .toBe('https://challenges.example.com/projects/42/challenges/challenge-uuid-1/view') - expect(projectLink.getAttribute('target')) - .toBe('_blank') + const descriptionLink = await screen.findByRole('link', { name: description }) + expect(descriptionLink.getAttribute('href')) + .toBe(`https://www.example.com/challenges/${externalId}`) + }) + + it('renders topgear payment details with handle and payment summary only', async () => { + const topgearPayment: Winning = { + ...payment, + description: 'Test Project Topgeader BA - Week Ending: May 02, 2026', + externalId: 'topgear-challenge-1', + type: 'topgear payment', + } + + render() + + expect(screen.getByText('Handle')) + .toBeTruthy() + expect(screen.getByText('Payment')) + .toBeTruthy() + expect(screen.getByText('disnadiji')) + .toBeTruthy() + expect(screen.queryByText('Payment Creator')) + .toBeNull() + expect(screen.queryByText('Challenge Creator')) + .toBeNull() + expect(screen.queryByText('Task Creator')) + .toBeNull() + expect(screen.queryByRole('tab', { name: 'Task Details' })) + .toBeNull() + expect(screen.getByRole('tab', { name: 'General Info' })) + .toBeTruthy() + expect(screen.getByRole('tab', { name: 'Audit History' })) + .toBeTruthy() + + const descriptionLink = screen.getByRole('link', { + name: 'Test Project Topgeader BA - Week Ending: May 02, 2026', + }) + + expect(descriptionLink.getAttribute('href')) + .toBe('https://www.example.com/challenges/topgear-challenge-1') + + expect(mockedFetchWinningPaymentDetails) + .not.toHaveBeenCalled() + + await userEvent.click(screen.getByRole('tab', { name: 'Audit History' })) + + await waitFor(() => { + expect(mockedFetchAuditLogs) + .toHaveBeenCalledWith(topgearPayment.id) + }) + }) + + it('renders taas payment details with handle and payment summary only', async () => { + const taasPayment: Winning = { + ...payment, + description: 'Test Project TaaS BA - Week Ending: May 02, 2026', + externalId: 'taas-challenge-1', + type: 'taas payment', + } + + render() + + expect(screen.getByText('Handle')) + .toBeTruthy() + expect(screen.getByText('Payment')) + .toBeTruthy() + expect(screen.queryByText('Payment Creator')) + .toBeNull() + expect(screen.getByRole('tab', { name: 'General Info' })) + .toBeTruthy() + expect(screen.getByRole('tab', { name: 'Audit History' })) + .toBeTruthy() + + const descriptionLink = screen.getByRole('link', { + name: 'Test Project TaaS BA - Week Ending: May 02, 2026', + }) + + expect(descriptionLink.getAttribute('href')) + .toBe('https://www.example.com/challenges/taas-challenge-1') + + expect(mockedFetchWinningPaymentDetails) + .not.toHaveBeenCalled() + + await userEvent.click(screen.getByRole('tab', { name: 'Audit History' })) + + await waitFor(() => { + expect(mockedFetchAuditLogs) + .toHaveBeenCalledWith(taasPayment.id) + }) }) }) diff --git a/src/apps/wallet-admin/src/lib/components/payment-view/PaymentView.tsx b/src/apps/wallet-admin/src/lib/components/payment-view/PaymentView.tsx index a597eee66..4452c47c3 100644 --- a/src/apps/wallet-admin/src/lib/components/payment-view/PaymentView.tsx +++ b/src/apps/wallet-admin/src/lib/components/payment-view/PaymentView.tsx @@ -1,57 +1,113 @@ -/* eslint-disable react/no-array-index-key */ -/* eslint-disable unicorn/no-null */ -/* eslint-disable max-len */ /* eslint-disable react/jsx-no-bind */ -/* eslint-disable complexity */ -/* eslint-disable ordered-imports/ordered-imports */ import React from 'react' -import { Button, Collapsible } from '~/libs/ui' +import { Button } from '~/libs/ui' import { TOPCODER_URL } from '~/config/environments/default.env' +import { PayoutAudit } from '../../models/PayoutAudit' import { WinningsAudit } from '../../models/WinningsAudit' import { Winning, WinningPaymentDetails } from '../../models/WinningDetail' -import { PayoutAudit } from '../../models/PayoutAudit' import { fetchAuditLogs, + fetchChallengePaymentSummary, fetchPayoutAuditLogs, fetchWinningPaymentDetails, getMemberHandle, } from '../../services/wallet' + import { buildWorkAppChallengeUrl, buildWorkManagerAssignmentUrl, buildWorkManagerProjectUrl, - formatOptionalDate, - formatOptionalText, - renderOptionalLinkedText, + getPaymentDetailsSummaryConfig, + isChallengePaymentType, + resolvePaymentAgreementSummary, + resolvePaymentApproverHandle, + resolveTaskCreatorHandle, } from './payment-view.utils' - +import PaymentAgreementBanner from './PaymentAgreementBanner' +import PaymentAuditHistoryTab from './tabs/PaymentAuditHistoryTab' +import PaymentDetailsSummaryRow from './PaymentDetailsSummaryRow' +import PaymentDetailsTabs, { PaymentDetailsTabOption } from './PaymentDetailsTabs' +import PaymentEngagementDetailsTab from './tabs/PaymentEngagementDetailsTab' +import PaymentExternalTransactionTab from './tabs/PaymentExternalTransactionTab' +import PaymentGeneralInfoTab from './tabs/PaymentGeneralInfoTab' +import PaymentTaskDetailsTab from './tabs/PaymentTaskDetailsTab' +import PaymentWorkLogTab from './tabs/PaymentWorkLogTab' import styles from './PaymentView.module.scss' interface PaymentViewProps { isPoints?: boolean + onClose?: () => void payment: Winning } +type PaymentDetailsTabId + = 'general' + | 'engagement' + | 'work-log' + | 'task-details' + | 'audit' + | 'external-transaction' + const PaymentView: React.FC = (props: PaymentViewProps) => { - const [view, setView] = React.useState<'details' | 'audit' | 'external_transaction'>('details') + const [activeTab, setActiveTab] = React.useState('general') const [auditLines, setAuditLines] = React.useState([]) - const [externalTransactionAudit, setExternalTransactionAudit] = React.useState([]) + const [externalTransactionAudit, setExternalTransactionAudit] = React.useState() + const [isAuditLoading, setIsAuditLoading] = React.useState(false) + const [isExternalTransactionLoading, setIsExternalTransactionLoading] = React.useState(false) const [paymentDetails, setPaymentDetails] = React.useState() const [isPaymentDetailsLoading, setIsPaymentDetailsLoading] = React.useState(false) const [paymentDetailsError, setPaymentDetailsError] = React.useState() + const [challengeCreatorHandle, setChallengeCreatorHandle] = React.useState() + const [challengeBudgetApproverHandle, setChallengeBudgetApproverHandle] = React.useState() const isEngagementPayment = props.payment.type.toLowerCase() === 'engagement payment' const isTaskPayment = props.payment.type.toLowerCase() === 'task payment' - const hasEngagementDetails = Boolean(paymentDetails?.engagementDetails) + const isChallengePayment = isChallengePaymentType(props.payment.type) + const shouldFetchChallengeSummary = isChallengePayment || isTaskPayment + const shouldFetchPaymentDetails = isEngagementPayment || isTaskPayment + const summaryConfig = getPaymentDetailsSummaryConfig(props.payment.type) - const handleToggleView = (newView: 'audit' | 'details' | 'external_transaction'): void => { - setView(newView) - } + const agreementSummary = isEngagementPayment + ? resolvePaymentAgreementSummary(props.payment, paymentDetails) + : undefined + + const isPaidPayment = props.payment.status.toUpperCase() === 'PAID' + + const tabs = React.useMemo((): PaymentDetailsTabOption[] => { + const auditTabs: PaymentDetailsTabOption[] = [ + { id: 'audit', label: 'Audit History' }, + ...(isPaidPayment + ? [{ id: 'external-transaction' as const, label: 'External Transaction' }] + : []), + ] + + if (isEngagementPayment) { + return [ + { id: 'general', label: 'General Info' }, + { id: 'engagement', label: 'Engagement Details' }, + { id: 'work-log', label: 'Work Log' }, + ...auditTabs, + ] + } + + if (isTaskPayment) { + return [ + { id: 'general', label: 'General Info' }, + { id: 'task-details', label: 'Task Details' }, + ...auditTabs, + ] + } + + return [ + { id: 'general', label: 'General Info' }, + ...auditTabs, + ] + }, [isEngagementPayment, isPaidPayment, isTaskPayment]) React.useEffect(() => { - if (!isEngagementPayment && !isTaskPayment) { + if (!shouldFetchPaymentDetails) { setPaymentDetails(undefined) setIsPaymentDetailsLoading(false) setPaymentDetailsError(undefined) @@ -72,7 +128,11 @@ const PaymentView: React.FC = (props: PaymentViewProps) => { .catch(() => { if (!ignore) { setPaymentDetails(undefined) - setPaymentDetailsError(isTaskPayment ? 'Unable to load task details.' : 'Unable to load engagement details.') + setPaymentDetailsError( + isTaskPayment + ? 'Unable to load task details.' + : 'Unable to load engagement details.', + ) } }) .finally(() => { @@ -84,34 +144,111 @@ const PaymentView: React.FC = (props: PaymentViewProps) => { return () => { ignore = true } - }, [isEngagementPayment, isTaskPayment, props.payment]) + }, [isTaskPayment, props.payment, shouldFetchPaymentDetails]) React.useEffect(() => { - if (view === 'audit') { - fetchAuditLogs(props.payment.id) - .then(auditLogs => { - const userIds = auditLogs.map(log => log.userId) - getMemberHandle(userIds) - .then((handles: Map) => { - auditLogs.forEach((log: WinningsAudit) => { - log.userId = handles.get(parseInt(log.userId, 10)) ?? log.userId - }) - }) - .catch(() => undefined) - .finally(() => { - setAuditLines(auditLogs) - }) + if (!shouldFetchChallengeSummary || !props.payment.externalId) { + setChallengeCreatorHandle(undefined) + setChallengeBudgetApproverHandle(undefined) + return undefined + } + + let ignore = false + + fetchChallengePaymentSummary(props.payment.externalId) + .then(summary => { + if (!ignore) { + if (isChallengePayment) { + setChallengeCreatorHandle(summary.creatorHandle) + } + + setChallengeBudgetApproverHandle(summary.budgetApproverHandle) + } + }) + .catch(() => { + if (!ignore) { + setChallengeCreatorHandle(undefined) + setChallengeBudgetApproverHandle(undefined) + } + }) + + return () => { + ignore = true + } + }, [ + isChallengePayment, + props.payment.externalId, + shouldFetchChallengeSummary, + ]) + + React.useEffect(() => { + if (activeTab === 'external-transaction') { + let ignore = false + setIsExternalTransactionLoading(true) + + fetchPayoutAuditLogs(props.payment.id) + .then(payoutAudit => { + if (!ignore) { + setExternalTransactionAudit(payoutAudit) + } }) .catch(() => { - setAuditLines([]) + if (!ignore) { + setExternalTransactionAudit(undefined) + } }) - } else if (view === 'external_transaction') { - fetchPayoutAuditLogs(props.payment.id) - .then(payoutAudit => { - setExternalTransactionAudit(payoutAudit) + .finally(() => { + if (!ignore) { + setIsExternalTransactionLoading(false) + } }) + + return () => { + ignore = true + } + } + + if (activeTab !== 'audit') { + return undefined } - }, [props.payment.id, view]) + + let ignore = false + setIsAuditLoading(true) + + fetchAuditLogs(props.payment.id) + .then(auditLogs => { + const userIds = auditLogs.map(log => log.userId) + + return getMemberHandle(userIds) + .then((handles: Map) => { + auditLogs.forEach((log: WinningsAudit) => { + log.userId = handles.get(parseInt(log.userId, 10)) ?? log.userId + }) + + return auditLogs + }) + .catch(() => auditLogs) + }) + .then(auditLogs => { + if (!ignore) { + setAuditLines(auditLogs) + } + }) + .catch(() => { + if (!ignore) { + setAuditLines([]) + } + }) + .finally(() => { + if (!ignore) { + setIsAuditLoading(false) + } + }) + + return () => { + ignore = true + } + }, [activeTab, props.payment.id]) const formatAction = (action: string): React.ReactNode => { const fromIndex = action.indexOf('from') @@ -121,6 +258,7 @@ const PaymentView: React.FC = (props: PaymentViewProps) => { const beforeFrom = action.substring(0, fromIndex) const fromValue = action.substring(fromIndex + 5, toIndex) const toValue = action.substring(toIndex + 3) + return ( <> {beforeFrom} @@ -143,359 +281,122 @@ const PaymentView: React.FC = (props: PaymentViewProps) => { : isTaskPayment ? buildWorkAppChallengeUrl(paymentDetails?.taskDetails?.projectId, props.payment.externalId) : `${TOPCODER_URL}/challenges/${props.payment.externalId}` - const projectLink = buildWorkManagerProjectUrl(paymentDetails?.engagementDetails) + + const taskCreatorHandle = resolveTaskCreatorHandle(paymentDetails) + + const budgetApproverHandle = shouldFetchChallengeSummary + ? challengeBudgetApproverHandle + : undefined + + const paymentApproverHandle = resolvePaymentApproverHandle( + paymentDetails, + isTaskPayment, + ) + + const renderTabContent = (): React.ReactNode => { + if (activeTab === 'general') { + return ( + + ) + } + + if (activeTab === 'engagement') { + return ( + + ) + } + + if (activeTab === 'work-log') { + return ( + + ) + } + + if (activeTab === 'task-details') { + return ( + + ) + } + + if (activeTab === 'external-transaction') { + return ( + + ) + } + + return ( + + ) + } return (
-
- {view === 'details' && ( - <> -
- Description - {descriptionLink - ? ( - - {props.payment.description} - - ) - :

{props.payment.description}

} -
-
- Payment ID -

{props.payment.id}

-
-
- Handle -

{props.payment.handle}

-
-
- Type -

{props.payment.type}

-
- -
- {props.isPoints ? 'Points' : 'Payment'} -

- {props.isPoints ? `${props.payment.grossAmountNumber}` : props.payment.grossAmountNumber.toLocaleString(undefined, { - currency: 'USD', - style: 'currency', - })} -

-
- -
- Payment Status -

{props.payment.status}

-
- -
- Release Date -

{props.payment.releaseDateObj.toLocaleDateString()}

-
- - {props.payment.datePaid !== '-' && ( -
- Date Paid -

{props.payment.datePaid}

-
- )} - - {isEngagementPayment && ( -
-

Engagement Details

- {isPaymentDetailsLoading - ?

Loading engagement details...

- : undefined} - {!isPaymentDetailsLoading && paymentDetailsError - ?

{paymentDetailsError}

- : undefined} - {!isPaymentDetailsLoading && !paymentDetailsError && !hasEngagementDetails - ? ( -

- Engagement details are unavailable for this payment. -

- ) - : undefined} - {!isPaymentDetailsLoading && !paymentDetailsError && hasEngagementDetails && ( -
-
- Project Name - {projectLink && paymentDetails?.engagementDetails?.projectName - ? ( - - {paymentDetails.engagementDetails.projectName} - - ) - : ( -

- {formatOptionalText(paymentDetails?.engagementDetails?.projectName)} -

- )} -
-
- Billing Start Date -

- {formatOptionalDate(paymentDetails?.engagementDetails?.billingStartDate)} -

-
-
- Duration -

- {paymentDetails?.engagementDetails?.durationMonths - ? `${paymentDetails.engagementDetails.durationMonths} month${paymentDetails.engagementDetails.durationMonths === 1 ? '' : 's'}` - : '-'} -

-
-
- Rate per Hour -

- {paymentDetails?.engagementDetails?.ratePerHour - ? Number(paymentDetails.engagementDetails.ratePerHour) - .toLocaleString(undefined, { - currency: 'USD', - style: 'currency', - }) - : '-'} -

-
-
- Standard Hours per Week -

- {formatOptionalText(paymentDetails?.engagementDetails?.standardHoursPerWeek)} -

-
-
- Other Remarks -

- {renderOptionalLinkedText(paymentDetails?.engagementDetails?.otherRemarks)} -

-
-
- )} -
- )} - - {isEngagementPayment && ( -
-

Work Log / Manager Inputs

- {isPaymentDetailsLoading - ?

Loading work log...

- : ( -
-
- Hours Worked -

- {formatOptionalText(paymentDetails?.workLog?.hoursWorked)} -

-
-
- Remarks -

- {renderOptionalLinkedText(paymentDetails?.workLog?.remarks)} -

-
-
- Payment Creator -

- {formatOptionalText(paymentDetails?.paymentCreatorHandle)} -

-
-
- )} -
- )} - - {isTaskPayment && ( -
-

Task Details

- {isPaymentDetailsLoading - ?

Loading task details...

- : undefined} - {!isPaymentDetailsLoading && paymentDetailsError - ?

{paymentDetailsError}

- : undefined} - {!isPaymentDetailsLoading && !paymentDetailsError && ( -
-
- Task Creator -

- {formatOptionalText(paymentDetails?.taskDetails?.paymentCreatorHandle)} -

-
-
- Task Description -

- {props.payment.description - ? props.payment.description.substring(0, 500) - : '-'} -

-
-
- Project Name - {buildWorkManagerProjectUrl(paymentDetails?.taskDetails) && paymentDetails?.taskDetails?.projectName - ? ( - - {paymentDetails.taskDetails.projectName} - - ) - : ( -

- {formatOptionalText(paymentDetails?.taskDetails?.projectName)} -

- )} -
-
- Payment Approver -

- {formatOptionalText(paymentDetails?.taskDetails?.paymentApproverHandle)} -

-
-
- )} -
- )} - -
-
- - )} - - {view === 'audit' && ( - <> -
- {auditLines - && auditLines.length > 0 - && auditLines.map(line => ( - - { - new Date(line.createdAt) - .toLocaleString() - } - - ) - } - containerClass={styles.container} - contentClass={styles.content} - > -
-
-

- User: - {line.userId} -

-

- Action: - {formatAction(line.action)} -

-

- Note: - {line.note} -

-
-
-
- ))} - {(auditLines === undefined || auditLines.length === 0) && ( -
-

No audit data available

-
- )} -
-
-
- - )} - - {view === 'external_transaction' && ( - <> -
- {externalTransactionAudit - && externalTransactionAudit.length > 0 - && externalTransactionAudit.map((externalTransaction: PayoutAudit, index: number) => ( - <> - Internal Record} - containerClass={styles.container} - contentClass={styles.content} - > -
-
-

- Provider Used: - {' '} - {externalTransaction.paymentMethodUsed} -

-

- Status: - {externalTransaction.status} -

-

- Processed At: - {externalTransaction.createdAt} -

-

- Total Amount Processed: - {' '} - {externalTransaction.totalNetAmount} -

-
-
-
- External Record} - containerClass={styles.container} - contentClass={styles.content} - > -
-
-
-                                                        {JSON.stringify(
-                                                            externalTransaction.externalTransactionDetails,
-                                                            undefined,
-                                                            2,
-                                                        )}
-                                                    
-
-
-
- - ))} - {externalTransactionAudit === undefined && ( -
-

No external transaction data is available

-
- )} -
-
-
- - )} + + {agreementSummary && ( + + )} + setActiveTab(tabId as PaymentDetailsTabId)} + tabs={tabs} + /> +
+ {renderTabContent()}
+ {props.onClose && ( +
+
+ )}
) } diff --git a/src/apps/wallet-admin/src/lib/components/payment-view/payment-view.utils.agreement.spec.ts b/src/apps/wallet-admin/src/lib/components/payment-view/payment-view.utils.agreement.spec.ts new file mode 100644 index 000000000..1a94bf6b3 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/payment-view/payment-view.utils.agreement.spec.ts @@ -0,0 +1,132 @@ +import { Winning } from '../../models/WinningDetail' + +import { + buildEngagementAgreementSummary, + formatAgreementBreakdown, + formatAgreementExpectedAmount, +} from './payment-view.utils' + +const basePayment: Winning = { + createDate: '01/01/2026', + currency: 'USD', + datePaid: '-', + description: 'Engagement payment', + details: [], + externalId: 'assignment-1', + grossAmount: '$0.00', + grossAmountNumber: 0, + handle: 'member', + id: 'winning-1', + releaseDate: '01/01/2026', + releaseDateObj: new Date('2026-01-01T00:00:00.000Z'), + status: 'Owed', + type: 'engagement payment', +} + +describe('buildEngagementAgreementSummary', () => { + it('matches weekly expected amount', () => { + const summary = buildEngagementAgreementSummary( + { ...basePayment, grossAmountNumber: 2000 }, + { + paymentCycle: 'weekly', + ratePerHour: '50', + standardHoursPerDay: 8, + }, + ) + + expect(summary?.status) + .toBe('match') + expect(summary?.expectedAmount) + .toBe(2000) + expect(summary?.expectedAmountMax) + .toBeUndefined() + expect(formatAgreementBreakdown(summary!)) + .toBe('$50.00 x 8 hours x 5 days') + }) + + it('flags fortnightly payments below expected amount', () => { + const summary = buildEngagementAgreementSummary( + { ...basePayment, grossAmountNumber: 3000 }, + { + paymentCycle: 'fortnightly', + ratePerHour: '50', + standardHoursPerDay: 8, + }, + ) + + expect(summary?.status) + .toBe('under') + expect(summary?.expectedAmount) + .toBe(4000) + }) + + it('flags fortnightly payments above expected amount', () => { + const summary = buildEngagementAgreementSummary( + { ...basePayment, grossAmountNumber: 5000 }, + { + paymentCycle: 'fortnightly', + ratePerHour: '50', + standardHoursPerDay: 8, + }, + ) + + expect(summary?.status) + .toBe('over') + }) + + it('matches monthly payments inside the expected range', () => { + const summary = buildEngagementAgreementSummary( + { ...basePayment, grossAmountNumber: 8500 }, + { + paymentCycle: 'monthly', + ratePerHour: '50', + standardHoursPerDay: 8, + }, + ) + + expect(summary?.status) + .toBe('match') + expect(summary?.expectedAmount) + .toBe(8000) + expect(summary?.expectedAmountMax) + .toBe(9200) + expect(formatAgreementExpectedAmount(summary!)) + .toBe('$8,000.00 - $9,200.00') + expect(formatAgreementBreakdown(summary!)) + .toBe( + '$50.00 x 8 hours x 20 days - $50.00 x 8 hours x 23 days', + ) + }) + + it('flags monthly payments below the expected range', () => { + const summary = buildEngagementAgreementSummary( + { ...basePayment, grossAmountNumber: 7500 }, + { + paymentCycle: 'monthly', + ratePerHour: '50', + standardHoursPerDay: 8, + }, + ) + + expect(summary?.status) + .toBe('under') + expect(summary?.differenceAmount) + .toBe(500) + }) + + it('flags monthly payments above the expected range', () => { + const summary = buildEngagementAgreementSummary( + { ...basePayment, grossAmountNumber: 9500 }, + { + paymentCycle: 'monthly', + ratePerHour: '50', + standardHoursPerDay: 8, + }, + ) + + expect(summary?.status) + .toBe('over') + expect(summary?.differenceAmount) + .toBe(300) + }) +}) diff --git a/src/apps/wallet-admin/src/lib/components/payment-view/payment-view.utils.ts b/src/apps/wallet-admin/src/lib/components/payment-view/payment-view.utils.ts index 8c5b25425..63c910507 100644 --- a/src/apps/wallet-admin/src/lib/components/payment-view/payment-view.utils.ts +++ b/src/apps/wallet-admin/src/lib/components/payment-view/payment-view.utils.ts @@ -2,7 +2,116 @@ import React from 'react' import { EnvironmentConfig } from '~/config' -import { PaymentEngagementDetails } from '../../models/WinningDetail' +import { + PaymentAgreementSummary, + PaymentEngagementDetails, + PaymentWorkLog, + Winning, +} from '../../models/WinningDetail' + +const WEEKLY_PAYMENT_DAYS = 5 +const FORTNIGHTLY_PAYMENT_DAYS = 10 +const MONTHLY_PAYMENT_DAYS_MIN = 20 +const MONTHLY_PAYMENT_DAYS_MAX = 23 +const AGREEMENT_AMOUNT_TOLERANCE = 0.01 + +type EngagementPaymentCycle = 'weekly' | 'fortnightly' | 'monthly' + +function normalizeEngagementPaymentCycle( + paymentCycle?: string, +): EngagementPaymentCycle { + const normalizedCycle = paymentCycle?.trim() + .toLowerCase() + + if (normalizedCycle === 'fortnightly' || normalizedCycle === 'biweekly') { + return 'fortnightly' + } + + if (normalizedCycle === 'monthly') { + return 'monthly' + } + + return 'weekly' +} + +function getPaymentCycleWorkDays(paymentCycle: EngagementPaymentCycle): number { + if (paymentCycle === 'fortnightly') { + return FORTNIGHTLY_PAYMENT_DAYS + } + + if (paymentCycle === 'monthly') { + return MONTHLY_PAYMENT_DAYS_MIN + } + + return WEEKLY_PAYMENT_DAYS +} + +function resolveAgreementStatus( + actualAmount: number, + expectedAmount: number, + expectedAmountMax?: number, +): PaymentAgreementSummary['status'] { + if (expectedAmountMax !== undefined) { + if ( + actualAmount >= expectedAmount - AGREEMENT_AMOUNT_TOLERANCE + && actualAmount <= expectedAmountMax + AGREEMENT_AMOUNT_TOLERANCE + ) { + return 'match' + } + + if (actualAmount < expectedAmount - AGREEMENT_AMOUNT_TOLERANCE) { + return 'under' + } + + return 'over' + } + + const difference = actualAmount - expectedAmount + + if (Math.abs(difference) <= AGREEMENT_AMOUNT_TOLERANCE) { + return 'match' + } + + if (difference < -AGREEMENT_AMOUNT_TOLERANCE) { + return 'under' + } + + return 'over' +} + +function resolveAgreementDifferenceAmount( + actualAmount: number, + expectedAmount: number, + expectedAmountMax: number | undefined, + status: PaymentAgreementSummary['status'], +): number { + if (status === 'match') { + return 0 + } + + if (expectedAmountMax !== undefined) { + if (status === 'under') { + return Number((expectedAmount - actualAmount).toFixed(2)) + } + + return Number((actualAmount - expectedAmountMax).toFixed(2)) + } + + return Math.abs(Number((actualAmount - expectedAmount).toFixed(2))) +} + +function parseRatePerHour(value?: string): number | undefined { + if (!value) { + return undefined + } + + const parsedValue = Number(String(value) + .replace(/[^0-9.-]/g, '')) + + return Number.isFinite(parsedValue) && parsedValue > 0 + ? parsedValue + : undefined +} export function buildWorkManagerAssignmentUrl( engagementDetails?: PaymentEngagementDetails, @@ -151,9 +260,342 @@ export function formatOptionalDate( return value } - return date.toLocaleDateString('en-US', { + return date.toLocaleDateString('en-GB', { + day: 'numeric', + month: 'numeric', + year: 'numeric', + }) +} + +export function formatCurrencyAmount( + amount: number, + currency: string = 'USD', +): string { + return amount.toLocaleString(undefined, { + currency, + style: 'currency', + }) +} + +/** Agreement banner amounts use `$` only (no `US$` locale prefix). */ +export function formatAgreementCurrencyAmount(amount: number): string { + return `$${amount.toLocaleString('en-US', { + maximumFractionDigits: 2, + minimumFractionDigits: 2, + })}` +} + +export interface AuditTimestampParts { + dateLine: string + timeLine: string +} + +export function formatAuditTimestampParts( + value: string, +): AuditTimestampParts | undefined { + const date = new Date(value) + + if (Number.isNaN(date.getTime())) { + return undefined + } + + const dateLine = `${date.toLocaleDateString('en-GB', { day: '2-digit', - month: 'short', + month: '2-digit', year: 'numeric', + })},` + const timeLine = date.toLocaleTimeString('en-US', { + hour: 'numeric', + minute: '2-digit', }) + + return { dateLine, timeLine } +} + +export function formatAuditTimestamp(value: string): string { + const parts = formatAuditTimestampParts(value) + + if (!parts) { + return value + } + + return `${parts.dateLine} ${parts.timeLine}` +} + +export function getEngagementHoursPerDay( + engagementDetails?: PaymentEngagementDetails, +): number | undefined { + if (engagementDetails?.standardHoursPerDay !== undefined) { + return engagementDetails.standardHoursPerDay + } + + if (engagementDetails?.standardHoursPerWeek !== undefined) { + return engagementDetails.standardHoursPerWeek / WEEKLY_PAYMENT_DAYS + } + + return undefined +} + +export function getEngagementWorkDays( + engagementDetails?: PaymentEngagementDetails, +): number { + return getPaymentCycleWorkDays( + normalizeEngagementPaymentCycle(engagementDetails?.paymentCycle), + ) +} + +export function buildEngagementAgreementSummary( + payment: Winning, + engagementDetails?: PaymentEngagementDetails, +): PaymentAgreementSummary | undefined { + const ratePerHour = parseRatePerHour(engagementDetails?.ratePerHour) + const hoursPerDay = getEngagementHoursPerDay(engagementDetails) + + if (!ratePerHour || hoursPerDay === undefined || hoursPerDay <= 0) { + return undefined + } + + const paymentCycle = normalizeEngagementPaymentCycle( + engagementDetails?.paymentCycle, + ) + const workDays = getPaymentCycleWorkDays(paymentCycle) + const expectedAmount = Number( + (ratePerHour * hoursPerDay * workDays).toFixed(2), + ) + const expectedAmountMax = paymentCycle === 'monthly' + ? Number((ratePerHour * hoursPerDay * MONTHLY_PAYMENT_DAYS_MAX).toFixed(2)) + : undefined + const actualAmount = payment.grossAmountNumber + const status = resolveAgreementStatus( + actualAmount, + expectedAmount, + expectedAmountMax, + ) + const differenceAmount = resolveAgreementDifferenceAmount( + actualAmount, + expectedAmount, + expectedAmountMax, + status, + ) + + return { + actualAmount, + differenceAmount, + expectedAmount, + expectedAmountMax, + hoursPerDay, + paymentCycle: formatPaymentCycle(engagementDetails), + ratePerHour, + status, + workDays, + } +} + +function formatAgreementRangePart( + ratePerHour: number, + hoursPerDay: number, + workDays: number, +): string { + const rate = formatAgreementCurrencyAmount(ratePerHour) + + return `${rate} x ${hoursPerDay} hours x ${workDays} days` +} + +export function formatAgreementExpectedAmount( + summary: PaymentAgreementSummary, +): string { + if (summary.expectedAmountMax !== undefined) { + return `${formatAgreementCurrencyAmount(summary.expectedAmount)} - ${ + formatAgreementCurrencyAmount(summary.expectedAmountMax) + }` + } + + return formatAgreementCurrencyAmount(summary.expectedAmount) +} + +export function formatAgreementBreakdown( + summary: PaymentAgreementSummary, +): string { + if (summary.expectedAmountMax !== undefined) { + return `${formatAgreementRangePart( + summary.ratePerHour, + summary.hoursPerDay, + MONTHLY_PAYMENT_DAYS_MIN, + )} - ${formatAgreementRangePart( + summary.ratePerHour, + summary.hoursPerDay, + MONTHLY_PAYMENT_DAYS_MAX, + )}` + } + + return formatAgreementRangePart( + summary.ratePerHour, + summary.hoursPerDay, + summary.workDays, + ) +} + +export function formatAgreementDifferenceLabel( + summary: PaymentAgreementSummary, +): string { + const formattedDifference = `$${summary.differenceAmount.toLocaleString()}` + + if (summary.status === 'under') { + return `${formattedDifference} less` + } + + return `${formattedDifference} more` +} + +export function formatPaymentCycle( + engagementDetails?: PaymentEngagementDetails, +): string { + const cycle = engagementDetails?.paymentCycle?.trim() + + if (!cycle) { + return 'Weekly' + } + + return cycle.charAt(0) + .toUpperCase() + cycle.slice(1) + .toLowerCase() +} + +export function isContestPaymentType(type: string): boolean { + return type.toLowerCase() === 'contest payment' +} + +export function isCopilotPaymentType(type: string): boolean { + return type.toLowerCase() === 'copilot payment' +} + +export function isReviewBoardPaymentType(type: string): boolean { + return type.toLowerCase() === 'review board payment' +} + +/** Challenge payments: 4-column summary (Challenge Creator / Budget Approver) + General Info / Audit */ +export function isChallengePaymentType(type: string): boolean { + return isContestPaymentType(type) + || isCopilotPaymentType(type) + || isReviewBoardPaymentType(type) +} + +export function isEngagementPaymentType(type: string): boolean { + return type.toLowerCase() === 'engagement payment' +} + +export function isTaskPaymentType(type: string): boolean { + return type.toLowerCase() === 'task payment' +} + +export function isTopgearPaymentType(type: string): boolean { + return type.toLowerCase() === 'topgear payment' +} + +export function isTaasPaymentType(type: string): boolean { + return type.toLowerCase() === 'taas payment' +} + +export function usesCompactPaymentSummary(type: string): boolean { + return isTopgearPaymentType(type) || isTaasPaymentType(type) +} + +/** + * Task creator handle for task payments — always from `taskDetails.paymentCreatorHandle`. + * Root-level `paymentCreatorHandle` is a client id and must not be shown. + */ +export function resolveTaskCreatorHandle( + paymentDetails?: { + taskDetails?: { paymentCreatorHandle?: string } + }, +): string | undefined { + return paymentDetails?.taskDetails?.paymentCreatorHandle +} + +/** + * Payment approver handle from finance payment-details (audit trail after approval). + * Engagement payments return it on `engagementDetails`; task payments on `taskDetails`. + */ +export function resolvePaymentApproverHandle( + paymentDetails?: { + engagementDetails?: { paymentApproverHandle?: string } + taskDetails?: { paymentApproverHandle?: string } + }, + isTaskPayment: boolean = false, +): string | undefined { + if (isTaskPayment) { + return paymentDetails?.taskDetails?.paymentApproverHandle + } + + return paymentDetails?.engagementDetails?.paymentApproverHandle +} + +export interface PaymentDetailsSummaryConfig { + approverLabel?: string + columns: 2 | 4 | 5 + creatorLabel?: string + secondaryApproverLabel?: string +} + +export function getPaymentDetailsSummaryConfig(type: string): PaymentDetailsSummaryConfig { + if (usesCompactPaymentSummary(type)) { + return { + columns: 2, + } + } + + if (isChallengePaymentType(type)) { + return { + approverLabel: 'Budget Approver', + columns: 4, + creatorLabel: 'Challenge Creator', + } + } + + if (isTaskPaymentType(type)) { + return { + approverLabel: 'Budget Approver', + columns: 5, + creatorLabel: 'Task Creator', + secondaryApproverLabel: 'Payment Approver', + } + } + + if (isEngagementPaymentType(type)) { + return { + approverLabel: 'Payment Approver', + columns: 4, + creatorLabel: 'Payment Creator', + } + } + + return { + approverLabel: 'Payment Approver', + columns: 4, + creatorLabel: 'Payment Creator', + } +} + +export function resolvePaymentAgreementSummary( + payment: Winning, + paymentDetails?: { + agreementSummary?: PaymentAgreementSummary + engagementDetails?: PaymentEngagementDetails + workLog?: PaymentWorkLog + }, +): PaymentAgreementSummary | undefined { + if (paymentDetails?.agreementSummary) { + return paymentDetails.agreementSummary + } + + return buildEngagementAgreementSummary( + payment, + paymentDetails?.engagementDetails, + ) +} + +export function stripHtml(html: string, maxLength: number = 500): string { + const div = document.createElement('div') + div.innerHTML = html + return (div.textContent ?? div.innerText ?? '').substring(0, maxLength) } diff --git a/src/apps/wallet-admin/src/lib/components/payment-view/tabs/PaymentAuditHistoryTab.tsx b/src/apps/wallet-admin/src/lib/components/payment-view/tabs/PaymentAuditHistoryTab.tsx new file mode 100644 index 000000000..35e168dc9 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/payment-view/tabs/PaymentAuditHistoryTab.tsx @@ -0,0 +1,63 @@ +import { FC, ReactNode } from 'react' +import classNames from 'classnames' + +import { WinningsAudit } from '../../../models/WinningsAudit' +import { formatAuditTimestamp } from '../payment-view.utils' +import styles from '../PaymentView.module.scss' + +interface PaymentAuditHistoryTabProps { + readonly auditLines: WinningsAudit[] + readonly formatAction: (action: string) => ReactNode + readonly isLoading: boolean +} + +const PaymentAuditHistoryTab: FC = ( + props: PaymentAuditHistoryTabProps, +) => { + if (props.isLoading) { + return

Loading audit history...

+ } + + if (props.auditLines.length === 0) { + return

No audit data available

+ } + + return ( +
+
+
Date
+
User
+
Action
+
Note
+
+ {props.auditLines.map((line, index) => ( +
+
+ {formatAuditTimestamp(line.createdAt)} +
+
+ {line.userId} +
+
+ {props.formatAction(line.action)} +
+
+ {line.note || '-'} +
+
+ ))} +
+ ) +} + +export default PaymentAuditHistoryTab diff --git a/src/apps/wallet-admin/src/lib/components/payment-view/tabs/PaymentEngagementDetailsTab.tsx b/src/apps/wallet-admin/src/lib/components/payment-view/tabs/PaymentEngagementDetailsTab.tsx new file mode 100644 index 000000000..15f405932 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/payment-view/tabs/PaymentEngagementDetailsTab.tsx @@ -0,0 +1,114 @@ +import { FC } from 'react' + +import { PaymentEngagementDetails } from '../../../models/WinningDetail' +import { + formatOptionalDate, + formatOptionalText, + formatPaymentCycle, + getEngagementHoursPerDay, + renderOptionalLinkedText, +} from '../payment-view.utils' +import styles from '../PaymentView.module.scss' + +interface PaymentEngagementDetailsTabProps { + readonly engagementDetails?: PaymentEngagementDetails + readonly errorMessage?: string + readonly isLoading: boolean + readonly projectLink?: string +} + +const PaymentEngagementDetailsTab: FC = ( + props: PaymentEngagementDetailsTabProps, +) => { + if (props.isLoading) { + return

Loading engagement details...

+ } + + if (props.errorMessage) { + return

{props.errorMessage}

+ } + + if (!props.engagementDetails) { + return ( +

+ Engagement details are unavailable for this payment. +

+ ) + } + + const hoursPerDay = getEngagementHoursPerDay(props.engagementDetails) + + return ( +
+
+
+ Project Name + {props.projectLink && props.engagementDetails.projectName + ? ( + + {props.engagementDetails.projectName} + + ) + : ( +

+ {formatOptionalText(props.engagementDetails.projectName)} +

+ )} +
+
+ Rate per Hour +

+ {props.engagementDetails.ratePerHour + ? Number(props.engagementDetails.ratePerHour) + .toLocaleString(undefined, { + currency: 'USD', + style: 'currency', + }) + : '-'} +

+
+
+ Duration +

+ {props.engagementDetails.durationMonths + ? `${props.engagementDetails.durationMonths} month${ + props.engagementDetails.durationMonths === 1 ? '' : 's' + }` + : '-'} +

+
+
+ Standard Hours per Day +

+ {formatOptionalText(hoursPerDay)} +

+
+
+ Billing Start Date +

+ {formatOptionalDate(props.engagementDetails.billingStartDate)} +

+
+
+ Payment Cycle +

+ {formatPaymentCycle(props.engagementDetails)} +

+
+
+
+ Other Remarks +

+ {renderOptionalLinkedText(props.engagementDetails.otherRemarks)} +

+
+
+ ) +} + +export default PaymentEngagementDetailsTab diff --git a/src/apps/wallet-admin/src/lib/components/payment-view/tabs/PaymentExternalTransactionTab.tsx b/src/apps/wallet-admin/src/lib/components/payment-view/tabs/PaymentExternalTransactionTab.tsx new file mode 100644 index 000000000..216803281 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/payment-view/tabs/PaymentExternalTransactionTab.tsx @@ -0,0 +1,70 @@ +/* eslint-disable react/no-array-index-key */ +import { FC } from 'react' + +import { Collapsible } from '~/libs/ui' + +import { PayoutAudit } from '../../../models/PayoutAudit' +import styles from '../PaymentView.module.scss' + +interface PaymentExternalTransactionTabProps { + readonly isLoading: boolean + readonly payoutAudits?: PayoutAudit[] +} + +const PaymentExternalTransactionTab: FC = ( + props: PaymentExternalTransactionTabProps, +) => { + if (props.isLoading) { + return

Loading external transaction details...

+ } + + if (!props.payoutAudits || props.payoutAudits.length === 0) { + return

No external transaction data is available

+ } + + return ( +
+ {props.payoutAudits.map((externalTransaction, index) => ( +
+ Internal Record} + > +
+ Provider Used +

{externalTransaction.paymentMethodUsed}

+
+
+ Status +

{externalTransaction.status}

+
+
+ Processed At +

{externalTransaction.createdAt}

+
+
+ Total Amount Processed +

{externalTransaction.totalNetAmount}

+
+
+ External Record} + > +
+                            {JSON.stringify(
+                                externalTransaction.externalTransactionDetails,
+                                undefined,
+                                2,
+                            )}
+                        
+
+
+ ))} +
+ ) +} + +export default PaymentExternalTransactionTab diff --git a/src/apps/wallet-admin/src/lib/components/payment-view/tabs/PaymentGeneralInfoTab.tsx b/src/apps/wallet-admin/src/lib/components/payment-view/tabs/PaymentGeneralInfoTab.tsx new file mode 100644 index 000000000..78e932629 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/payment-view/tabs/PaymentGeneralInfoTab.tsx @@ -0,0 +1,61 @@ +import { FC } from 'react' + +import { Winning } from '../../../models/WinningDetail' +import styles from '../PaymentView.module.scss' + +interface PaymentGeneralInfoTabProps { + readonly createDate: string + readonly description: string + readonly descriptionLink?: string + readonly payment: Winning +} + +const PaymentGeneralInfoTab: FC = (props: PaymentGeneralInfoTabProps) => ( +
+
+ Description + {props.descriptionLink + ? ( + + {props.description} + + ) + :

{props.description}

} +
+
+
+ Payment Status +

{props.payment.status}

+
+
+ Create Date +

{props.createDate}

+
+
+ Payment Type +

{props.payment.type}

+
+
+ Release Date +

{props.payment.releaseDateObj.toLocaleDateString('en-GB')}

+
+
+ Payment ID +

{props.payment.id}

+
+ {props.payment.datePaid !== '-' && ( +
+ Date Paid +

{props.payment.datePaid}

+
+ )} +
+
+) + +export default PaymentGeneralInfoTab diff --git a/src/apps/wallet-admin/src/lib/components/payment-view/tabs/PaymentTaskDetailsTab.tsx b/src/apps/wallet-admin/src/lib/components/payment-view/tabs/PaymentTaskDetailsTab.tsx new file mode 100644 index 000000000..469a8ecec --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/payment-view/tabs/PaymentTaskDetailsTab.tsx @@ -0,0 +1,84 @@ +import { FC } from 'react' + +import { Winning, WinningPaymentDetails } from '../../../models/WinningDetail' +import { + formatOptionalText, + resolvePaymentApproverHandle, + resolveTaskCreatorHandle, + stripHtml, +} from '../payment-view.utils' +import styles from '../PaymentView.module.scss' + +interface PaymentTaskDetailsTabProps { + readonly errorMessage?: string + readonly isLoading: boolean + readonly payment: Winning + readonly paymentDetails?: WinningPaymentDetails + readonly projectLink?: string +} + +const PaymentTaskDetailsTab: FC = ( + props: PaymentTaskDetailsTabProps, +) => { + if (props.isLoading) { + return

Loading task details...

+ } + + if (props.errorMessage) { + return

{props.errorMessage}

+ } + + const taskDescription = props.paymentDetails?.taskDetails?.taskDescription + ?? props.payment.description + const taskCreatorHandle = resolveTaskCreatorHandle(props.paymentDetails) + const paymentApproverHandle = resolvePaymentApproverHandle( + props.paymentDetails, + true, + ) + + return ( +
+
+ Task Creator +

+ {formatOptionalText(taskCreatorHandle)} +

+
+
+ Project Name + {props.projectLink && props.paymentDetails?.taskDetails?.projectName + ? ( + + {props.paymentDetails.taskDetails.projectName} + + ) + : ( +

+ {formatOptionalText(props.paymentDetails?.taskDetails?.projectName)} +

+ )} +
+
+ Task Description +

+ {taskDescription + ? stripHtml(taskDescription) + : '-'} +

+
+
+ Payment Approver +

+ {formatOptionalText(paymentApproverHandle)} +

+
+
+ ) +} + +export default PaymentTaskDetailsTab diff --git a/src/apps/wallet-admin/src/lib/components/payment-view/tabs/PaymentWorkLogTab.tsx b/src/apps/wallet-admin/src/lib/components/payment-view/tabs/PaymentWorkLogTab.tsx new file mode 100644 index 000000000..6284f63d7 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/payment-view/tabs/PaymentWorkLogTab.tsx @@ -0,0 +1,42 @@ +import { FC } from 'react' + +import { PaymentWorkLog } from '../../../models/WinningDetail' +import { formatOptionalText, renderOptionalLinkedText } from '../payment-view.utils' +import styles from '../PaymentView.module.scss' + +interface PaymentWorkLogTabProps { + readonly errorMessage?: string + readonly isLoading: boolean + readonly workLog?: PaymentWorkLog +} + +const PaymentWorkLogTab: FC = (props: PaymentWorkLogTabProps) => { + if (props.isLoading) { + return

Loading work log...

+ } + + if (props.errorMessage) { + return

{props.errorMessage}

+ } + + return ( +
+
+
+ Hours Worked +

+ {formatOptionalText(props.workLog?.hoursWorked)} +

+
+
+ Remarks +

+ {renderOptionalLinkedText(props.workLog?.remarks)} +

+
+
+
+ ) +} + +export default PaymentWorkLogTab diff --git a/src/apps/wallet-admin/src/lib/components/payments-table/PaymentTable.module.scss b/src/apps/wallet-admin/src/lib/components/payments-table/PaymentTable.module.scss index f2bf29c72..c612fbd3e 100644 --- a/src/apps/wallet-admin/src/lib/components/payments-table/PaymentTable.module.scss +++ b/src/apps/wallet-admin/src/lib/components/payments-table/PaymentTable.module.scss @@ -9,19 +9,17 @@ table { width: 100%; - min-width: 980px; + min-width: 1100px; border-collapse: separate; border-spacing: 0; margin: 16px 0; + table-layout: fixed; th, td { text-align: left; padding: 8px; - - &:last-child { - width: 50px; - } + vertical-align: top; } tbody { @@ -45,9 +43,56 @@ table { text-transform: uppercase; &.description { - width: 360px; - max-width: 360px; + width: 32%; + } + + &.descriptionCompact { + width: 22%; } + + &.type { + width: 14%; + } + } + + .description { + overflow-wrap: anywhere; + word-break: break-word; + } + + .descriptionCompact { + overflow-wrap: anywhere; + word-break: break-word; + } + + .handleColumn { + overflow-wrap: anywhere; + width: 11%; + word-break: break-word; + } + + .paymentColumn { + white-space: nowrap; + width: 9%; + } + + .statusColumn { + overflow-wrap: anywhere; + width: 14%; + } + + .dateColumn { + white-space: nowrap; + width: 10%; + } + + .actionsColumn { + width: 96px; + } + + .bulkSelectColumn { + padding-right: 4px; + width: 34px; } tbody tr td:first-child { @@ -62,6 +107,20 @@ table { } +.approverTable { + .handleColumn { + width: 10%; + } + + .description { + width: 26%; + } + + .descriptionCompact { + width: 20%; + } +} + .selected { background-color: #e7f4ff; } @@ -102,10 +161,12 @@ table { } .actionButtons { - padding-left: 8px; display: flex; justify-content: flex-end; align-items: center; + gap: $sp-2; + padding-left: 4px; + white-space: nowrap; } @media (max-width: 768px) { diff --git a/src/apps/wallet-admin/src/lib/components/payments-table/PaymentTable.tsx b/src/apps/wallet-admin/src/lib/components/payments-table/PaymentTable.tsx index 7ed0831c9..9d67acb98 100644 --- a/src/apps/wallet-admin/src/lib/components/payments-table/PaymentTable.tsx +++ b/src/apps/wallet-admin/src/lib/components/payments-table/PaymentTable.tsx @@ -2,6 +2,7 @@ /* eslint-disable react/jsx-no-bind */ /* eslint-disable @typescript-eslint/explicit-function-return-type */ import React, { useEffect, useState } from 'react' +import classNames from 'classnames' import { Button, IconOutline } from '~/libs/ui' @@ -54,6 +55,14 @@ const PaymentsTable: React.FC = (props: PaymentTableProps) => const allVisibleSelected = visibleSelectablePayments.length > 0 && visibleSelectablePayments.every(p => selectedPayments[p.id]) const someVisibleSelected = visibleSelectablePayments.some(p => selectedPayments[p.id]) && !allVisibleSelected + const hasDatePaidValues = props.payments.some(payment => { + const datePaid = payment.datePaid?.trim() + + return !!datePaid && datePaid !== '-' + }) + const isApproverTable = !!props.enableBulkEdit + const useCompactDescription = hasDatePaidValues || isApproverTable + const onToggleSelectAll = (checked: boolean) => { if (checked) { const next: { [paymentId: string]: Winning } = {} @@ -74,11 +83,11 @@ const PaymentsTable: React.FC = (props: PaymentTableProps) => return ( <>
- +
{props.enableBulkEdit && ( - )} - - - - - - - - + + + + + + + + + @@ -105,7 +115,7 @@ const PaymentsTable: React.FC = (props: PaymentTableProps) => className={selectedPayments[payment.id] ? 'selected' : ''} > {props.enableBulkEdit && ( - )} - - - - - - - - + + + + + + + + ))} diff --git a/src/apps/wallet-admin/src/lib/models/WinningDetail.ts b/src/apps/wallet-admin/src/lib/models/WinningDetail.ts index 3a5881f22..6432bff4d 100644 --- a/src/apps/wallet-admin/src/lib/models/WinningDetail.ts +++ b/src/apps/wallet-admin/src/lib/models/WinningDetail.ts @@ -28,9 +28,12 @@ export interface PaymentEngagementDetails { engagementTitle?: string billingStartDate?: string durationMonths?: number + paymentCycle?: string ratePerHour?: string + standardHoursPerDay?: number standardHoursPerWeek?: number otherRemarks?: string + paymentApproverHandle?: string } export interface PaymentWorkLog { @@ -43,9 +46,25 @@ export interface PaymentTaskDetails { projectName?: string paymentApproverHandle?: string paymentCreatorHandle?: string + taskDescription?: string +} + +export type PaymentAgreementStatus = 'match' | 'under' | 'over' + +export interface PaymentAgreementSummary { + status: PaymentAgreementStatus + actualAmount: number + differenceAmount: number + expectedAmount: number + expectedAmountMax?: number + hoursPerDay: number + paymentCycle: string + ratePerHour: number + workDays: number } export interface WinningPaymentDetails { + agreementSummary?: PaymentAgreementSummary engagementDetails?: PaymentEngagementDetails paymentCreatorHandle?: string workLog?: PaymentWorkLog diff --git a/src/apps/wallet-admin/src/lib/services/wallet.spec.ts b/src/apps/wallet-admin/src/lib/services/wallet.spec.ts index 36dfe701f..1c526a313 100644 --- a/src/apps/wallet-admin/src/lib/services/wallet.spec.ts +++ b/src/apps/wallet-admin/src/lib/services/wallet.spec.ts @@ -2,8 +2,14 @@ import { xhrGetAsync } from '~/libs/core' import { Winning } from '../models/WinningDetail' -import { fetchWinningPaymentDetails } from './wallet' +import { fetchChallengePaymentSummary, fetchWinningPaymentDetails } from './wallet' +jest.mock('~/config/environments/default.env', () => ({ + CHALLENGE_API_URL: 'https://example.com/v6/challenges', + CHALLENGE_API_VERSION: 'v5', +}), { + virtual: true, +}) jest.mock('~/config', () => ({ EnvironmentConfig: { API: { @@ -15,6 +21,14 @@ jest.mock('~/config', () => ({ virtual: true, }) jest.mock('~/libs/core', () => ({ + xhrCreateInstance: jest.fn(() => ({ + defaults: { + headers: { + common: {}, + }, + }, + get: jest.fn(), + })), xhrDeleteAsync: jest.fn(), xhrGetAsync: jest.fn(), xhrPatchAsync: jest.fn(), @@ -185,3 +199,53 @@ describe('fetchWinningPaymentDetails', () => { ) }) }) + +describe('fetchChallengePaymentSummary', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + it('maps challenge createdBy and approvalApprovedBy to summary handles', async () => { + const mockedGetAsync = xhrGetAsync as jest.Mock + + mockedGetAsync.mockResolvedValueOnce({ + approvalApprovedBy: 'kartik', + createdBy: 'wendell', + }) + + const result = await fetchChallengePaymentSummary('challenge-1') + + expect(result) + .toEqual({ + budgetApproverHandle: 'kartik', + creatorHandle: 'wendell', + paymentApproverHandle: 'kartik', + }) + expect(mockedGetAsync) + .toHaveBeenCalledWith( + 'https://example.com/v6/challenges/challenge-1', + expect.any(Object), + ) + }) + + it('resolves numeric user ids through the member API', async () => { + const mockedGetAsync = xhrGetAsync as jest.Mock + + mockedGetAsync + .mockResolvedValueOnce({ + approvalApprovedBy: '2002', + createdBy: '1001', + }) + .mockResolvedValueOnce([{ handle: 'wendell', userId: 1001 }]) + .mockResolvedValueOnce([{ handle: 'kartik', userId: 2002 }]) + + const result = await fetchChallengePaymentSummary('challenge-1') + + expect(result) + .toEqual({ + budgetApproverHandle: 'kartik', + creatorHandle: 'wendell', + paymentApproverHandle: 'kartik', + }) + }) +}) diff --git a/src/apps/wallet-admin/src/lib/services/wallet.ts b/src/apps/wallet-admin/src/lib/services/wallet.ts index 78445ebae..e93833aa7 100644 --- a/src/apps/wallet-admin/src/lib/services/wallet.ts +++ b/src/apps/wallet-admin/src/lib/services/wallet.ts @@ -1,7 +1,15 @@ /* eslint-disable camelcase */ import { EnvironmentConfig } from '~/config' -import { xhrDeleteAsync, xhrGetAsync, xhrPatchAsync, xhrPostAsync } from '~/libs/core' +import { CHALLENGE_API_URL, CHALLENGE_API_VERSION } from '~/config/environments/default.env' +import { + xhrCreateInstance, + xhrDeleteAsync, + xhrGetAsync, + xhrPatchAsync, + xhrPostAsync, +} from '~/libs/core' import { postAsyncWithBlobHandling } from '~/libs/core/lib/xhr/xhr-functions/xhr.functions' +import { fetchChallenge } from '~/apps/work/src/lib/services/challenges.service' import { WalletDetails } from '../models/WalletDetails' import { @@ -20,6 +28,23 @@ import ApiResponse from '../models/ApiResponse' const baseUrl = `${EnvironmentConfig.TC_FINANCE_API}` const memberApiBaseUrl = `${EnvironmentConfig.API.V6}/members` const engagementsApiBaseUrl = `${EnvironmentConfig.API.V6}/engagements/engagements` +const challengeApiBaseUrl = CHALLENGE_API_URL.replace(/\/$/, '') +const challengeApiXhr = xhrCreateInstance() + +if (CHALLENGE_API_VERSION) { + challengeApiXhr.defaults.headers.common['app-version'] = CHALLENGE_API_VERSION +} + +interface ChallengePaymentSummaryResponse { + approvalApprovedBy?: string + createdBy?: string +} + +export interface ChallengePaymentSummary { + budgetApproverHandle?: string + creatorHandle?: string + paymentApproverHandle?: string +} interface EngagementAssignmentContextResponse { assignmentId: string @@ -27,9 +52,11 @@ interface EngagementAssignmentContextResponse { engagementId: string engagementTitle: string otherRemarks?: string | null + paymentCycle?: string | null projectId: string projectName?: string ratePerHour?: string | null + standardHoursPerDay?: number | null standardHoursPerWeek?: number | null startDate?: string | null } @@ -40,7 +67,9 @@ interface EngagementAssignmentResponse { memberHandle?: string | null memberId?: number | string | null otherRemarks?: string | null + paymentCycle?: string | null ratePerHour?: string | null + standardHoursPerDay?: number | string | null standardHoursPerWeek?: number | string | null startDate?: string | null } @@ -103,9 +132,45 @@ function normalizeOptionalNumber(value: unknown): number | undefined { * @remarks This keeps the finance payment-details response contract stable even * when wallet-admin has to recover missing engagement metadata on the client. */ +// eslint-disable-next-line complexity +function mergeEngagementDetails( + primary?: PaymentEngagementDetails, + fallback?: PaymentEngagementDetails, +): PaymentEngagementDetails | undefined { + if (!primary && !fallback) { + return undefined + } + + return { + ...fallback, + ...primary, + assignmentId: primary?.assignmentId ?? fallback?.assignmentId, + billingStartDate: primary?.billingStartDate ?? fallback?.billingStartDate, + durationMonths: primary?.durationMonths ?? fallback?.durationMonths, + engagementId: primary?.engagementId ?? fallback?.engagementId, + engagementTitle: primary?.engagementTitle ?? fallback?.engagementTitle, + otherRemarks: primary?.otherRemarks ?? fallback?.otherRemarks, + paymentApproverHandle: + primary?.paymentApproverHandle ?? fallback?.paymentApproverHandle, + paymentCycle: primary?.paymentCycle ?? fallback?.paymentCycle, + projectId: primary?.projectId ?? fallback?.projectId, + projectName: primary?.projectName ?? fallback?.projectName, + ratePerHour: primary?.ratePerHour ?? fallback?.ratePerHour, + standardHoursPerDay: + primary?.standardHoursPerDay ?? fallback?.standardHoursPerDay, + standardHoursPerWeek: + primary?.standardHoursPerWeek ?? fallback?.standardHoursPerWeek, + } +} + function mapAssignmentContextToEngagementDetails( context: EngagementAssignmentContextResponse, ): PaymentEngagementDetails { + const standardHoursPerDay = normalizeOptionalNumber(context.standardHoursPerDay) + ?? (normalizeOptionalNumber(context.standardHoursPerWeek) !== undefined + ? Number((Number(context.standardHoursPerWeek) / 5).toFixed(2)) + : undefined) + return { assignmentId: normalizeOptionalString(context.assignmentId), billingStartDate: normalizeOptionalString(context.startDate), @@ -113,9 +178,11 @@ function mapAssignmentContextToEngagementDetails( engagementId: normalizeOptionalString(context.engagementId), engagementTitle: normalizeOptionalString(context.engagementTitle), otherRemarks: normalizeOptionalString(context.otherRemarks), + paymentCycle: normalizeOptionalString(context.paymentCycle) || 'WEEKLY', projectId: normalizeOptionalString(context.projectId), projectName: normalizeOptionalString(context.projectName), ratePerHour: normalizeOptionalString(context.ratePerHour), + standardHoursPerDay, standardHoursPerWeek: normalizeOptionalNumber(context.standardHoursPerWeek), } } @@ -198,6 +265,10 @@ function buildEngagementDetailsFromEngagement( } const assignment = findMatchingEngagementAssignment(engagement.assignments, winning) + const standardHoursPerDay = normalizeOptionalNumber(assignment?.standardHoursPerDay) + ?? (normalizeOptionalNumber(assignment?.standardHoursPerWeek) !== undefined + ? Number((Number(assignment?.standardHoursPerWeek) / 5).toFixed(2)) + : undefined) return { assignmentId: normalizeOptionalString(assignment?.id) || normalizeOptionalString(winning.assignmentId), @@ -206,6 +277,7 @@ function buildEngagementDetailsFromEngagement( engagementId, engagementTitle: normalizeOptionalString(engagement.title), otherRemarks: normalizeOptionalString(assignment?.otherRemarks), + paymentCycle: normalizeOptionalString(assignment?.paymentCycle) || 'WEEKLY', projectId: normalizeOptionalString(engagement.projectId) || normalizeOptionalString(engagement.project?.id), @@ -213,6 +285,7 @@ function buildEngagementDetailsFromEngagement( normalizeOptionalString(engagement.projectName) || normalizeOptionalString(engagement.project?.name), ratePerHour: normalizeOptionalString(assignment?.ratePerHour), + standardHoursPerDay, standardHoursPerWeek: normalizeOptionalNumber(assignment?.standardHoursPerWeek), } } @@ -266,6 +339,25 @@ export async function fetchPayoutAuditLogs(paymentId: string): Promise { + if (!winning.externalId || !paymentDetails.taskDetails) { + return + } + + try { + const challenge = await fetchChallenge(winning.externalId) + if (challenge.description) { + paymentDetails.taskDetails.taskDescription = challenge.description + } + } catch { + // Fall through — keep whatever description the finance API returned + } +} + export async function fetchWinningPaymentDetails( winning: Winning, ): Promise { @@ -279,10 +371,17 @@ export async function fetchWinningPaymentDetails( const paymentDetails = response.data || {} - if ( - winning.type.toLowerCase() !== 'engagement payment' - || paymentDetails.engagementDetails - ) { + if (winning.type.toLowerCase() !== 'engagement payment') { + if (winning.type.toLowerCase() === 'task payment') { + await enrichTaskDescription(winning, paymentDetails) + } + + return paymentDetails + } + + let engagementDetails: PaymentEngagementDetails | undefined = paymentDetails.engagementDetails + + if (engagementDetails?.ratePerHour) { return paymentDetails } @@ -296,37 +395,40 @@ export async function fetchWinningPaymentDetails( `${engagementsApiBaseUrl}/assignments/${assignmentLookupId}/context`, ) - return { - ...paymentDetails, - engagementDetails: mapAssignmentContextToEngagementDetails(assignmentContext), - } + engagementDetails = mergeEngagementDetails( + engagementDetails, + mapAssignmentContextToEngagementDetails(assignmentContext), + ) } catch {} } - const engagementId = normalizeOptionalString(winning.externalId) - - if (!engagementId) { - return paymentDetails + if (!engagementDetails?.ratePerHour) { + const engagementId = normalizeOptionalString(winning.externalId) + + if (engagementId) { + try { + const engagement = await xhrGetAsync( + `${engagementsApiBaseUrl}/${engagementId}`, + ) + const fallbackEngagementDetails = buildEngagementDetailsFromEngagement( + engagement, + winning, + ) + + engagementDetails = mergeEngagementDetails( + engagementDetails, + fallbackEngagementDetails, + ) + } catch {} + } } - try { - const engagement = await xhrGetAsync( - `${engagementsApiBaseUrl}/${engagementId}`, - ) - const engagementDetails = buildEngagementDetailsFromEngagement( - engagement, - winning, - ) - - return engagementDetails - ? { - ...paymentDetails, - engagementDetails, - } - : paymentDetails - } catch { - return paymentDetails - } + return engagementDetails + ? { + ...paymentDetails, + engagementDetails, + } + : paymentDetails } export async function editPayment(updates: { @@ -360,10 +462,16 @@ export async function exportSearchResults( const filteredFilters: Record = {} for (const key in filters) { - if (['categories'].includes(key)) { + if (['categories', 'winnerIds'].includes(key)) { filteredFilters[key] = filters[key] } else if (filters[key].length > 0 && key !== 'pageSize') { - filteredFilters[key] = filters[key][0] + if (key === 'status' && filters[key].length > 1) { + filteredFilters[key] = filters[key] + } else if (key === 'status') { + filteredFilters[key] = filters[key][0] + } else { + filteredFilters[key] = filters[key][0] + } } } @@ -402,10 +510,16 @@ export async function fetchWinnings( const filteredFilters: Record = {} for (const key in filters) { - if (['categories'].includes(key)) { + if (['categories', 'winnerIds'].includes(key)) { filteredFilters[key] = filters[key] } else if (filters[key].length > 0 && key !== 'pageSize') { - filteredFilters[key] = filters[key][0] + if (key === 'status' && filters[key].length > 1) { + filteredFilters[key] = filters[key] + } else if (key === 'status') { + filteredFilters[key] = filters[key][0] + } else { + filteredFilters[key] = filters[key][0] + } } } @@ -521,6 +635,61 @@ export async function removePaymentProvider(type: string): Promise { + if (/^\d+$/.test(userRef)) { + const handleMap = await getMemberHandle([userRef]) + + return handleMap.get(parseInt(userRef, 10)) ?? userRef + } + + return userRef +} + +/** + * Loads challenge creator and budget approver handles for wallet-admin challenge + * payments from challenge-api. + * + * @param challengeId challenge id stored on the winning `externalId`. + * @returns `creatorHandle` from `createdBy`, `budgetApproverHandle` and + * `paymentApproverHandle` from `approvalApprovedBy`, each resolved to a member + * handle when needed. + * @remarks Challenge payments should not use finance `paymentCreatorHandle` + * because finance can return a client id instead of the creator handle. + */ +export async function fetchChallengePaymentSummary( + challengeId: string, +): Promise { + const normalizedChallengeId = normalizeOptionalString(challengeId) + + if (!normalizedChallengeId) { + return {} + } + + try { + const challenge = await xhrGetAsync( + `${challengeApiBaseUrl}/${normalizedChallengeId}`, + challengeApiXhr, + ) + const createdBy = normalizeOptionalString(challenge.createdBy) + const approvalApprovedBy = normalizeOptionalString(challenge.approvalApprovedBy) + + const [creatorHandle, approvalApproverHandle] = await Promise.all([ + createdBy ? resolveUserRefAsMemberHandle(createdBy) : Promise.resolve(undefined), + approvalApprovedBy + ? resolveUserRefAsMemberHandle(approvalApprovedBy) + : Promise.resolve(undefined), + ]) + + return { + budgetApproverHandle: approvalApproverHandle, + creatorHandle, + paymentApproverHandle: approvalApproverHandle, + } + } catch { + return {} + } +} + export async function getMemberHandle(userIds: string[]): Promise> { const BATCH_SIZE = 50 diff --git a/src/apps/wallet/src/home/tabs/winnings/PaymentsListView.tsx b/src/apps/wallet/src/home/tabs/winnings/PaymentsListView.tsx index 850d83202..a1ef69dcc 100644 --- a/src/apps/wallet/src/home/tabs/winnings/PaymentsListView.tsx +++ b/src/apps/wallet/src/home/tabs/winnings/PaymentsListView.tsx @@ -4,6 +4,7 @@ import React, { FC, useCallback, useEffect } from 'react' import { Collapsible, LoadingCircles } from '~/libs/ui' import { UserProfile } from '~/libs/core' +import { toPaymentTypeCategories } from '~/libs/shared/lib/utils/payment-type-filter.utils' import { getPayments } from '../../../lib/services/wallet' import { Winning, WinningDetail } from '../../../lib/models/WinningDetail' @@ -38,12 +39,17 @@ function formatIOSDateString(iosDateString: string): string { return date.toLocaleDateString('en-GB', options) } +const taskPaymentCategory = 'TASK_PAYMENT' +const engagementPaymentCategory = 'ENGAGEMENT_PAYMENT' + +function isTopcoderTaskOrEngagementPayment(category: string): boolean { + return category === taskPaymentCategory || category === engagementPaymentCategory +} + function formatStatus(status: string): string { switch (status) { case 'ON_HOLD': return 'On Hold' - case 'OWED': - return 'Available' case 'PROCESSING': return 'Processing' case 'PAID': @@ -59,6 +65,22 @@ function formatStatus(status: string): string { } } +function formatMemberPaymentStatus( + status: string, + category: string, + releaseDate: string, +): string { + if (status === 'ON_HOLD_ADMIN' && isTopcoderTaskOrEngagementPayment(category)) { + return 'Created' + } + + if (status === 'OWED') { + return new Date(releaseDate) <= new Date() ? 'Available' : 'Owed' + } + + return formatStatus(status) +} + function isIdVerificationComplete(paymentStatus?: WinningDetail['paymentStatus']): boolean { if (!paymentStatus) { return false @@ -151,7 +173,11 @@ const PaymentsListView: FC = (props: PaymentsListViewProp ? 'On Hold (ID Verification)' : 'On Hold' ) - : formatStatus(payment.details[0].status) + : formatMemberPaymentStatus( + payment.details[0].status, + payment.category, + payment.releaseDate, + ) ), type: payment.category.replaceAll('_', ' ') .toLowerCase(), @@ -163,12 +189,22 @@ const PaymentsListView: FC = (props: PaymentsListViewProp const fetchWinnings = useCallback(async () => { setIsLoading(true) try { - const payments = await getPayments(props.profile.userId.toString(), pagination.pageSize, (pagination.currentPage - 1) * pagination.pageSize, filters) + const apiFilters = { ...filters } + if (apiFilters.category?.length) { + apiFilters.category = toPaymentTypeCategories(apiFilters.category) + } + + const payments = await getPayments( + props.profile.userId.toString(), + pagination.pageSize, + (pagination.currentPage - 1) * pagination.pageSize, + apiFilters, + ) const winningsData = convertToWinnings(payments.winnings) setWinnings(winningsData) setPagination(payments.pagination) - } catch (apiError) { - console.error('Failed to fetch winnings:', apiError) + } catch { + setWinnings([]) } finally { setIsLoading(false) } @@ -221,28 +257,30 @@ const PaymentsListView: FC = (props: PaymentsListViewProp type: 'dropdown', }, { + displayValueInTrigger: true, + key: 'category', label: 'Type', options: [ { label: 'Task Payment', - value: 'TASK_PAYMENT', + value: 'Task', }, { label: 'Contest Payment', - value: 'CONTEST_PAYMENT', + value: 'Contest', }, { label: 'Copilot Payment', - value: 'COPILOT_PAYMENT', + value: 'Copilot', }, { label: 'Review Board Payment', - value: 'REVIEW_BOARD_PAYMENT', + value: 'Review Board', }, { label: 'Engagement Payment', - value: 'ENGAGEMENT_PAYMENT', + value: 'Engagement', }, { label: 'All', @@ -256,7 +294,7 @@ const PaymentsListView: FC = (props: PaymentsListViewProp label: 'Status', options: [ { - label: 'Available', + label: 'Owed', value: 'OWED', }, { diff --git a/src/apps/wallet/src/lib/components/filter-bar/FilterBar.tsx b/src/apps/wallet/src/lib/components/filter-bar/FilterBar.tsx index b82ae5a5c..035946632 100644 --- a/src/apps/wallet/src/lib/components/filter-bar/FilterBar.tsx +++ b/src/apps/wallet/src/lib/components/filter-bar/FilterBar.tsx @@ -15,6 +15,7 @@ type Filter = { key: string label: string type: 'input' | 'dropdown' | 'member_autocomplete' + displayValueInTrigger?: boolean options?: FilterOptions[] } @@ -31,7 +32,7 @@ const FilterBar: React.FC = (props: FilterBarProps) => { ) { setSelectedValue(new Map(selectedValue.set(filter.key, event.target.value))) props.onFilterChange(filter.key, [event.target.value]) @@ -39,6 +40,7 @@ const FilterBar: React.FC = (props: FilterBarProps) => { name={filter.key} label={filter.label} dirty + displayValueInTrigger={filter.displayValueInTrigger} placeholder={filter.label} /> ) diff --git a/src/apps/work/src/lib/components/AcceptApplicationModal/AcceptApplicationModal.tsx b/src/apps/work/src/lib/components/AcceptApplicationModal/AcceptApplicationModal.tsx index b5f76f83c..b1496262e 100644 --- a/src/apps/work/src/lib/components/AcceptApplicationModal/AcceptApplicationModal.tsx +++ b/src/apps/work/src/lib/components/AcceptApplicationModal/AcceptApplicationModal.tsx @@ -33,8 +33,10 @@ export interface AcceptApplicationFormData { agreementRate: string durationMonths: number otherRemarks?: string + paymentCycle: string ratePerHour: string startDate: string + standardHoursPerDay: number standardHoursPerWeek: number } @@ -48,9 +50,10 @@ interface AcceptApplicationModalProps { interface ValidationErrors { durationMonths?: string + paymentCycle?: string ratePerHour?: string startDate?: string - standardHoursPerWeek?: string + standardHoursPerDay?: string } const AcceptApplicationModal: FC = ( @@ -59,9 +62,10 @@ const AcceptApplicationModal: FC = ( const [durationMonths, setDurationMonths] = useState('') const [errors, setErrors] = useState({}) const [otherRemarks, setOtherRemarks] = useState('') + const [paymentCycle, setPaymentCycle] = useState('WEEKLY') const [ratePerHour, setRatePerHour] = useState('') const [startDate, setStartDate] = useState() - const [standardHoursPerWeek, setStandardHoursPerWeek] = useState('') + const [standardHoursPerDay, setStandardHoursPerDay] = useState('') const isSubmitting = props.isSubmitting === true @@ -72,17 +76,29 @@ const AcceptApplicationModal: FC = ( [], ) const agreementRate = useMemo( - () => calculateAssignmentRatePerWeek(ratePerHour, standardHoursPerWeek), - [ratePerHour, standardHoursPerWeek], + () => { + const parsedStandardHoursPerDay = toPositiveNumberWithMaxDecimalPlaces( + standardHoursPerDay, + 2, + ) + + if (parsedStandardHoursPerDay === undefined) { + return '' + } + + return calculateAssignmentRatePerWeek(ratePerHour, parsedStandardHoursPerDay * 5) + }, + [ratePerHour, standardHoursPerDay], ) const resetState = useCallback((): void => { setDurationMonths('') setErrors({}) setOtherRemarks('') + setPaymentCycle('WEEKLY') setRatePerHour('') setStartDate(undefined) - setStandardHoursPerWeek('') + setStandardHoursPerDay('') }, []) const handleCancel = useCallback((): void => { @@ -94,10 +110,13 @@ const AcceptApplicationModal: FC = ( const nextErrors: ValidationErrors = {} const parsedDurationMonths = toPositiveInteger(durationMonths) const parsedRatePerHour = toPositiveNumber(ratePerHour) - const parsedStandardHoursPerWeek = toPositiveNumberWithMaxDecimalPlaces( - standardHoursPerWeek, + const parsedStandardHoursPerDay = toPositiveNumberWithMaxDecimalPlaces( + standardHoursPerDay, 2, ) + const normalizedPaymentCycle = String(paymentCycle || 'WEEKLY') + .trim() + .toUpperCase() if (!startDate) { nextErrors.startDate = 'Billing start date is required.' @@ -111,11 +130,15 @@ const AcceptApplicationModal: FC = ( nextErrors.ratePerHour = 'Rate per hour must be a positive number.' } - if (parsedStandardHoursPerWeek === undefined) { - nextErrors.standardHoursPerWeek = 'Standard hours per week must be a ' + if (parsedStandardHoursPerDay === undefined) { + nextErrors.standardHoursPerDay = 'Standard hours per day must be a ' + 'positive number with up to 2 decimal places.' } + if (!['WEEKLY', 'FORTNIGHTLY', 'MONTHLY'].includes(normalizedPaymentCycle)) { + nextErrors.paymentCycle = 'Payment cycle must be Weekly, Fortnightly, or Monthly.' + } + if (Object.keys(nextErrors).length > 0) { setErrors(nextErrors) return @@ -125,7 +148,7 @@ const AcceptApplicationModal: FC = ( !startDate || parsedDurationMonths === undefined || parsedRatePerHour === undefined - || parsedStandardHoursPerWeek === undefined + || parsedStandardHoursPerDay === undefined ) { return } @@ -134,8 +157,10 @@ const AcceptApplicationModal: FC = ( agreementRate, durationMonths: parsedDurationMonths, otherRemarks: otherRemarks.trim() || undefined, + paymentCycle: normalizedPaymentCycle, ratePerHour: parsedRatePerHour.toString(), - standardHoursPerWeek: parsedStandardHoursPerWeek, + standardHoursPerDay: parsedStandardHoursPerDay, + standardHoursPerWeek: Number((parsedStandardHoursPerDay * 5).toFixed(2)), startDate: serializeTentativeAssignmentDate(startDate), }) @@ -144,10 +169,11 @@ const AcceptApplicationModal: FC = ( agreementRate, durationMonths, otherRemarks, + paymentCycle, props, ratePerHour, resetState, - standardHoursPerWeek, + standardHoursPerDay, startDate, ]) @@ -257,43 +283,52 @@ const AcceptApplicationModal: FC = (
{ - setStandardHoursPerWeek( + setStandardHoursPerDay( sanitizePositiveNumericInput(event.target.value, 2), ) setErrors(previous => ({ ...previous, - standardHoursPerWeek: undefined, + standardHoursPerDay: undefined, })) }} pattern='[0-9.]*' type='text' - value={standardHoursPerWeek} + value={standardHoursPerDay} /> - {errors.standardHoursPerWeek - ?

{errors.standardHoursPerWeek}

+ {errors.standardHoursPerDay + ?

{errors.standardHoursPerDay}

: undefined}
-
diff --git a/src/apps/work/src/lib/components/BillingAccountLineItemsModal/BillingAccountLineItemsModal.spec.tsx b/src/apps/work/src/lib/components/BillingAccountLineItemsModal/BillingAccountLineItemsModal.spec.tsx index faa3114c1..8cbd9e45e 100644 --- a/src/apps/work/src/lib/components/BillingAccountLineItemsModal/BillingAccountLineItemsModal.spec.tsx +++ b/src/apps/work/src/lib/components/BillingAccountLineItemsModal/BillingAccountLineItemsModal.spec.tsx @@ -177,6 +177,33 @@ describe('BillingAccountLineItemsModal', () => { .toBeNull() }) + it('uses billing-account markup for locked challenge fees when challenge markup cannot be loaded', () => { + mockedFetchChallenge.mockRejectedValueOnce(new Error('Forbidden')) + + renderModal({ + ...baseBillingAccountDetails, + lockedAmounts: [ + { + amount: '28.6', + date: '2026-05-12T00:00:00.000Z', + externalId: '5fdf48d2-811f-4914-b713-9e5f423c907d', + externalName: 'Copilot and Admin with reviews', + externalType: 'CHALLENGE', + }, + ], + lockedBudget: 28.6, + markup: 0.33, + totalBudgetRemaining: 971.4, + }) + + expect(screen.getAllByText('$28.60')) + .toHaveLength(2) + expect(screen.getByText('$9.44')) + .toBeTruthy() + expect(screen.queryByText('-')) + .toBeNull() + }) + it('removes challenge markup once from consumed challenge charges before showing member payments', async () => { renderModal({ ...baseBillingAccountDetails, diff --git a/src/apps/work/src/lib/components/BillingAccountLineItemsModal/BillingAccountLineItemsModal.tsx b/src/apps/work/src/lib/components/BillingAccountLineItemsModal/BillingAccountLineItemsModal.tsx index 8849d0d81..3aecd2cde 100644 --- a/src/apps/work/src/lib/components/BillingAccountLineItemsModal/BillingAccountLineItemsModal.tsx +++ b/src/apps/work/src/lib/components/BillingAccountLineItemsModal/BillingAccountLineItemsModal.tsx @@ -272,10 +272,11 @@ async function fetchChallengeDetailsById( * @param item Billing-account line item being displayed. * @param billingAccountDetails Billing account detail payload. * @param challengeDetailsById Hydrated challenge details, or `undefined` while loading. - * @returns Challenge-specific markup, billing-account fallback for legacy - * rows, or `undefined` when the challenge row is still being hydrated. + * @returns Challenge-specific markup when available, otherwise the billing-account markup fallback. * @remarks Canonical challenge rows use challenge billing markup so `0` markup - * challenges do not inherit the billing account default fee. + * challenges do not inherit the billing account default fee once hydrated. The + * billing-account fallback keeps locked rows from showing a blank fee when + * challenge hydration is unavailable. */ function getLineItemChallengeMarkup( item: BillingAccountLineItem, @@ -291,6 +292,7 @@ function getLineItemChallengeMarkup( } return challengeDetailsById?.get(challengeId)?.billing?.markup + ?? billingAccountDetails.markup } /** diff --git a/src/apps/work/src/lib/components/PaymentFormModal/PaymentFormModal.tsx b/src/apps/work/src/lib/components/PaymentFormModal/PaymentFormModal.tsx index 2cec47e65..24042982f 100644 --- a/src/apps/work/src/lib/components/PaymentFormModal/PaymentFormModal.tsx +++ b/src/apps/work/src/lib/components/PaymentFormModal/PaymentFormModal.tsx @@ -25,8 +25,10 @@ import { } from '../../models' import { calculatePaymentAmount, + getAssignmentPaymentCycle, getAssignmentRatePerHour, - getAssignmentStandardHoursPerWeek, + getAssignmentStandardHoursPerDay, + getExpectedHoursLabel, } from '../../utils' import { calculatePaymentChallengeFee, @@ -55,12 +57,11 @@ interface PaymentFormModalProps { } interface ValidationErrors { + fromDate?: string hoursWorked?: string - weekEnding?: string + toDate?: string } -const SATURDAY_DAY_INDEX = 6 - function normalizeTitleSegment(value?: string): string { return String(value || '') .trim() @@ -68,48 +69,34 @@ function normalizeTitleSegment(value?: string): string { function formatTitleDate(value: Date): string { return value.toLocaleDateString('en-US', { - day: '2-digit', + day: 'numeric', month: 'short', year: 'numeric', }) } -function formatWeekEndingTitle(value?: Date | null): string { - if (!value) { +function formatWorkPeriodTitle(fromDate?: Date | null, toDate?: Date | null): string { + if (!fromDate || !toDate) { return '' } - return `Week Ending: ${formatTitleDate(value)}` + return `Work Period ${formatTitleDate(fromDate)} - ${formatTitleDate(toDate)}` } function buildPaymentTitle( projectName?: string, engagementName?: string, - weekEndingTitle?: string, + workPeriodTitle?: string, ): string { return [ normalizeTitleSegment(projectName), normalizeTitleSegment(engagementName), - normalizeTitleSegment(weekEndingTitle), + normalizeTitleSegment(workPeriodTitle), ] .filter(Boolean) .join(' - ') } -function getDefaultWeekEndingDate(): Date { - const defaultDate = new Date() - defaultDate.setHours(0, 0, 0, 0) - - const daysUntilSaturday = (SATURDAY_DAY_INDEX - defaultDate.getDay() + 7) % 7 - defaultDate.setDate(defaultDate.getDate() + daysUntilSaturday) - - return defaultDate -} - -function isSaturday(value?: Date | null): boolean { - return Boolean(value) && value?.getDay() === SATURDAY_DAY_INDEX -} - function normalizePositiveValue(value: unknown): string { if (value === null || value === undefined || value === '') { return '' @@ -125,12 +112,12 @@ function normalizePositiveValue(value: unknown): string { .toString() } -const WeekEndingInput = forwardRef>( +const DateInput = forwardRef>( (props, ref): JSX.Element => ( = ( props: PaymentFormModalProps, @@ -146,9 +133,10 @@ const PaymentFormModal: FC = ( const isSubmitting = props.isSubmitting === true const [errors, setErrors] = useState({}) + const [fromDate, setFromDate] = useState(undefined) const [hoursWorked, setHoursWorked] = useState('') const [remarks, setRemarks] = useState('') - const [weekEndingDate, setWeekEndingDate] = useState(() => getDefaultWeekEndingDate()) + const [toDate, setToDate] = useState(undefined) const ratePerHour = useMemo( () => getAssignmentRatePerHour(props.member || {}), @@ -164,26 +152,43 @@ const PaymentFormModal: FC = ( : undefined), [amount, props.billingAccountMarkup], ) + const paymentCycle = useMemo( + () => getAssignmentPaymentCycle(props.member || {}), + [props.member], + ) + const standardHoursPerDay = useMemo( + () => getAssignmentStandardHoursPerDay(props.member || {}), + [props.member], + ) + const expectedHoursLabel = useMemo( + () => getExpectedHoursLabel(props.member || {}), + [props.member], + ) const paymentTitle = useMemo( () => { - if (!isSaturday(weekEndingDate)) { + if (!fromDate || !toDate) { + return '' + } + + if (fromDate.getTime() > toDate.getTime()) { return '' } return buildPaymentTitle( props.projectName, props.engagementName, - formatWeekEndingTitle(weekEndingDate), + formatWorkPeriodTitle(fromDate, toDate), ) }, - [props.engagementName, props.projectName, weekEndingDate], + [fromDate, props.engagementName, props.projectName, toDate], ) const resetState = useCallback((): void => { setErrors({}) - setHoursWorked(normalizePositiveValue(getAssignmentStandardHoursPerWeek(props.member || {}))) + setFromDate(undefined) + setHoursWorked('') setRemarks('') - setWeekEndingDate(getDefaultWeekEndingDate()) + setToDate(undefined) }, [props.member]) useEffect(() => { @@ -204,12 +209,20 @@ const PaymentFormModal: FC = ( const nextErrors: ValidationErrors = {} const parsedHoursWorked = Number(hoursWorked) - if (!weekEndingDate) { - nextErrors.weekEnding = 'Week ending date is required.' - } else if (!isSaturday(weekEndingDate)) { - nextErrors.weekEnding = 'Week ending date must be a Saturday.' - } else if (!paymentTitle.trim()) { - nextErrors.weekEnding = 'Payment title cannot be generated from week ending.' + if (!fromDate) { + nextErrors.fromDate = 'From date is required.' + } + + if (!toDate) { + nextErrors.toDate = 'To date is required.' + } + + if (fromDate && toDate && fromDate.getTime() > toDate.getTime()) { + nextErrors.toDate = 'To date must be the same day or after the from date.' + } + + if (!paymentTitle.trim()) { + nextErrors.fromDate = nextErrors.fromDate || 'Payment title cannot be generated from the selected period.' } if (!Number.isFinite(parsedHoursWorked) || parsedHoursWorked <= 0) { @@ -230,8 +243,9 @@ const PaymentFormModal: FC = ( } if ( - !weekEndingDate - || !isSaturday(weekEndingDate) + !fromDate + || !toDate + || fromDate.getTime() > toDate.getTime() || !paymentTitle.trim() || !Number.isFinite(parsedHoursWorked) || parsedHoursWorked <= 0 @@ -256,7 +270,7 @@ const PaymentFormModal: FC = ( }) resetState() - }, [amount, hoursWorked, paymentTitle, props, ratePerHour, remarks, resetState, weekEndingDate]) + }, [amount, fromDate, hoursWorked, paymentTitle, props, ratePerHour, remarks, resetState, toDate]) return ( = ( {formatCurrency(ratePerHour)}
- Rate Per Week - {formatCurrency(props.member?.agreementRate)} + Payment Cycle + {paymentCycle} +
+
+ Standard Hours Per Day + {normalizePositiveValue(standardHoursPerDay) || '-'}
{BILLING_ACCOUNT_MEMBER_PAYMENT_DETAILS_ENABLED ? ( @@ -306,25 +324,52 @@ const PaymentFormModal: FC = (
- Week ending * + From * + + } + dateFormat='MM/dd/yyyy' + disabled={isSubmitting} + onChange={date => { + setFromDate(date ?? undefined) + setErrors(previous => ({ + ...previous, + fromDate: undefined, + })) + }} + placeholderText='From: ...' + preventOpenOnFocus + portalId='react-date-portal' + popperPlacement='bottom-start' + selected={fromDate} + /> + + {errors.fromDate + ?

{errors.fromDate}

+ : undefined} +
+ +
+ + To * } + customInput={} dateFormat='MM/dd/yyyy' disabled={isSubmitting} - filterDate={isSaturday} + minDate={fromDate || undefined} onChange={date => { - setWeekEndingDate(date) + setToDate(date ?? undefined) setErrors(previous => ({ ...previous, - weekEnding: undefined, + toDate: undefined, })) }} - placeholderText='Week ending: ...' + placeholderText='To: ...' preventOpenOnFocus portalId='react-date-portal' popperPlacement='bottom-start' - selected={weekEndingDate} + selected={toDate} /> {paymentTitle ? ( @@ -333,14 +378,24 @@ const PaymentFormModal: FC = (

) : undefined} - {errors.weekEnding - ?

{errors.weekEnding}

+ {errors.toDate + ?

{errors.toDate}

: undefined}
{ }) }) - it('filters preloaded project billing accounts locally when searching in edit mode', async () => { - fetchProjectBillingAccountsMock.mockResolvedValue([ + it('uses user-scoped billing account search when editing with user context', async () => { + searchBillingAccountsMock.mockResolvedValueOnce([ { active: true, endDate: '2023-11-20T00:00:00.000Z', @@ -294,6 +294,15 @@ describe('FormBillingAccountAutocomplete', () => { startDate: '2023-10-04T00:00:00.000Z', }, ]) + searchBillingAccountsMock.mockResolvedValueOnce([ + { + active: true, + endDate: '2026-10-16T23:59:00.000Z', + id: '80001063', + name: 'BA For Marios', + startDate: '2023-10-31T00:00:00.000Z', + }, + ]) render( { ) await waitFor(() => { - expect(fetchProjectBillingAccountsMock) - .toHaveBeenCalledWith('100578') + expect(searchBillingAccountsMock) + .toHaveBeenCalledWith({ + page: 1, + perPage: 20, + userId: '12345', + }) }) + expect(fetchProjectBillingAccountsMock) + .not + .toHaveBeenCalled() await waitFor(() => { expect(latestAsyncSelectProps?.defaultOptions) .toEqual(expect.arrayContaining([ @@ -320,17 +336,21 @@ describe('FormBillingAccountAutocomplete', () => { let options: unknown await act(async () => { - options = await loadOptions('Budget') + options = await loadOptions('BA F') }) expect(searchBillingAccountsMock) - .not - .toHaveBeenCalled() + .toHaveBeenLastCalledWith({ + name: 'BA F', + page: 1, + perPage: 20, + userId: '12345', + }) expect(options) .toEqual([ expect.objectContaining({ - label: expect.stringContaining('Budget Validation - 3'), - value: '80001042', + label: expect.stringContaining('BA For Marios'), + value: '80001063', }), ]) }) diff --git a/src/apps/work/src/lib/components/form/FormBillingAccountAutocomplete/FormBillingAccountAutocomplete.tsx b/src/apps/work/src/lib/components/form/FormBillingAccountAutocomplete/FormBillingAccountAutocomplete.tsx index 9e7f7f82a..f13e5b67a 100644 --- a/src/apps/work/src/lib/components/form/FormBillingAccountAutocomplete/FormBillingAccountAutocomplete.tsx +++ b/src/apps/work/src/lib/components/form/FormBillingAccountAutocomplete/FormBillingAccountAutocomplete.tsx @@ -198,16 +198,13 @@ function createDebouncedLoader( /** * Async billing-account selector backed by server-side name search. * - * When `projectId` is provided, the selector preloads the project's billing - * accounts so edit flows match legacy behavior. Edit-mode search stays within - * that preloaded project-scoped list so the typed results remain consistent - * with the available options. When only `userId` is provided, the selector - * preloads billing accounts granted to that user so create flows start with - * their accessible options. Server-side search remains scoped to `userId` when - * provided. When `selectedBillingAccount` is provided, the field seeds the - * selected option label from project-scoped data before attempting a direct - * billing-account lookup. The field stores only the selected billing-account - * id in form state. + * When `userId` is provided, the selector preloads and searches billing + * accounts granted to that user so create and edit flows use the same + * Billing Accounts API source. When only `projectId` is provided, the selector + * falls back to the legacy project-scoped list and filters it locally. When + * `selectedBillingAccount` is provided, the field seeds the selected option + * label from project-scoped data before attempting a direct billing-account + * lookup. The field stores only the selected billing-account id in form state. */ export const FormBillingAccountAutocomplete: FC = ( props: FormBillingAccountAutocompleteProps, @@ -245,6 +242,11 @@ export const FormBillingAccountAutocomplete: FC normalizeOptionalStringValue(props.userId), + [props.userId], + ) + const loadBillingAccountOptions = useCallback( async (inputValue: string): Promise => { const normalizedInputValue = inputValue.trim() @@ -255,7 +257,7 @@ export const FormBillingAccountAutocomplete: FC toOption(account)) @@ -281,7 +283,7 @@ export const FormBillingAccountAutocomplete: FC normalizeOptionalStringValue(props.userId), - [props.userId], - ) - const shouldPreloadInitialOptions = useMemo( () => !!props.projectId || !!normalizedUserId, [normalizedUserId, props.projectId], @@ -358,13 +355,13 @@ export const FormBillingAccountAutocomplete: FC { diff --git a/src/apps/work/src/lib/components/form/FormTextField/FormTextField.spec.tsx b/src/apps/work/src/lib/components/form/FormTextField/FormTextField.spec.tsx new file mode 100644 index 000000000..de834e9e4 --- /dev/null +++ b/src/apps/work/src/lib/components/form/FormTextField/FormTextField.spec.tsx @@ -0,0 +1,80 @@ +/* eslint-disable import/no-extraneous-dependencies, ordered-imports/ordered-imports */ +import { + fireEvent, + render, + screen, + waitFor, +} from '@testing-library/react' +import { + FormProvider, + useForm, +} from 'react-hook-form' + +import FormTextField from './FormTextField' + +interface TestFormData { + reviewerCount: number +} + +/** + * Mimics the reviewer count clamp used by `HumanReviewTab`. + * + * @param value raw input value from the number field. + * @returns a minimum reviewer count string. + * @throws Does not throw. + */ +function sanitizeReviewerCount(value: string): string { + return value === '0' ? '1' : value +} + +/** + * Renders `FormTextField` with form-state output for regression assertions. + * + * @returns the test form wrapper. + * @throws Does not throw. + */ +const TestHarness = (): JSX.Element => { + const formMethods = useForm({ + defaultValues: { + reviewerCount: 1, + }, + }) + + return ( + + +
+ {formMethods.formState.isDirty ? 'true' : 'false'} +
+
+ ) +} + +describe('FormTextField', () => { + it('does not dirty the form when sanitization keeps the rendered value unchanged', async () => { + render() + + const reviewerCountInput = screen.getByRole('spinbutton', { + name: 'Reviewer Count', + }) as HTMLInputElement + + fireEvent.change(reviewerCountInput, { + target: { + value: '0', + }, + }) + + await waitFor(() => { + expect(reviewerCountInput.value) + .toBe('1') + }) + expect(screen.getByTestId('is-dirty').textContent) + .toBe('false') + }) +}) diff --git a/src/apps/work/src/lib/components/form/FormTextField/FormTextField.tsx b/src/apps/work/src/lib/components/form/FormTextField/FormTextField.tsx index 35a9a07d1..3be4cc8b7 100644 --- a/src/apps/work/src/lib/components/form/FormTextField/FormTextField.tsx +++ b/src/apps/work/src/lib/components/form/FormTextField/FormTextField.tsx @@ -19,7 +19,9 @@ export interface FormTextFieldProps { counterPosition?: 'below' | 'inline' disabled?: boolean hint?: string + max?: number label: string + min?: number maxLength?: number name: string placeholder?: string @@ -28,12 +30,33 @@ export interface FormTextFieldProps { type?: 'number' | 'text' } +/** + * Formats the stored form value the same way the text input renders it. + * + * @param value current React Hook Form field value. + * @returns the string value rendered in the input. + * @throws Does not throw. + */ +function getInputValue(value: unknown): string { + if (typeof value === 'string') { + return value + } + + if (typeof value === 'number' && Number.isFinite(value)) { + return String(value) + } + + return '' +} + export const FormTextField: FC = (props: FormTextFieldProps) => { const className = props.className const counterPosition = props.counterPosition || 'below' const disabled = props.disabled const hint = props.hint + const max = props.max const label = props.label + const min = props.min const maxLength = props.maxLength const name = props.name const placeholder = props.placeholder @@ -59,18 +82,16 @@ export const FormTextField: FC = (props: FormTextFieldProps) ? sanitize(nextRawValue) : nextRawValue + if (nextValue === getInputValue(field.value)) { + return + } + field.onChange(nextValue) }, [field, sanitize], ) - const value = typeof field.value === 'string' - ? field.value - : ( - typeof field.value === 'number' && Number.isFinite(field.value) - ? String(field.value) - : '' - ) + const value = getInputValue(field.value) const isCounterInline = !!maxLength && counterPosition === 'inline' return ( @@ -96,7 +117,9 @@ export const FormTextField: FC = (props: FormTextFieldProps) )} disabled={disabled} id={name} + max={max} maxLength={maxLength} + min={min} name={field.name} onBlur={field.onBlur} onChange={handleInputChange} diff --git a/src/apps/work/src/lib/components/form/PrizeInput/PrizeInput.tsx b/src/apps/work/src/lib/components/form/PrizeInput/PrizeInput.tsx index c8db27b93..15f057149 100644 --- a/src/apps/work/src/lib/components/form/PrizeInput/PrizeInput.tsx +++ b/src/apps/work/src/lib/components/form/PrizeInput/PrizeInput.tsx @@ -2,7 +2,9 @@ import { ChangeEvent, FC, useCallback, + useEffect, useMemo, + useRef, } from 'react' import classNames from 'classnames' @@ -15,9 +17,11 @@ import styles from './PrizeInput.module.scss' type PrizeType = 'USD' | 'POINT' export interface PrizeInputProps { + autoFocus?: boolean disabled?: boolean error?: boolean onChange: (value: number) => void + onFocus?: () => void prizeType: PrizeType value: number } @@ -45,6 +49,21 @@ export const PrizeInput: FC = (props: PrizeInputProps) => { return String(Math.trunc(props.value)) }, [props.value]) + const inputRef = useRef(null) + + useEffect(() => { + if (!props.autoFocus || props.disabled || !inputRef.current) { + return + } + + inputRef.current.focus() + inputRef.current.setSelectionRange(inputValue.length, inputValue.length) + }, [ + inputValue, + props.autoFocus, + props.disabled, + ]) + return (
= (props: PrizeInputProps) => {
diff --git a/src/apps/work/src/lib/constants/challenge-editor.constants.ts b/src/apps/work/src/lib/constants/challenge-editor.constants.ts index f3352d1d2..bf137422f 100644 --- a/src/apps/work/src/lib/constants/challenge-editor.constants.ts +++ b/src/apps/work/src/lib/constants/challenge-editor.constants.ts @@ -25,6 +25,8 @@ export const SKILLS_SEARCH_DEBOUNCE_MS = 300 export const MAX_PRIZE_VALUE = 1000000 +export const MAX_MANUAL_REVIEWER_COUNT = 20 + export const MAX_CHECKPOINT_PRIZE_COUNT = 8 export const DEFAULT_CHECKPOINT_PRIZE = 50 diff --git a/src/apps/work/src/lib/hooks/useDownloadArtifact.ts b/src/apps/work/src/lib/hooks/useDownloadArtifact.ts index 244be898a..135c7fa71 100644 --- a/src/apps/work/src/lib/hooks/useDownloadArtifact.ts +++ b/src/apps/work/src/lib/hooks/useDownloadArtifact.ts @@ -26,6 +26,9 @@ const MIME_EXTENSION_MAP: Record = { 'text/plain': 'txt', } +const EMPTY_ZIP_SIZE_BYTES = 22 +const EMPTY_ZIP_END_OF_CENTRAL_DIRECTORY = '504b0506' + function getErrorMessage(error: unknown): string { if (error instanceof Error && error.message.trim()) { return error.message @@ -69,6 +72,42 @@ function buildArtifactFileName( return `${fileNameBase}.${extension}` } +/** + * Detects the known empty ZIP body produced by older Marathon Match internal artifacts. + * @param blob Download response body returned by Review API. + * @returns `true` when the blob is a ZIP end-of-central-directory record with no entries. + * Used by `assertArtifactHasContent` before the work app saves an artifact download. + */ +async function isEmptyZipBlob(blob: Blob): Promise { + if (blob.size !== EMPTY_ZIP_SIZE_BYTES) { + return false + } + + const headerBytes = new Uint8Array( + await blob.slice(0, 4) + .arrayBuffer(), + ) + const header = Array.from(headerBytes) + .map(byte => byte.toString(16) + .padStart(2, '0')) + .join('') + + return header === EMPTY_ZIP_END_OF_CENTRAL_DIRECTORY +} + +/** + * Rejects artifact downloads that are known empty ZIP placeholders. + * @param blob Download response body returned by Review API. + * @returns Promise that resolves when the artifact can be saved. + * @throws Error when the artifact is the empty internal ZIP generated by older scorer runs. + * Used by `useDownloadArtifact` so the UI does not save a misleading corrupt-looking file. + */ +async function assertArtifactHasContent(blob: Blob): Promise { + if (await isEmptyZipBlob(blob)) { + throw new Error('Submission artifact is empty. Rerun scoring to regenerate it.') + } +} + function downloadBlob(blob: Blob, fileName: string): void { const blobUrl = window.URL.createObjectURL(blob) const link = document.createElement('a') @@ -120,6 +159,7 @@ export function useDownloadArtifact(): UseDownloadArtifactResult { normalizedSubmissionId, normalizedArtifactId, ) + await assertArtifactHasContent(blob) const fileName = buildArtifactFileName( normalizedArtifactId, blob, diff --git a/src/apps/work/src/lib/models/Engagement.model.ts b/src/apps/work/src/lib/models/Engagement.model.ts index f42d81a61..f252100f8 100644 --- a/src/apps/work/src/lib/models/Engagement.model.ts +++ b/src/apps/work/src/lib/models/Engagement.model.ts @@ -18,6 +18,8 @@ export type ApplicationStatus = 'REJECTED' | 'SELECTED' | 'SUBMITTED' | 'UNDER_R export type AssignmentStatus = 'ACTIVE' | 'ASSIGNED' | 'COMPLETED' | 'OFFER_REJECTED' | 'SELECTED' | 'TERMINATED' +export type PaymentCycle = 'WEEKLY' | 'FORTNIGHTLY' | 'MONTHLY' + export interface Assignment { agreementRate: string durationMonths?: number | string @@ -27,8 +29,10 @@ export interface Assignment { memberHandle: string memberId: number | string otherRemarks: string + paymentCycle?: PaymentCycle | string ratePerHour?: string startDate: string + standardHoursPerDay?: number | string standardHoursPerWeek?: number | string status: AssignmentStatus | string terminationReason?: string diff --git a/src/apps/work/src/lib/schemas/challenge-editor.schema.spec.ts b/src/apps/work/src/lib/schemas/challenge-editor.schema.spec.ts index 84a6430a5..c919962bd 100644 --- a/src/apps/work/src/lib/schemas/challenge-editor.schema.spec.ts +++ b/src/apps/work/src/lib/schemas/challenge-editor.schema.spec.ts @@ -1,4 +1,5 @@ import { + MAX_MANUAL_REVIEWER_COUNT, PRIZE_SET_TYPES, PRIZE_TYPES, REVIEW_TYPES, @@ -300,4 +301,22 @@ describe('challenge-editor schema reviewer slot assignment validation', () => { .resolves .toBeTruthy() }) + + it('rejects reviewer counts above the manual reviewer limit', async () => { + await expect( + challengeAdvancedOptionsSchema.validate({ + ...baseFormData, + reviewers: [ + { + isMemberReview: true, + memberReviewerCount: MAX_MANUAL_REVIEWER_COUNT + 1, + scorecardId: 'scorecard-id', + shouldOpenOpportunity: true, + }, + ], + }), + ) + .rejects + .toThrow(`Number of reviewers cannot exceed ${MAX_MANUAL_REVIEWER_COUNT}`) + }) }) diff --git a/src/apps/work/src/lib/schemas/challenge-editor.schema.ts b/src/apps/work/src/lib/schemas/challenge-editor.schema.ts index 72403d8c0..6796a5ba4 100644 --- a/src/apps/work/src/lib/schemas/challenge-editor.schema.ts +++ b/src/apps/work/src/lib/schemas/challenge-editor.schema.ts @@ -2,6 +2,7 @@ import * as yup from 'yup' import { MAX_CHALLENGE_NAME_LENGTH, + MAX_MANUAL_REVIEWER_COUNT, MAX_PRIZE_VALUE, MIN_DESCRIPTION_LENGTH, PHASE_DURATION_MIN_MINUTES, @@ -176,7 +177,7 @@ function getRequiredReviewerSlots(value: unknown): number { return 1 } - return Math.max(1, Math.trunc(parsedValue)) + return Math.min(MAX_MANUAL_REVIEWER_COUNT, Math.max(1, Math.trunc(parsedValue))) } function getCopilotFee(prizeSets: unknown): number { @@ -228,6 +229,13 @@ const reviewerSchema = yup.object({ then: schema => schema.required('Member is required when public review opportunity is closed'), }), memberReviewerCount: yup.number() + .transform(emptyStringToUndefined) + .integer('Number of reviewers must be a positive integer') + .min(1, 'Number of reviewers must be a positive integer') + .max( + MAX_MANUAL_REVIEWER_COUNT, + `Number of reviewers cannot exceed ${MAX_MANUAL_REVIEWER_COUNT}`, + ) .optional(), phaseId: yup.string() .optional(), diff --git a/src/apps/work/src/lib/schemas/engagement-editor.schema.ts b/src/apps/work/src/lib/schemas/engagement-editor.schema.ts index 57b2e7697..27c861fae 100644 --- a/src/apps/work/src/lib/schemas/engagement-editor.schema.ts +++ b/src/apps/work/src/lib/schemas/engagement-editor.schema.ts @@ -4,8 +4,10 @@ interface EngagementEditorSchemaAssignmentDetails { agreementRate?: string durationMonths?: number | string memberHandle?: string + paymentCycle?: string ratePerHour?: string startDate?: string + standardHoursPerDay?: number | string standardHoursPerWeek?: number | string } @@ -118,7 +120,12 @@ function hasCompleteAssignmentDetails( && !Number.isNaN(parsedStartDate.getTime()) && toPositiveInteger(detail.durationMonths) !== undefined && isPositiveDecimal(detail.ratePerHour) - && isPositiveDecimal(detail.standardHoursPerWeek, 2) + && isPositiveDecimal(detail.standardHoursPerDay, 2) + && ['WEEKLY', 'FORTNIGHTLY', 'MONTHLY'].includes( + String(detail.paymentCycle || 'WEEKLY') + .trim() + .toUpperCase(), + ) } export const engagementEditorSchema: yup.ObjectSchema = yup.object({ diff --git a/src/apps/work/src/lib/services/engagements.service.ts b/src/apps/work/src/lib/services/engagements.service.ts index edec3dc02..4f8128065 100644 --- a/src/apps/work/src/lib/services/engagements.service.ts +++ b/src/apps/work/src/lib/services/engagements.service.ts @@ -55,8 +55,10 @@ interface AssignmentDetails { memberHandle?: string memberId?: number | string otherRemarks?: string + paymentCycle?: string ratePerHour?: string startDate?: string + standardHoursPerDay?: number | string standardHoursPerWeek?: number | string } @@ -309,6 +311,12 @@ function serializeEngagementPayload(data: EngagementUpsertData): Record ({ @@ -13,6 +14,26 @@ jest.mock('../constants', () => ({ })) describe('challenge utils', () => { + describe('isMarathonMatchChallenge', () => { + it('recognizes Marathon Match from the canonical challenge type id', () => { + expect(isMarathonMatchChallenge({ + typeId: '929bc408-9cf2-4b3e-ba71-adfbf693046c', + })) + .toBe(true) + }) + + it('returns false when no Marathon Match identifiers are present', () => { + expect(isMarathonMatchChallenge({ + tags: ['development'], + type: { + name: 'Code', + }, + typeId: '927abff4-7af9-4145-8ba1-577c16e64e2e', + })) + .toBe(false) + }) + }) + describe('getSubmissionProvisionalScore', () => { it('returns only provisional marathon scores', () => { expect(getSubmissionProvisionalScore({ @@ -42,6 +63,35 @@ describe('challenge utils', () => { .toBe(12) }) + it('returns the latest provisional summation before stale raw scores', () => { + expect(getSubmissionProvisionalScore({ + reviewSummation: [ + { + aggregateScore: 93.82, + isProvisional: true, + metadata: { + testProcess: 'provisional', + }, + updatedAt: '2026-05-20T03:41:00.000Z', + }, + { + aggregateScore: 73.2513061836071, + isProvisional: true, + metadata: { + testProcess: 'provisional', + }, + updatedAt: '2026-05-20T04:04:00.000Z', + }, + ], + submissions: [ + { + provisionalScore: 93.82, + }, + ], + })) + .toBe(73.2513061836071) + }) + it('returns undefined when provisional scoring is unavailable', () => { expect(getSubmissionProvisionalScore({ review: [ @@ -106,6 +156,35 @@ describe('challenge utils', () => { .toBe(20) }) + it('returns the latest system summation before stale raw scores', () => { + expect(getSubmissionSystemScore({ + reviewSummation: [ + { + aggregateScore: 60, + isFinal: true, + metadata: { + testProcess: 'system', + }, + updatedAt: '2026-05-20T03:41:00.000Z', + }, + { + aggregateScore: 88.5, + isFinal: true, + metadata: { + testProcess: 'system', + }, + updatedAt: '2026-05-20T04:04:00.000Z', + }, + ], + submissions: [ + { + finalScore: 60, + }, + ], + })) + .toBe(88.5) + }) + it('returns undefined when system scoring is unavailable', () => { expect(getSubmissionSystemScore({ review: [ diff --git a/src/apps/work/src/lib/utils/challenge.utils.ts b/src/apps/work/src/lib/utils/challenge.utils.ts index 32a843372..91a82b63e 100644 --- a/src/apps/work/src/lib/utils/challenge.utils.ts +++ b/src/apps/work/src/lib/utils/challenge.utils.ts @@ -41,6 +41,10 @@ interface SubmissionTestProgressCandidate extends SubmissionTestProgressDisplay updatedAt: number } +const MARATHON_MATCH_TYPE_IDS = new Set([ + '929bc408-9cf2-4b3e-ba71-adfbf693046c', +]) + function getPhaseStartDate(phase: ChallengePhase): string { const phaseStartDate = phase.actualStartDate || phase.scheduledStartDate @@ -114,6 +118,28 @@ function getAverageScore(scores: Array): number | undefined return totalScore / validScores.length } +/** + * Resolves the best timestamp available for ordering scored review summations. + * @param entry Review summation returned by Review API. + * @returns Epoch milliseconds or 0 when no parseable timestamp exists. + * Used by marathon score display to pick the latest relative-scoring update. + */ +function getReviewSummationScoreTimestamp(entry: ReviewSummation): number { + const timestamp = entry.updatedAt || entry.createdAt || '' + const parsedTimestamp = Date.parse(timestamp) + + return Number.isFinite(parsedTimestamp) + ? parsedTimestamp + : 0 +} + +/** + * Returns the latest valid score from matching Review API summations. + * @param reviewSummation Review summations attached to a submission. + * @param matcher Predicate that selects the requested Marathon Match score phase. + * @returns Latest valid aggregate score, or `undefined` when no matching score exists. + * Used by marathon score display so relative-score rewrites replace stale raw scores. + */ function getScoreFromSummation( reviewSummation: ReviewSummation[] | undefined, matcher: (entry: ReviewSummation) => boolean, @@ -122,9 +148,20 @@ function getScoreFromSummation( return undefined } - const matchedSummation = reviewSummation.find(matcher) + const matchedSummation = reviewSummation + .map((entry, index) => ({ + entry, + index, + score: toValidScore(entry.aggregateScore), + timestamp: getReviewSummationScoreTimestamp(entry), + })) + .filter(item => item.score !== undefined && matcher(item.entry)) + .sort((first, second) => ( + second.timestamp - first.timestamp + || second.index - first.index + ))[0] - return toValidScore(matchedSummation?.aggregateScore) + return matchedSummation?.score } /** @@ -352,14 +389,20 @@ function normalizeChallengeTypeToken(value: unknown): string { /** * Returns whether the challenge is a Marathon Match. * @param challenge Challenge payload from the challenge API. - * @returns `true` when the type or tags identify Marathon Match. + * @returns `true` when the type, type ID, or tags identify Marathon Match. * Used by the submissions view to expose marathon-only runner log actions. */ -export function isMarathonMatchChallenge(challenge: Pick): boolean { +export function isMarathonMatchChallenge( + challenge: Pick, +): boolean { const typeName = getChallengeTypeName(challenge.type) const typeAbbreviation = typeof challenge.type === 'object' ? challenge.type?.abbreviation : undefined + const typeId = typeof challenge.typeId === 'string' + ? challenge.typeId.trim() + .toLowerCase() + : '' const typeTokens = [ typeName, typeAbbreviation, @@ -370,6 +413,7 @@ export function isMarathonMatchChallenge(challenge: Pick getReviewSummationTestProcess(item) === 'provisional', + ) + if (provisionalSummationScore !== undefined) { + return provisionalSummationScore + } + const legacyProvisionalScore = toValidScore( submission.submissions?.[0]?.provisionalScore, ) @@ -440,10 +492,7 @@ export function getSubmissionProvisionalScore( return legacyProvisionalScore } - return getScoreFromSummation( - submission.reviewSummation, - item => getReviewSummationTestProcess(item) === 'provisional', - ) + return undefined } export function getSubmissionInitialScore(submission: ScoredSubmissionLike): number { @@ -477,11 +526,19 @@ export function getFinalScore(submission: ScoredSubmissionLike): number { * Returns only the system marathon score for a submission. * @param submission Submission-like object containing legacy phase scores or Review API summations. * @returns System score, or `undefined` when system scoring has not produced a valid score. - * Used by marathon submission score display to show pending/unavailable system scores as empty. + * Used by marathon submission score display to prefer latest relative summations over stale raw scores. */ export function getSubmissionSystemScore( submission: ScoredSubmissionLike, ): number | undefined { + const systemSummationScore = getScoreFromSummation( + submission.reviewSummation, + item => getReviewSummationTestProcess(item) === 'system', + ) + if (systemSummationScore !== undefined) { + return systemSummationScore + } + const legacyFinalScore = toValidScore( submission.submissions?.[0]?.finalScore, ) @@ -489,10 +546,7 @@ export function getSubmissionSystemScore( return legacyFinalScore } - return getScoreFromSummation( - submission.reviewSummation, - item => getReviewSummationTestProcess(item) === 'system', - ) + return undefined } export function getSubmissionFinalScore(submission: ScoredSubmissionLike): number { diff --git a/src/apps/work/src/lib/utils/engagement.utils.ts b/src/apps/work/src/lib/utils/engagement.utils.ts index df54c53f9..bb3836cf0 100644 --- a/src/apps/work/src/lib/utils/engagement.utils.ts +++ b/src/apps/work/src/lib/utils/engagement.utils.ts @@ -132,6 +132,22 @@ function toOptionalNumberishValue(value: unknown): number | string | undefined { return undefined } +function toStandardHoursPerDay(value: unknown): number | string | undefined { + const normalized = toOptionalNumberishValue(value) + + if (normalized === undefined) { + return undefined + } + + const parsedValue = Number(normalized) + + if (!Number.isFinite(parsedValue) || parsedValue <= 0) { + return undefined + } + + return Number((parsedValue / 5).toFixed(2)) +} + function toIdentifierValue(...values: unknown[]): number | string { for (const value of values) { if (typeof value === 'number' && Number.isFinite(value)) { @@ -310,6 +326,11 @@ function normalizeAssignment( const ratePerHour = toOptionalString( assignment.ratePerHour ?? assignment.rate_per_hour, ) + const standardHoursPerDay = toOptionalNumberishValue( + assignment.standardHoursPerDay ?? assignment.standard_hours_per_day, + ) ?? toStandardHoursPerDay( + assignment.standardHoursPerWeek ?? assignment.standard_hours_per_week, + ) const standardHoursPerWeek = toOptionalNumberishValue( assignment.standardHoursPerWeek ?? assignment.standard_hours_per_week, ) @@ -317,7 +338,13 @@ function normalizeAssignment( assignment.agreementRate ?? assignment.agreement_rate ?? assignment.rate - ?? calculateAssignmentRatePerWeek(ratePerHour, standardHoursPerWeek), + ?? calculateAssignmentRatePerWeek( + ratePerHour, + standardHoursPerWeek + ?? (standardHoursPerDay !== undefined + ? Number(standardHoursPerDay) * 5 + : undefined), + ), ) return { @@ -356,7 +383,12 @@ function normalizeAssignment( ?? assignment.other_remarks ?? assignment.remarks, ), + paymentCycle: normalizeString( + assignment.paymentCycle + ?? assignment.payment_cycle, + ) || 'WEEKLY', ratePerHour, + standardHoursPerDay, standardHoursPerWeek, startDate: toIsoString( assignment.startDate diff --git a/src/apps/work/src/lib/utils/index.ts b/src/apps/work/src/lib/utils/index.ts index 791f0a0bc..dae957d45 100644 --- a/src/apps/work/src/lib/utils/index.ts +++ b/src/apps/work/src/lib/utils/index.ts @@ -12,8 +12,11 @@ export * from './metadata.utils' export * from './navigation.utils' export { calculatePaymentAmount, + getAssignmentPaymentCycle, getAssignmentRatePerHour, + getAssignmentStandardHoursPerDay, getAssignmentStandardHoursPerWeek, + getExpectedHoursLabel, getAssignmentStatus, getPaymentAmount, getPaymentCreatorLabel, diff --git a/src/apps/work/src/lib/utils/payment.utils.ts b/src/apps/work/src/lib/utils/payment.utils.ts index 4229d4132..95d758869 100644 --- a/src/apps/work/src/lib/utils/payment.utils.ts +++ b/src/apps/work/src/lib/utils/payment.utils.ts @@ -5,6 +5,8 @@ import { import { Assignment, AssignmentPayment } from '../models' +const DEFAULT_PAYMENT_CYCLE = 'WEEKLY' + const currencyFormatter = new Intl.NumberFormat('en-US', { currency: 'USD', style: 'currency', @@ -220,6 +222,34 @@ export function getAssignmentStatus(member: Partial): string { return String(member.status) } +export function getAssignmentPaymentCycle(member: Partial): string { + const normalizedCycle = toOptionalDisplayString(member.paymentCycle) + + return normalizedCycle + ? normalizedCycle.toUpperCase() + : DEFAULT_PAYMENT_CYCLE +} + +export function getAssignmentStandardHoursPerDay( + member: Partial, +): number | string | undefined { + if ( + member.standardHoursPerDay !== undefined + && member.standardHoursPerDay !== null + && member.standardHoursPerDay !== '' + ) { + return member.standardHoursPerDay + } + + const standardHoursPerWeek = toNumber(member.standardHoursPerWeek) + + if (standardHoursPerWeek === undefined || standardHoursPerWeek <= 0) { + return undefined + } + + return Number((standardHoursPerWeek / 5).toFixed(2)) +} + /** * Reads the assignment standard hours value from a member object. * @@ -229,7 +259,21 @@ export function getAssignmentStatus(member: Partial): string { export function getAssignmentStandardHoursPerWeek( member: Partial, ): number | string | undefined { - return member.standardHoursPerWeek + if ( + member.standardHoursPerWeek !== undefined + && member.standardHoursPerWeek !== null + && member.standardHoursPerWeek !== '' + ) { + return member.standardHoursPerWeek + } + + const standardHoursPerDay = toNumber(getAssignmentStandardHoursPerDay(member)) + + if (standardHoursPerDay === undefined || standardHoursPerDay <= 0) { + return undefined + } + + return Number((standardHoursPerDay * 5).toFixed(2)) } /** @@ -262,6 +306,29 @@ export function getAssignmentRatePerHour( return agreementRate / standardHoursPerWeek } +export function getExpectedHoursLabel(member: Partial): string { + const standardHoursPerDay = toNumber(getAssignmentStandardHoursPerDay(member)) + + if (standardHoursPerDay === undefined || standardHoursPerDay <= 0) { + return '' + } + + const paymentCycle = getAssignmentPaymentCycle(member) + + if (paymentCycle === 'FORTNIGHTLY') { + return `${Number((standardHoursPerDay * 10).toFixed(2))} hours` + } + + if (paymentCycle === 'MONTHLY') { + const minimum = Number((standardHoursPerDay * 20).toFixed(2)) + const maximum = Number((standardHoursPerDay * 23).toFixed(2)) + + return `${minimum}-${maximum} hours` + } + + return `${Number((standardHoursPerDay * 5).toFixed(2))} hours` +} + /** * Calculates the payment amount from hours worked and hourly pay rate. * diff --git a/src/apps/work/src/lib/utils/prize.utils.ts b/src/apps/work/src/lib/utils/prize.utils.ts index e9ffd1a70..beaeb232c 100644 --- a/src/apps/work/src/lib/utils/prize.utils.ts +++ b/src/apps/work/src/lib/utils/prize.utils.ts @@ -1,4 +1,5 @@ import { + MAX_MANUAL_REVIEWER_COUNT, MAX_PRIZE_VALUE, PRIZE_SET_TYPES, PRIZE_TYPES, @@ -102,7 +103,10 @@ export function getFirstPlacePrizeValue(prizeSets?: PrizeSet[]): number { } function getReviewerCount(reviewer?: ReviewerInput): number { - return Math.max(1, Math.trunc(toNumber(reviewer?.memberReviewerCount) || 1)) + return Math.min( + MAX_MANUAL_REVIEWER_COUNT, + Math.max(1, Math.trunc(toNumber(reviewer?.memberReviewerCount) || 1)), + ) } export function calculateEstimatedReviewerCost( diff --git a/src/apps/work/src/pages/budget-approvals/BudgetApprovalsPage/BudgetApprovalsPage.spec.tsx b/src/apps/work/src/pages/budget-approvals/BudgetApprovalsPage/BudgetApprovalsPage.spec.tsx new file mode 100644 index 000000000..81c1c3ef2 --- /dev/null +++ b/src/apps/work/src/pages/budget-approvals/BudgetApprovalsPage/BudgetApprovalsPage.spec.tsx @@ -0,0 +1,217 @@ +/* eslint-disable no-var, global-require, @typescript-eslint/no-var-requires */ +/* eslint-disable import/no-extraneous-dependencies, ordered-imports/ordered-imports */ +import type { + Context, + PropsWithChildren, +} from 'react' +import { + render, + screen, +} from '@testing-library/react' +import { MemoryRouter } from 'react-router-dom' + +import { + useFetchChallenges, + useFetchProjects, +} from '../../../lib/hooks' +import { WorkAppContextModel } from '../../../lib/models' + +import { BudgetApprovalsPage } from './BudgetApprovalsPage' + +var mockWorkAppContext: Context + +jest.mock('~/apps/review/src/lib', () => ({ + PageWrapper: (props: PropsWithChildren<{ pageTitle?: string }>) => ( +
+

{props.pageTitle}

+ {props.children} +
+ ), +}), { + virtual: true, +}) +jest.mock('~/libs/ui', () => ({ + IconOutline: { + ExternalLinkIcon: () => external-link-icon, + }, +}), { + virtual: true, +}) +jest.mock('../../../lib/components', () => ({ + Pagination: () =>
Pagination
, +})) +jest.mock('../../../lib/constants', () => ({ + CHALLENGE_APPROVAL_STATUS: { + PENDING_APPROVAL: 'PENDING_APPROVAL', + }, + CHALLENGE_STATUS: { + DRAFT: 'DRAFT', + }, + PAGE_SIZE: 10, + PROJECT_ROLES: { + COPILOT: 'copilot', + MANAGER: 'manager', + }, +})) +jest.mock('../../../lib/contexts', () => { + const React = require('react') as typeof import('react') + + mockWorkAppContext = React.createContext({ + isAdmin: false, + isAnonymous: false, + isCopilot: false, + isManager: false, + isReadOnly: false, + loginUserInfo: undefined, + userRoles: [], + }) + + return { + WorkAppContext: mockWorkAppContext, + } +}) +jest.mock('../../../lib/hooks', () => ({ + useFetchChallenges: jest.fn(), + useFetchProjects: jest.fn(), +})) +jest.mock('react-select', () => ({ + __esModule: true, + default: (props: { + inputId?: string + onChange: (option?: { label: string; value: string }) => void + options: Array<{ label: string; value: string }> + placeholder?: string + value?: { label: string; value: string } + }) => { + function handleChange(event: { target: { value: string } }): void { + const selectedOption = props.options.find(option => option.value === event.target.value) + props.onChange(selectedOption) + } + + return ( + + ) + }, +})) + +const mockedUseFetchChallenges = useFetchChallenges as jest.Mock +const mockedUseFetchProjects = useFetchProjects as jest.Mock + +const managerContextValue: WorkAppContextModel = { + isAdmin: false, + isAnonymous: false, + isCopilot: false, + isManager: true, + isReadOnly: false, + loginUserInfo: { + email: 'manager@example.com', + exp: 0, + handle: 'manager-user', + iat: 0, + roles: ['project manager'], + userId: 12345, + } as WorkAppContextModel['loginUserInfo'], + userRoles: ['project manager'], +} + +describe('BudgetApprovalsPage', () => { + beforeEach(() => { + jest.clearAllMocks() + + mockedUseFetchChallenges.mockReturnValue({ + challenges: [], + error: undefined, + isLoading: false, + isValidating: false, + metadata: { + page: 1, + perPage: 10, + total: 0, + totalPages: 0, + }, + mutate: jest.fn(), + }) + mockedUseFetchProjects.mockReturnValue({ + error: undefined, + isLoading: false, + isValidating: false, + projects: [], + }) + }) + + it('scopes manager budget approvals to full access project memberships only', () => { + const MockWorkAppContext = mockWorkAppContext + + mockedUseFetchProjects.mockReturnValue({ + error: undefined, + isLoading: false, + isValidating: false, + projects: [ + { + id: 'full-access-project', + members: [{ + role: 'manager', + userId: 12345, + }], + name: 'Full Access Project', + status: 'active', + }, + { + id: 'copilot-project', + members: [{ + role: 'copilot', + userId: 12345, + }], + name: 'Copilot Project', + status: 'active', + }, + { + id: 'write-project', + members: [{ + role: 'customer', + userId: 12345, + }], + name: 'Write Project', + status: 'active', + }, + ], + }) + + render( + + + + + , + ) + + expect(mockedUseFetchProjects) + .toHaveBeenCalledWith({ + memberOnly: true, + }) + expect(mockedUseFetchChallenges) + .toHaveBeenCalledWith(expect.objectContaining({ + approvalStatus: 'PENDING_APPROVAL', + projectIds: ['full-access-project'], + status: 'DRAFT', + })) + expect(screen.getByRole('option', { name: 'Full Access Project' })) + .toBeTruthy() + expect(screen.queryByRole('option', { name: 'Copilot Project' })) + .toBeNull() + expect(screen.queryByRole('option', { name: 'Write Project' })) + .toBeNull() + }) +}) diff --git a/src/apps/work/src/pages/budget-approvals/BudgetApprovalsPage/BudgetApprovalsPage.tsx b/src/apps/work/src/pages/budget-approvals/BudgetApprovalsPage/BudgetApprovalsPage.tsx index ae17ccf68..311b5fdf3 100644 --- a/src/apps/work/src/pages/budget-approvals/BudgetApprovalsPage/BudgetApprovalsPage.tsx +++ b/src/apps/work/src/pages/budget-approvals/BudgetApprovalsPage/BudgetApprovalsPage.tsx @@ -103,8 +103,7 @@ export const BudgetApprovalsPage: FC = () => { /** * For non-admins, restrict project options to projects where the user has - * "full write" access (manager or copilot project membership role). - * Admins see all projects. + * Full Access project membership. Admins see all projects. */ const accessibleProjects = useMemo( () => { @@ -125,7 +124,7 @@ export const BudgetApprovalsPage: FC = () => { ?.toLowerCase() ?.trim() - return memberRole === PROJECT_ROLES.MANAGER || memberRole === PROJECT_ROLES.COPILOT + return memberRole === PROJECT_ROLES.MANAGER }) }, [isAdmin, projectsResult.projects, userId], diff --git a/src/apps/work/src/pages/challenges/ChallengeEditorPage/README.md b/src/apps/work/src/pages/challenges/ChallengeEditorPage/README.md index 24527c587..5adbef9d4 100644 --- a/src/apps/work/src/pages/challenges/ChallengeEditorPage/README.md +++ b/src/apps/work/src/pages/challenges/ChallengeEditorPage/README.md @@ -63,7 +63,7 @@ The form uses `challengeBasicInfoSchema` from `src/apps/work/src/lib/schemas/cha - `ChallengeScheduleSection`: schedule editor for challenge start and phase dates. It keeps the detected timezone above the controls, renders the `Start Date` label with the `Scheduled` and `Immediately` start-mode radios aligned to the end of that header row above the input with a green selected state, persists the selected start mode in challenge metadata so saved `/edit` and `/view` routes reopen with the correct radio state, recalculates root phase dates when the challenge start changes, honors a completed predecessor phase's actual end date when deriving successor schedule rows, and keeps completed phases' end-date and duration controls locked to match legacy work-manager behavior. `Task` challenges hide this editable section across create, edit, and read-only view routes to match legacy work-manager behavior. - `DesignWorkTypeField`: shown for Design + Challenge, with the legacy work-type options (`Application Front-End Design`, `Print/Presentation`, `Web Design`, `Widget or Mobile Screen Design`, `Wireframes`). The selected value is stored in challenge tags. - `FunChallengeField`: shown for `Marathon Match` type and remains editable after creation so the form can switch between fun-challenge and standard marathon-match fields. -- `ReviewersField`: hidden for `Task` and `Marathon Match` challenges because manual reviewer assignment is handled elsewhere. On the human-review tab, each manual reviewer card keeps the legacy review-type dropdown, backfills missing legacy review-type values from the matching default reviewer or iterative-review phase fallback, and each manual reviewer phase selector hides registration/submission phases and any phase already assigned on another manual reviewer card while preserving the card's current selection. +- `ReviewersField`: hidden for `Task` and `Marathon Match` challenges because manual reviewer assignment is handled elsewhere. On the human-review tab, each manual reviewer card keeps the legacy review-type dropdown, backfills missing legacy review-type values from the matching default reviewer or iterative-review phase fallback, and each manual reviewer phase selector hides registration/submission phases and any phase already assigned on another manual reviewer card while preserving the card's current selection. Manual reviewer counts are capped before rendering member assignment controls so closed public opportunities cannot create an unbounded number of member selectors. - `Submission Settings`: shown for Design `Challenge` and Design `First2Finish` types, and contains the final-deliverables, stock-art, and submission-visibility controls. - `FinalDeliverablesField`: design-challenge file-type editor that persists the legacy `fileTypes` metadata payload used on challenge draft pages. - `MaximumSubmissionsField`: non-visual compatibility field that rewrites the legacy `submissionLimit` metadata to the unlimited-only payload so design challenges no longer expose submission-cap controls. It defers dirtying that automatic normalization until the editor finishes its initial resource hydration, including the first render after asynchronously loaded challenge details arrive, which preserves copilot restoration before autosave/manual-save starts treating the metadata rewrite as a user change. @@ -72,7 +72,7 @@ The form uses `challengeBasicInfoSchema` from `src/apps/work/src/lib/schemas/cha - `TermsField`: advanced-option multi-select for challenge terms. The create route seeds the standard Topcoder terms entry automatically once the terms list loads, including immediately after the first draft-creation step assigns a challenge id, so the editor matches legacy work-manager defaults while still allowing the NDA toggle to add or remove the NDA term separately. - `ChallengeTagsField`: multi creatable tag picker excluding special challenge tags. - `ChallengeSkillsField`: async multi skills picker with billing-account-based required behavior. -- `ChallengePrizesField`: placement-prize editor with an inline USD/POINTS selector that uses the challenge editor's green selected state, keeps the `Challenge Prizes` header on one line, and stays right-aligned above the fixed-width prize inputs. Each row always shows a numbered `Prize X` label, multi-prize setups allow tied lower placements while still rejecting prize increases for lower places, older payloads that omit the placement set are hydrated on demand, and only removable rows render the delete action so the first prize stays aligned with the selector. +- `ChallengePrizesField`: placement-prize editor with an inline USD/POINTS selector that uses the challenge editor's green selected state, keeps the `Challenge Prizes` header on one line, and stays right-aligned above the fixed-width prize inputs. Each row always shows a numbered `Prize X` label, multi-prize setups allow tied lower placements while still rejecting prize increases for lower places, focused prize inputs keep focus while consecutive digits are entered, older payloads that omit the placement set are hydrated on demand, and only removable rows render the delete action so the first prize stays aligned with the selector. - `AssignedMemberField`: task-only assignee selector backed by member ids; persisted through the challenge `Submitter` resource assignment and restored from resources when task payloads omit the legacy field. - `CopilotField`: clearable dropdown populated with copilot handles from the current project; persisted through the challenge `Copilot` resource assignment and restored from resources when draft payloads omit the legacy field. Persisted selections are matched case-insensitively so refreshes still show the saved copilot even when the resource payload and project-member option list disagree on handle casing, and member-id-only copilot resources are normalized back to handles during refresh hydration. Save-time form resets also reload the persisted copilot resource before the editor reopens the saved draft, so sparse challenge responses do not blank the field. When a refreshed draft still carries a legacy member-id-only copilot resource, the next save deletes that stale resource before writing the canonical handle-based assignment so the challenge does not keep duplicate copilot rows. The initial `New` draft-creation step also saves any selected copilot assignment before the editor resets from fetched challenge data, so the basic-information selection survives the transition into the full draft form. A copilot is required whenever the copilot fee is greater than 0, and that rule is enforced by form validation before save or launch actions run. - `CopilotFeeField`: optional copilot payment input that updates only the underlying copilot prize set, preserving placement prize edits and removing the copilot prize set when cleared so empty fees do not leave hidden validation errors. diff --git a/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ChallengeEditorForm.tsx b/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ChallengeEditorForm.tsx index 94e3bfe28..b5ddcc347 100644 --- a/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ChallengeEditorForm.tsx +++ b/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ChallengeEditorForm.tsx @@ -29,6 +29,7 @@ import { import { AUTOSAVE_DELAY_MS, DESIGN_WORK_TYPES, + MAX_MANUAL_REVIEWER_COUNT, PRIZE_SET_TYPES, ROUND_TYPES, } from '../../../../lib/constants/challenge-editor.constants' @@ -157,6 +158,9 @@ import { import { NDAField, } from './NDAField' +import { + RateChallengeField, +} from './RateChallengeField' import { ReviewCostField, } from './ReviewCostField' @@ -289,6 +293,7 @@ const CHALLENGE_TYPE_MARATHON_MATCH_NAME = 'MARATHONMATCH' const CHALLENGE_TYPE_MARATHON_MATCH_SHORT_ABBREVIATION = 'MM' const CHALLENGE_TYPE_TASK_NAME = 'TASK' const CHALLENGE_TYPE_TASK_SHORT_ABBREVIATION = 'TSK' +const CHALLENGE_TRACK_DEVELOPMENT_NAME = 'DEVELOPMENT' const CHECKPOINT_REQUIRED_PHASES = [ 'Checkpoint Submission', 'Checkpoint Screening', @@ -827,7 +832,7 @@ function getRequiredMemberReviewerCount(reviewer: Reviewer | undefined): number ? Math.trunc(reviewerCountValue) : 1 - return reviewerCount + return Math.min(MAX_MANUAL_REVIEWER_COUNT, reviewerCount) } function getAssignedMemberReviewerSlots(reviewer: Reviewer | undefined): string[] { @@ -1642,20 +1647,36 @@ export const ChallengeEditorForm: FC = ( values.trackId, ], ) + const fallbackChallengeTrackName = useMemo( + (): string | undefined => { + const challengeTrack = props.challenge?.track + + if (!challengeTrack) { + return props.challenge?.legacy?.track + } + + if (typeof challengeTrack === 'string') { + return challengeTrack + } + + return challengeTrack.track || challengeTrack.name || challengeTrack.abbreviation + }, + [ + props.challenge?.legacy?.track, + props.challenge?.track, + ], + ) + const resolvedChallengeTrackName = selectedChallengeTrack?.track + || selectedChallengeTrack?.name + || selectedChallengeTrack?.abbreviation + || fallbackChallengeTrackName const isDesignTrackSelected = useMemo( (): boolean => { - const normalizedTrack = ( - selectedChallengeTrack?.track - || selectedChallengeTrack?.name - || selectedChallengeTrack?.abbreviation - || '' - ) - .trim() - .toUpperCase() + const normalizedTrack = normalizeChallengeTypeToken(resolvedChallengeTrackName) - return normalizedTrack === CHALLENGE_TRACKS.DESIGN + return normalizedTrack === normalizeChallengeTypeToken(CHALLENGE_TRACKS.DESIGN) }, - [selectedChallengeTrack], + [resolvedChallengeTrackName], ) const isChallengeTypeSelected = useMemo( (): boolean => { @@ -1720,6 +1741,28 @@ export const ChallengeEditorForm: FC = ( resolvedChallengeTypeName, ], ) + const isDevelopmentChallengeSelected = useMemo( + (): boolean => { + const normalizedTrackName = normalizeChallengeTypeToken(resolvedChallengeTrackName) + const normalizedChallengeTypeName = normalizeChallengeTypeToken(resolvedChallengeTypeName) + const normalizedChallengeTypeAbbreviation + = normalizeChallengeTypeToken(resolvedChallengeTypeAbbreviation) + + return ( + normalizedTrackName === CHALLENGE_TRACK_DEVELOPMENT_NAME + || normalizedTrackName === normalizeChallengeTypeToken(CHALLENGE_TRACKS.DEVELOP) + ) + && ( + normalizedChallengeTypeName === CHALLENGE_TYPE_CHALLENGE_NAME + || normalizedChallengeTypeAbbreviation === CHALLENGE_TYPE_CHALLENGE_ABBREVIATION + ) + }, + [ + resolvedChallengeTrackName, + resolvedChallengeTypeAbbreviation, + resolvedChallengeTypeName, + ], + ) const showRoundTypeField = isDesignTrackSelected && isChallengeTypeSelected const showDesignWorkTypeField = isDesignTrackSelected && isChallengeTypeSelected const challengeTypeOptions = useMemo( @@ -1812,6 +1855,7 @@ export const ChallengeEditorForm: FC = ( const isFunChallengeSelected = values.funChallenge === true const showFunChallengeField = isMarathonMatchChallengeSelected const showMarathonMatchScorerSection = isMarathonMatchChallengeSelected && isChallengeCreated + const showRateChallengeField = isMarathonMatchChallengeSelected || isDevelopmentChallengeSelected const showPrizesAndBillingSection = !isFunChallengeSelected const showEditableTimelineSection = !isTaskChallenge const usesManualReviewers = useMemo( @@ -3685,6 +3729,9 @@ export const ChallengeEditorForm: FC = ( label='Wipro Allowed' name='wiproAllowed' /> + {showRateChallengeField + ? + : undefined}
diff --git a/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ChallengePrizesField/ChallengePrizesField.spec.tsx b/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ChallengePrizesField/ChallengePrizesField.spec.tsx index a2fc43bc5..cb19bfe9e 100644 --- a/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ChallengePrizesField/ChallengePrizesField.spec.tsx +++ b/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ChallengePrizesField/ChallengePrizesField.spec.tsx @@ -5,6 +5,7 @@ import { render, screen, } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { FormProvider, useForm, @@ -20,26 +21,10 @@ jest.mock('../../../../../lib/components', () => ({ })) jest.mock('../../../../../lib/components/form', () => { - const React = jest.requireActual('react') + const actualPrizeInput = jest.requireActual('../../../../../lib/components/form/PrizeInput') return { - PrizeInput: function PrizeInput(props: { - onChange: (value: number) => void - value: number - }) { - const handleChange = React.useCallback((event: { target: { value: string } }): void => { - props.onChange(Number.parseInt(event.target.value || '0', 10) || 0) - }, [props]) - - return ( - 0 ? String(props.value) : ''} - /> - ) - }, + PrizeInput: actualPrizeInput.PrizeInput, } }) @@ -57,6 +42,21 @@ jest.mock('../../../../../lib/utils', () => ({ getPrizeType: ( prizeSets: Array<{ prizes?: Array<{ type: string }> }>, ) => prizeSets[0]?.prizes?.[0]?.type || 'USD', + validatePrizeValue: (value: string): string => { + const digitsOnly = value.replace(/[^\d]/g, '') + + if (!digitsOnly) { + return '' + } + + const parsedValue = Number.parseInt(digitsOnly, 10) + + if (Number.isNaN(parsedValue)) { + return '' + } + + return String(Math.min(parsedValue, 1000000)) + }, })) jest.mock('~/libs/ui', () => ({ @@ -134,8 +134,8 @@ describe('ChallengePrizesField', () => { it('keeps a labeled first prize row before a second prize is added', () => { render() - const onlyPrizeRow = screen.getByLabelText('Prize amount') - .parentElement?.parentElement as HTMLDivElement + const onlyPrizeRow = screen.getByRole('textbox') + .parentElement?.parentElement?.parentElement as HTMLDivElement const header = screen.getByRole('radiogroup', { name: /Challenge Prizes/i }) .parentElement as HTMLDivElement @@ -204,6 +204,35 @@ describe('ChallengePrizesField', () => { .toContain(styles.prizeRowWithRemove) }) + it('allows typing multiple digits into an empty prize input continuously', async () => { + const user = userEvent.setup() + + render( + , + ) + + const prizeInput = screen.getByRole('textbox') as HTMLInputElement + + await user.type(prizeInput, '55') + + expect(prizeInput.value) + .toBe('55') + expect(document.activeElement) + .toBe(prizeInput) + }) + it('allows equal lower placement prizes without showing an ordering error', () => { render( = ( const [pendingPrizeType, setPendingPrizeType] = useState() const [showPointsConfirmation, setShowPointsConfirmation] = useState(false) + const [activePrizeIndex, setActivePrizeIndex] = useState() const supportsMultiplePrizes = useMemo( () => hasMultiplePrizesSupport( @@ -283,6 +284,7 @@ export const ChallengePrizesField: FC = ( const handlePrizeValueChange = useCallback( (index: number, value: number): void => { + setActivePrizeIndex(index) setValue(`${placementPrizesName}.${index}.value` as never, value as never, { shouldDirty: true, shouldValidate: true, @@ -314,6 +316,13 @@ export const ChallengePrizesField: FC = ( ], ) + const prizeFocusHandlers = useMemo( + () => fields.map((_, index) => (): void => { + setActivePrizeIndex(index) + }), + [fields], + ) + const removeHandlers = useMemo( () => fields.map((_, index) => (): void => { remove(index) @@ -421,9 +430,11 @@ export const ChallengePrizesField: FC = (
diff --git a/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/RateChallengeField/RateChallengeField.spec.tsx b/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/RateChallengeField/RateChallengeField.spec.tsx new file mode 100644 index 000000000..fcb4922cb --- /dev/null +++ b/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/RateChallengeField/RateChallengeField.spec.tsx @@ -0,0 +1,156 @@ +/* eslint-disable import/no-extraneous-dependencies, ordered-imports/ordered-imports */ +import { FC } from 'react' +import { + render, + screen, + waitFor, +} from '@testing-library/react' +import '@testing-library/jest-dom' +import userEvent from '@testing-library/user-event' +import { + FormProvider, + useForm, + useWatch, +} from 'react-hook-form' + +import { ChallengeEditorFormData } from '../../../../../lib/models' + +import { RateChallengeField } from './RateChallengeField' + +interface MockFormCheckboxFieldProps { + checkboxOnlyHitArea?: boolean + label: string + name: string + onChange?: (checked: boolean) => void +} + +const mockFormCheckboxFieldProps: MockFormCheckboxFieldProps[] = [] + +jest.mock('../../../../../lib/components/form', () => ({ + FormCheckboxField: function MockFormCheckboxField(props: MockFormCheckboxFieldProps) { + const React: typeof import('react') = jest.requireActual('react') + const reactHookForm: typeof import('react-hook-form') = jest.requireActual('react-hook-form') + const formContext = reactHookForm.useFormContext() + const controller = reactHookForm.useController({ + control: formContext.control, + name: props.name, + }) + + mockFormCheckboxFieldProps.push(props) + + return React.createElement( + 'label', + { + 'data-checkbox-only-hit-area': props.checkboxOnlyHitArea === true + ? 'true' + : 'false', + }, + React.createElement('input', { + checked: controller.field.value === true, + onChange: (event: { target: { checked: boolean } }) => { + controller.field.onChange(event.target.checked) + props.onChange?.(event.target.checked) + }, + type: 'checkbox', + }), + props.label, + ) + }, +})) + +interface TestHarnessProps { + defaultMetadata?: Array<{ + name: string + value: unknown + }> +} + +const MetadataWatcher: FC = () => { + const metadata = useWatch({ + name: 'metadata', + }) + + return {JSON.stringify(metadata || [])} +} + +const TestHarness: FC = (props: TestHarnessProps) => { + const formMethods = useForm({ + defaultValues: { + description: 'Public challenge specification', + metadata: props.defaultMetadata, + name: 'Development challenge', + skills: [], + tags: [], + trackId: 'development-track', + typeId: 'development-type', + }, + }) + + return ( + + + + + ) +} + +describe('RateChallengeField', () => { + beforeEach(() => { + mockFormCheckboxFieldProps.length = 0 + }) + + it('defaults to rated when metadata is missing and stores false when unchecked', async () => { + const user = userEvent.setup() + + render() + + const checkbox = screen.getByRole('checkbox', { name: 'Rate this challenge' }) + await waitFor(() => { + expect(checkbox) + .toBeChecked() + }) + + await user.click(checkbox) + + expect(checkbox) + .not.toBeChecked() + expect(screen.getByTestId('metadata-value').textContent) + .toBe(JSON.stringify([{ + name: 'isRated', + value: 'false', + }])) + }) + + it('renders the checkbox without a duplicate field header', () => { + render() + + expect(mockFormCheckboxFieldProps[0].checkboxOnlyHitArea) + .toBe(true) + }) + + it('restores the default rated metadata when checked again', async () => { + const user = userEvent.setup() + + render( + , + ) + + const checkbox = screen.getByRole('checkbox', { name: 'Rate this challenge' }) + await waitFor(() => { + expect(checkbox) + .not.toBeChecked() + }) + + await user.click(checkbox) + + expect(checkbox) + .toBeChecked() + expect(screen.getByTestId('metadata-value').textContent) + .toBe(JSON.stringify([])) + }) +}) diff --git a/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/RateChallengeField/RateChallengeField.tsx b/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/RateChallengeField/RateChallengeField.tsx new file mode 100644 index 000000000..6fabcd614 --- /dev/null +++ b/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/RateChallengeField/RateChallengeField.tsx @@ -0,0 +1,115 @@ +import { + FC, + useCallback, + useEffect, +} from 'react' +import { + useFormContext, + useWatch, +} from 'react-hook-form' + +import { FormCheckboxField } from '../../../../../lib/components/form' +import { + getMetadataValue, + removeMetadataValue, + setMetadataValue, +} from '../../../../../lib/utils/metadata.utils' +import { + ChallengeEditorFormData, + ChallengeMetadata, +} from '../../../../../lib/models' + +const IS_RATED_FIELD = 'isRated' +const IS_RATED_TOGGLE_FIELD = 'isRatedToggle' + +interface RateChallengeFormData extends ChallengeEditorFormData { + isRatedToggle?: boolean +} + +/** + * Resolves whether a challenge should be treated as rated from metadata. + * + * @param metadata challenge metadata entries currently held by the editor form. + * @returns `false` only when the `isRated` metadata entry is explicitly `false`; + * otherwise challenges stay rated by default. + * @throws Does not throw. + */ +function isRatedMetadataEnabled(metadata: ChallengeMetadata[] | undefined): boolean { + return getMetadataValue(metadata, IS_RATED_FIELD) + ?.trim() + .toLowerCase() !== 'false' +} + +/** + * Renders the challenge rating opt-out control backed by challenge metadata. + * + * Missing `isRated` metadata defaults to rated, so the saved challenge only + * needs an explicit `isRated=false` entry when the user opts out. + * + * @returns The rated challenge checkbox used by the challenge editor. + * @throws Does not throw. + */ +export const RateChallengeField: FC = () => { + const formContext = useFormContext() + const metadata = useWatch({ + control: formContext.control, + name: 'metadata', + }) + const isRatedToggle = useWatch({ + control: formContext.control, + name: IS_RATED_TOGGLE_FIELD, + }) + + const isRated = isRatedMetadataEnabled(metadata) + + useEffect(() => { + if (isRatedToggle !== undefined) { + return + } + + formContext.setValue( + IS_RATED_TOGGLE_FIELD, + isRated, + { + shouldDirty: false, + shouldValidate: false, + }, + ) + }, [formContext, isRated, isRatedToggle]) + + const handleRatedChange = useCallback((checked: boolean): void => { + if (checked === isRated) { + return + } + + formContext.setValue( + 'metadata', + checked + ? removeMetadataValue(metadata, IS_RATED_FIELD) + : setMetadataValue( + metadata, + IS_RATED_FIELD, + 'false', + ), + { + shouldDirty: true, + shouldValidate: true, + }, + ) + }, [ + formContext, + isRated, + metadata, + ]) + + return ( + + ) +} + +export default RateChallengeField diff --git a/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/RateChallengeField/index.ts b/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/RateChallengeField/index.ts new file mode 100644 index 000000000..8a4187e97 --- /dev/null +++ b/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/RateChallengeField/index.ts @@ -0,0 +1 @@ +export * from './RateChallengeField' diff --git a/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ReviewersField/HumanReviewTab.spec.tsx b/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ReviewersField/HumanReviewTab.spec.tsx index 5cb7b1721..f3f82ecb3 100644 --- a/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ReviewersField/HumanReviewTab.spec.tsx +++ b/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ReviewersField/HumanReviewTab.spec.tsx @@ -12,11 +12,14 @@ import type { } from 'react' import { useEffect, + useRef, } from 'react' import { FormProvider, UseControllerReturn, useForm, + useFormContext as useReactHookFormContext, + useWatch, } from 'react-hook-form' import { @@ -26,6 +29,10 @@ import { useFetchResources, } from '../../../../../lib/hooks' import { + MAX_MANUAL_REVIEWER_COUNT, +} from '../../../../../lib/constants/challenge-editor.constants' +import { + deleteResource, fetchDefaultReviewers, fetchProfile, fetchScorecards, @@ -106,12 +113,48 @@ jest.mock('../../../../../lib/components/form', () => ({ }, FormTextField: (props: { label: string + max?: number + min?: number name: string - }) => ( -
- {props.label} -
- ), + sanitize?: (value: string) => string + type?: 'number' | 'text' + }) => { + const { + useController, + useFormContext, + }: typeof import('react-hook-form') = jest.requireActual('react-hook-form') + const formContext = useFormContext() + const controller: UseControllerReturn = useController({ + control: formContext.control, + name: props.name, + }) + const value = typeof controller.field.value === 'number' + ? String(controller.field.value) + : (controller.field.value || '') + + function handleChange(event: ChangeEvent): void { + const nextValue = props.sanitize + ? props.sanitize(event.target.value) + : event.target.value + + controller.field.onChange(nextValue) + } + + return ( +
+ + +
+ ) + }, FormUserAutocomplete: (props: { label: string name: string @@ -173,6 +216,7 @@ const mockedUseFetchChallengeTracks = useFetchChallengeTracks as jest.Mock const mockedUseFetchChallengeTypes = useFetchChallengeTypes as jest.Mock const mockedUseFetchResourceRoles = useFetchResourceRoles as jest.Mock const mockedUseFetchResources = useFetchResources as jest.Mock +const mockedDeleteResource = deleteResource as jest.Mock const mockedFetchDefaultReviewers = fetchDefaultReviewers as jest.Mock const mockedFetchProfile = fetchProfile as jest.Mock const mockedFetchScorecards = fetchScorecards as jest.Mock @@ -204,6 +248,8 @@ function createDeferredPromise(): DeferredPromise { interface TestHarnessProps { defaultValues?: Partial initialScorecardErrorMessage?: string + restoreStaleAdditionalMemberIds?: boolean + showAdditionalMemberIdsValue?: boolean showMemberValue?: boolean showScorecardValue?: boolean } @@ -252,6 +298,51 @@ function getPhaseOptionLabels(fieldName: string): string[] { .map(option => option.label) } +/** + * Simulates React Hook Form continuing to report a hidden blank member slot + * after the reviewer count cleanup tries to unregister it. + * + * @returns render-count marker used to detect runaway cleanup loops. + * @throws when the regression produces repeated trim/re-register renders. + */ +const StaleAdditionalMemberIdsReporter = (): JSX.Element => { + const formContext = useReactHookFormContext() + const renderCountRef = useRef(0) + const reviewerCount = useWatch({ + control: formContext.control, + name: 'reviewers.0.memberReviewerCount', + }) as number | string | undefined + const additionalMemberIds = useWatch({ + control: formContext.control, + name: 'reviewers.0.additionalMemberIds', + }) as string[] | undefined + + renderCountRef.current += 1 + if (renderCountRef.current > 20) { + throw new Error('Reviewer count cleanup looped') + } + + useEffect(() => { + if ( + String(reviewerCount) !== '1' + || additionalMemberIds !== undefined + ) { + return + } + + formContext.setValue('reviewers.0.additionalMemberIds', [''], { + shouldDirty: false, + shouldValidate: false, + }) + }, [ + additionalMemberIds, + formContext, + reviewerCount, + ]) + + return
{renderCountRef.current}
+} + const TestHarness = (props: TestHarnessProps): JSX.Element => { const formMethods = useForm({ defaultValues: { @@ -277,6 +368,18 @@ const TestHarness = (props: TestHarnessProps): JSX.Element => { return ( + {props.restoreStaleAdditionalMemberIds + ? + : undefined} + {props.showAdditionalMemberIdsValue + ? ( +
+ {formMethods.watch('reviewers.0.additionalMemberIds') === undefined + ? 'undefined' + : JSON.stringify(formMethods.watch('reviewers.0.additionalMemberIds'))} +
+ ) + : undefined} {props.showMemberValue ? (
@@ -330,6 +433,7 @@ describe('HumanReviewTab', () => { .mockResolvedValue(undefined), resources: [], }) + mockedDeleteResource.mockResolvedValue(undefined) mockedFetchDefaultReviewers.mockImplementation(() => createPendingPromise()) mockedFetchProfile.mockResolvedValue(undefined) mockedFetchScorecards.mockImplementation(() => createPendingPromise()) @@ -668,6 +772,119 @@ describe('HumanReviewTab', () => { }) }) + it('caps assignment fields when closed-opportunity reviewer count is too large', async () => { + render() + + fireEvent.change( + within(screen.getByTestId('reviewers.0.memberReviewerCount')) + .getByRole('spinbutton', { name: 'Reviewer Count' }), + { + target: { + value: String(MAX_MANUAL_REVIEWER_COUNT + 5), + }, + }, + ) + + await waitFor(() => { + expect(( + within(screen.getByTestId('reviewers.0.memberReviewerCount')) + .getByRole('spinbutton', { name: 'Reviewer Count' }) as HTMLInputElement + ).value) + .toBe(String(MAX_MANUAL_REVIEWER_COUNT)) + }) + expect(screen.getByTestId(`reviewers.0.additionalMemberIds.${MAX_MANUAL_REVIEWER_COUNT - 2}`)) + .not.toBeNull() + expect(screen.queryByTestId(`reviewers.0.additionalMemberIds.${MAX_MANUAL_REVIEWER_COUNT - 1}`)) + .toBeNull() + }) + + it('removes blank assignment slots without deleting resources after closing public opportunity', async () => { + render( + , + ) + + fireEvent.click(screen.getByLabelText('Open public review opportunity')) + + expect(screen.getByTestId('reviewers.0.additionalMemberIds.0')) + .not.toBeNull() + + fireEvent.change( + within(screen.getByTestId('reviewers.0.memberReviewerCount')) + .getByRole('spinbutton', { name: 'Reviewer Count' }), + { + target: { + value: '1', + }, + }, + ) + + await waitFor(() => { + expect(screen.queryByTestId('reviewers.0.additionalMemberIds.0')) + .toBeNull() + }) + expect(screen.getByTestId('additional-member-ids-value').textContent) + .toBe('undefined') + expect(mockedDeleteResource) + .not.toHaveBeenCalled() + }) + + it('does not repeat reviewer count cleanup when a hidden blank slot is still reported', async () => { + render( + , + ) + + fireEvent.change( + within(screen.getByTestId('reviewers.0.memberReviewerCount')) + .getByRole('spinbutton', { name: 'Reviewer Count' }), + { + target: { + value: '1', + }, + }, + ) + + await waitFor(() => { + expect(screen.getByTestId('stale-additional-member-renders')) + .not.toBeNull() + }) + expect(screen.queryByTestId('reviewers.0.additionalMemberIds.0')) + .toBeNull() + expect(mockedDeleteResource) + .not.toHaveBeenCalled() + }) + it('hides appeal phases for manual reviewer cards across challenge types', () => { mockedUseFetchChallengeTypes.mockReturnValue({ challengeTypes: [ diff --git a/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ReviewersField/HumanReviewTab.tsx b/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ReviewersField/HumanReviewTab.tsx index 001cc376c..3ff47258d 100644 --- a/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ReviewersField/HumanReviewTab.tsx +++ b/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/ReviewersField/HumanReviewTab.tsx @@ -29,6 +29,9 @@ import { UseFetchResourceRolesResult, useFetchResources, } from '../../../../../lib/hooks' +import { + MAX_MANUAL_REVIEWER_COUNT, +} from '../../../../../lib/constants/challenge-editor.constants' import { ChallengeEditorFormData, DefaultReviewer, @@ -286,7 +289,10 @@ function countMatchingValues(values: string[], value: string): number { } function getReviewerCount(reviewer?: Reviewer): number { - return Math.max(1, Math.trunc(toNumber(reviewer?.memberReviewerCount) || 1)) + return Math.min( + MAX_MANUAL_REVIEWER_COUNT, + Math.max(1, Math.trunc(toNumber(reviewer?.memberReviewerCount) || 1)), + ) } function getAdditionalMemberIds(reviewer?: Reviewer): string[] { @@ -591,7 +597,7 @@ function mapDefaultReviewerToReviewer( : (memberReview ? 0.05 : 0), isMemberReview: memberReview, memberReviewerCount: memberReview - ? defaultReviewerCount + ? Math.min(defaultReviewerCount, MAX_MANUAL_REVIEWER_COUNT) : undefined, phaseId: getReviewerPhaseId(defaultReviewer, phases), roleId: defaultReviewer?.roleId, @@ -718,6 +724,7 @@ export const HumanReviewTab: FC = () => { const [isScorecardsLoading, setIsScorecardsLoading] = useState(true) const [loadError, setLoadError] = useState() const autoBackfilledReviewerTypesRef = useRef>({}) + const trimmedAdditionalMemberIdsRef = useRef>({}) const validatedScorecardSelectionsRef = useRef>({}) const challengeId = useWatch({ @@ -1274,7 +1281,19 @@ export const HumanReviewTab: FC = () => { ]) const sanitizeIntegerValue = useCallback( - (value: string): string => value.replace(/[^\d]/g, ''), + (value: string): string => { + const digitsOnly = value.replace(/[^\d]/g, '') + if (!digitsOnly) { + return '' + } + + const parsedValue = Number.parseInt(digitsOnly, 10) + if (!Number.isFinite(parsedValue)) { + return String(MAX_MANUAL_REVIEWER_COUNT) + } + + return String(Math.min(Math.max(parsedValue, 1), MAX_MANUAL_REVIEWER_COUNT)) + }, [], ) @@ -1311,24 +1330,48 @@ export const HumanReviewTab: FC = () => { const reviewerCount = getReviewerCount(reviewer) const maxAdditionalMembers = Math.max(0, reviewerCount - 1) const additionalMemberIds = getAdditionalMemberIds(reviewer) + const additionalMemberIdsFieldName = `reviewers.${fieldIndex}.additionalMemberIds` if (additionalMemberIds.length <= maxAdditionalMembers) { + if (additionalMemberIds.length > 0 || maxAdditionalMembers > 0) { + delete trimmedAdditionalMemberIdsRef.current[additionalMemberIdsFieldName] + } + return } + // React Hook Form can continue reporting a just-unregistered blank slot briefly. + // Avoid re-running the same trim when the watched value has not actually moved. + const trimSignature = JSON.stringify({ + additionalMemberIds, + maxAdditionalMembers, + }) + if (trimmedAdditionalMemberIdsRef.current[additionalMemberIdsFieldName] === trimSignature) { + return + } + + trimmedAdditionalMemberIdsRef.current[additionalMemberIdsFieldName] = trimSignature + const nextAdditionalMemberIds = additionalMemberIds.slice(0, maxAdditionalMembers) + const removedAdditionalMemberIds = additionalMemberIds.slice(maxAdditionalMembers) const keptMemberIds = toUniqueValues([ normalizeText(reviewer.memberId), ...nextAdditionalMemberIds, ]) const removedMemberIds = toUniqueValues( - additionalMemberIds - .slice(maxAdditionalMembers) + removedAdditionalMemberIds .filter(memberId => !keptMemberIds.includes(memberId)), ) + const hasRemovedAdditionalMemberValue = removedAdditionalMemberIds.some(Boolean) + + removedAdditionalMemberIds.forEach((_, removedIndex) => { + formContext.unregister( + `reviewers.${fieldIndex}.additionalMemberIds.${maxAdditionalMembers + removedIndex}` as any, + ) + }) formContext.setValue( - `reviewers.${fieldIndex}.additionalMemberIds` as any, + additionalMemberIdsFieldName as any, nextAdditionalMemberIds.length ? nextAdditionalMemberIds : undefined, @@ -1339,7 +1382,12 @@ export const HumanReviewTab: FC = () => { ) const roleId = resolveRoleIdForReviewer(reviewer) - if (!normalizedChallengeId || !roleId || !removedMemberIds.length) { + if ( + !hasRemovedAdditionalMemberValue + || !normalizedChallengeId + || !roleId + || !removedMemberIds.length + ) { return } @@ -1788,6 +1836,8 @@ export const HumanReviewTab: FC = () => {
= ( @@ -59,12 +62,13 @@ export const AssignmentDetailsModal: FC = ( const [durationMonths, setDurationMonths] = useState(props.initialValue?.durationMonths || '') const [errors, setErrors] = useState({}) const [otherRemarks, setOtherRemarks] = useState(props.initialValue?.otherRemarks || '') + const [paymentCycle, setPaymentCycle] = useState(props.initialValue?.paymentCycle || 'WEEKLY') const [ratePerHour, setRatePerHour] = useState(props.initialValue?.ratePerHour || '') const [startDate, setStartDate] = useState( deserializeTentativeAssignmentDate(props.initialValue?.startDate), ) - const [standardHoursPerWeek, setStandardHoursPerWeek] = useState( - props.initialValue?.standardHoursPerWeek || '', + const [standardHoursPerDay, setStandardHoursPerDay] = useState( + props.initialValue?.standardHoursPerDay || '', ) const timezone = useMemo( @@ -74,8 +78,19 @@ export const AssignmentDetailsModal: FC = ( [], ) const agreementRate = useMemo( - () => calculateAssignmentRatePerWeek(ratePerHour, standardHoursPerWeek), - [ratePerHour, standardHoursPerWeek], + () => { + const parsedStandardHoursPerDay = toPositiveNumberWithMaxDecimalPlaces( + standardHoursPerDay, + 2, + ) + + if (parsedStandardHoursPerDay === undefined) { + return '' + } + + return calculateAssignmentRatePerWeek(ratePerHour, parsedStandardHoursPerDay * 5) + }, + [ratePerHour, standardHoursPerDay], ) useEffect(() => { @@ -84,9 +99,10 @@ export const AssignmentDetailsModal: FC = ( } setDurationMonths(props.initialValue?.durationMonths || '') + setPaymentCycle(props.initialValue?.paymentCycle || 'WEEKLY') setRatePerHour(props.initialValue?.ratePerHour || '') setStartDate(deserializeTentativeAssignmentDate(props.initialValue?.startDate)) - setStandardHoursPerWeek(props.initialValue?.standardHoursPerWeek || '') + setStandardHoursPerDay(props.initialValue?.standardHoursPerDay || '') setOtherRemarks(props.initialValue?.otherRemarks || '') setErrors({}) }, [props.initialValue, props.open]) @@ -95,10 +111,13 @@ export const AssignmentDetailsModal: FC = ( const nextErrors: ValidationErrors = {} const parsedDurationMonths = toPositiveInteger(durationMonths) const parsedRatePerHour = toPositiveNumber(ratePerHour) - const parsedStandardHoursPerWeek = toPositiveNumberWithMaxDecimalPlaces( - standardHoursPerWeek, + const parsedStandardHoursPerDay = toPositiveNumberWithMaxDecimalPlaces( + standardHoursPerDay, 2, ) + const normalizedPaymentCycle = String(paymentCycle || 'WEEKLY') + .trim() + .toUpperCase() if (!startDate) { nextErrors.startDate = 'Engagement start date is required.' @@ -112,11 +131,15 @@ export const AssignmentDetailsModal: FC = ( nextErrors.ratePerHour = 'Rate per hour must be a positive number.' } - if (parsedStandardHoursPerWeek === undefined) { - nextErrors.standardHoursPerWeek = 'Standard hours per week must be a ' + if (parsedStandardHoursPerDay === undefined) { + nextErrors.standardHoursPerDay = 'Standard hours per day must be a ' + 'positive number with up to 2 decimal places.' } + if (!['WEEKLY', 'FORTNIGHTLY', 'MONTHLY'].includes(normalizedPaymentCycle)) { + nextErrors.paymentCycle = 'Payment cycle must be Weekly, Fortnightly, or Monthly.' + } + if (Object.keys(nextErrors).length > 0) { setErrors(nextErrors) return @@ -126,7 +149,7 @@ export const AssignmentDetailsModal: FC = ( !startDate || parsedDurationMonths === undefined || parsedRatePerHour === undefined - || parsedStandardHoursPerWeek === undefined + || parsedStandardHoursPerDay === undefined ) { return } @@ -136,17 +159,20 @@ export const AssignmentDetailsModal: FC = ( durationMonths: String(parsedDurationMonths), memberHandle: props.memberHandle || '', otherRemarks: otherRemarks.trim() || undefined, + paymentCycle: normalizedPaymentCycle, ratePerHour: parsedRatePerHour.toString(), - standardHoursPerWeek: String(parsedStandardHoursPerWeek), + standardHoursPerDay: String(parsedStandardHoursPerDay), + standardHoursPerWeek: String(Number((parsedStandardHoursPerDay * 5).toFixed(2))), startDate: serializeTentativeAssignmentDate(startDate), }) }, [ agreementRate, durationMonths, otherRemarks, + paymentCycle, props, ratePerHour, - standardHoursPerWeek, + standardHoursPerDay, startDate, ]) @@ -252,43 +278,52 @@ export const AssignmentDetailsModal: FC = (
{ - setStandardHoursPerWeek( + setStandardHoursPerDay( sanitizePositiveNumericInput(event.target.value, 2), ) setErrors(previous => ({ ...previous, - standardHoursPerWeek: undefined, + standardHoursPerDay: undefined, })) }} pattern='[0-9.]*' type='text' - value={standardHoursPerWeek} + value={standardHoursPerDay} /> - {errors.standardHoursPerWeek - ?

{errors.standardHoursPerWeek}

+ {errors.standardHoursPerDay + ?

{errors.standardHoursPerDay}

: undefined}
-
diff --git a/src/apps/work/src/pages/engagements/EngagementEditorPage/components/EngagementEditorForm.tsx b/src/apps/work/src/pages/engagements/EngagementEditorPage/components/EngagementEditorForm.tsx index 6c9ad5f06..88e773f87 100644 --- a/src/apps/work/src/pages/engagements/EngagementEditorPage/components/EngagementEditorForm.tsx +++ b/src/apps/work/src/pages/engagements/EngagementEditorPage/components/EngagementEditorForm.tsx @@ -1,4 +1,5 @@ /* eslint-disable react/jsx-no-bind */ +/* eslint-disable complexity */ import { FC, @@ -115,7 +116,9 @@ type SerializedAssignmentDetailsPayload = { durationMonths?: number memberHandle: string otherRemarks?: string + paymentCycle?: string ratePerHour: string + standardHoursPerDay?: number standardHoursPerWeek?: number startDate: string } @@ -224,8 +227,16 @@ function serializeAssignmentDetails( ? String(detail.otherRemarks) .trim() : undefined, + paymentCycle: detail.paymentCycle + ? String(detail.paymentCycle) + .trim() + .toUpperCase() + : 'WEEKLY', ratePerHour: String(detail.ratePerHour || '') .trim(), + standardHoursPerDay: detail.standardHoursPerDay + ? Number(detail.standardHoursPerDay) + : undefined, standardHoursPerWeek: detail.standardHoursPerWeek ? Number(detail.standardHoursPerWeek) : undefined, @@ -248,9 +259,18 @@ function toAssignmentDetailsValue(assignment: EngagementAssignment): AssignmentD otherRemarks: assignment.otherRemarks ? String(assignment.otherRemarks) : undefined, + paymentCycle: assignment.paymentCycle + ? String(assignment.paymentCycle) + : 'WEEKLY', ratePerHour: assignment.ratePerHour ? String(assignment.ratePerHour) : '', + standardHoursPerDay: + assignment.standardHoursPerDay !== undefined && assignment.standardHoursPerDay !== null + ? String(assignment.standardHoursPerDay) + : (assignment.standardHoursPerWeek !== undefined && assignment.standardHoursPerWeek !== null + ? String(Number(assignment.standardHoursPerWeek) / 5) + : ''), standardHoursPerWeek: assignment.standardHoursPerWeek !== undefined && assignment.standardHoursPerWeek !== null ? String(assignment.standardHoursPerWeek) diff --git a/src/apps/work/src/pages/engagements/EngagementEditorPage/components/EngagementPrivateSection.tsx b/src/apps/work/src/pages/engagements/EngagementEditorPage/components/EngagementPrivateSection.tsx index e11fa900f..c2a357e4a 100644 --- a/src/apps/work/src/pages/engagements/EngagementEditorPage/components/EngagementPrivateSection.tsx +++ b/src/apps/work/src/pages/engagements/EngagementEditorPage/components/EngagementPrivateSection.tsx @@ -18,9 +18,9 @@ import { } from '../../../../lib/components/form' import { formatAssignmentCurrency, - getAssignmentStandardHoursPerWeek, + getAssignmentPaymentCycle, + getAssignmentStandardHoursPerDay, } from '../../../../lib/utils' -import { formatCurrency } from '../../../../lib/utils/payment.utils' import { AssignmentDetailsFormValue, @@ -116,7 +116,9 @@ function createEmptyAssignmentDetails(): AssignmentDetailsFormValue { durationMonths: '', memberHandle: '', otherRemarks: undefined, + paymentCycle: 'WEEKLY', ratePerHour: '', + standardHoursPerDay: '', standardHoursPerWeek: '', startDate: '', } @@ -287,15 +289,15 @@ export const EngagementPrivateSection: FC = ( , - Hours/Wk: + Hours/Day: {' '} - {getAssignmentStandardHoursPerWeek(assignmentDetail) || '-'} + {getAssignmentStandardHoursPerDay(assignmentDetail) || '-'} , - Rate/Wk: + Payment Cycle: {' '} - {formatCurrency(assignmentDetail.agreementRate)} + {getAssignmentPaymentCycle(assignmentDetail)} {!isLockedAssignment ? ( diff --git a/src/apps/work/src/pages/engagements/EngagementPaymentPage/EngagementPaymentPage.tsx b/src/apps/work/src/pages/engagements/EngagementPaymentPage/EngagementPaymentPage.tsx index 71f76f637..5dcd11a33 100644 --- a/src/apps/work/src/pages/engagements/EngagementPaymentPage/EngagementPaymentPage.tsx +++ b/src/apps/work/src/pages/engagements/EngagementPaymentPage/EngagementPaymentPage.tsx @@ -1,5 +1,6 @@ /* eslint-disable max-len */ /* eslint-disable react/jsx-no-bind */ +/* eslint-disable complexity */ import { FC, @@ -51,6 +52,8 @@ import { import { calculateAssignmentRatePerWeek, deserializeTentativeAssignmentDate, + getAssignmentPaymentCycle, + getAssignmentStandardHoursPerDay, getCountableEngagementAssignments, normalizeAssignmentStatus, sanitizePositiveNumericInput, @@ -133,6 +136,32 @@ function toOptionalNumber(value: unknown): number | undefined { return parsedValue } +function formatPaymentCycle(value: unknown): string { + const normalizedCycle = String(value || 'WEEKLY') + .trim() + .toUpperCase() + + if (normalizedCycle === 'FORTNIGHTLY') { + return 'Fortnightly' + } + + if (normalizedCycle === 'MONTHLY') { + return 'Monthly' + } + + return 'Weekly' +} + +function toWeeklyHours(standardHoursPerDay: unknown): number | undefined { + const parsedValue = Number(standardHoursPerDay) + + if (!Number.isFinite(parsedValue) || parsedValue <= 0) { + return undefined + } + + return Number((parsedValue * 5).toFixed(2)) +} + function isAssignedStatus(status: string): boolean { return status .trim() @@ -226,7 +255,9 @@ function buildAssignmentDetailsPayloadEntry( appendPayloadStringField(payload, 'agreementRate', assignment.agreementRate) appendPayloadNumberField(payload, 'durationMonths', assignment.durationMonths) appendPayloadStringField(payload, 'otherRemarks', assignment.otherRemarks) + appendPayloadStringField(payload, 'paymentCycle', assignment.paymentCycle) appendPayloadStringField(payload, 'ratePerHour', assignment.ratePerHour) + appendPayloadNumberField(payload, 'standardHoursPerDay', assignment.standardHoursPerDay) appendPayloadNumberField(payload, 'standardHoursPerWeek', assignment.standardHoursPerWeek) appendPayloadStringField(payload, 'startDate', assignment.startDate) @@ -237,8 +268,10 @@ interface EditAssignmentPayload { agreementRate: string durationMonths?: number otherRemarks?: string + paymentCycle: string ratePerHour: string startDate: string + standardHoursPerDay: number standardHoursPerWeek: number } @@ -287,9 +320,10 @@ interface EditAssignmentModalProps { interface EditAssignmentErrors { durationMonths?: string + paymentCycle?: string ratePerHour?: string startDate?: string - standardHoursPerWeek?: string + standardHoursPerDay?: string } export const EditAssignmentModal: FC = ( @@ -302,19 +336,39 @@ export const EditAssignmentModal: FC = ( ) const [errors, setErrors] = useState({}) const [otherRemarks, setOtherRemarks] = useState(props.assignment?.otherRemarks || '') + const [paymentCycle, setPaymentCycle] = useState(getAssignmentPaymentCycle(props.assignment || {})) const [ratePerHour, setRatePerHour] = useState(props.assignment?.ratePerHour || '') const [startDate, setStartDate] = useState( deserializeTentativeAssignmentDate(props.assignment?.startDate), ) - const [standardHoursPerWeek, setStandardHoursPerWeek] = useState( - props.assignment?.standardHoursPerWeek !== undefined && props.assignment?.standardHoursPerWeek !== null - ? String(props.assignment.standardHoursPerWeek) + const [standardHoursPerDay, setStandardHoursPerDay] = useState( + getAssignmentStandardHoursPerDay(props.assignment || {}) !== undefined + ? String(getAssignmentStandardHoursPerDay(props.assignment || {})) : '', ) + const weeklyHours = useMemo( + () => { + if (!standardHoursPerDay) { + return '' + } + + const parsedStandardHoursPerDay = toPositiveNumberWithMaxDecimalPlaces( + standardHoursPerDay, + 2, + ) + + if (parsedStandardHoursPerDay === undefined) { + return '' + } + + return String(Number((parsedStandardHoursPerDay * 5).toFixed(2))) + }, + [standardHoursPerDay], + ) const agreementRate = useMemo( - () => calculateAssignmentRatePerWeek(ratePerHour, standardHoursPerWeek), - [ratePerHour, standardHoursPerWeek], + () => calculateAssignmentRatePerWeek(ratePerHour, weeklyHours), + [ratePerHour, weeklyHours], ) const isSubmitting = props.isSubmitting === true @@ -326,11 +380,12 @@ export const EditAssignmentModal: FC = ( ) setErrors({}) setOtherRemarks(props.assignment?.otherRemarks || '') + setPaymentCycle(getAssignmentPaymentCycle(props.assignment || {})) setRatePerHour(props.assignment?.ratePerHour || '') setStartDate(deserializeTentativeAssignmentDate(props.assignment?.startDate)) - setStandardHoursPerWeek( - props.assignment?.standardHoursPerWeek !== undefined && props.assignment?.standardHoursPerWeek !== null - ? String(props.assignment.standardHoursPerWeek) + setStandardHoursPerDay( + getAssignmentStandardHoursPerDay(props.assignment || {}) !== undefined + ? String(getAssignmentStandardHoursPerDay(props.assignment || {})) : '', ) }, [props.assignment]) @@ -355,10 +410,13 @@ export const EditAssignmentModal: FC = ( ? toPositiveInteger(durationMonths) : undefined const parsedRatePerHour = toPositiveNumber(ratePerHour) - const parsedStandardHoursPerWeek = toPositiveNumberWithMaxDecimalPlaces( - standardHoursPerWeek, + const parsedStandardHoursPerDay = toPositiveNumberWithMaxDecimalPlaces( + standardHoursPerDay, 2, ) + const normalizedPaymentCycle = String(paymentCycle || 'WEEKLY') + .trim() + .toUpperCase() if (!startDate) { nextErrors.startDate = 'Billing start date is required.' @@ -372,11 +430,15 @@ export const EditAssignmentModal: FC = ( nextErrors.ratePerHour = 'Rate per hour must be a positive number.' } - if (parsedStandardHoursPerWeek === undefined) { - nextErrors.standardHoursPerWeek = 'Standard hours per week must be a ' + if (parsedStandardHoursPerDay === undefined) { + nextErrors.standardHoursPerDay = 'Standard hours per day must be a ' + 'positive number with up to 2 decimal places.' } + if (!['WEEKLY', 'FORTNIGHTLY', 'MONTHLY'].includes(normalizedPaymentCycle)) { + nextErrors.paymentCycle = 'Payment cycle must be Weekly, Fortnightly, or Monthly.' + } + if (Object.keys(nextErrors).length > 0) { setErrors(nextErrors) return @@ -385,16 +447,24 @@ export const EditAssignmentModal: FC = ( if ( !startDate || parsedRatePerHour === undefined - || parsedStandardHoursPerWeek === undefined + || parsedStandardHoursPerDay === undefined ) { return } + const parsedStandardHoursPerWeek = toWeeklyHours(parsedStandardHoursPerDay) + + if (parsedStandardHoursPerWeek === undefined) { + return + } + await props.onConfirm({ agreementRate, durationMonths: parsedDurationMonths, otherRemarks: otherRemarks.trim() || undefined, + paymentCycle: normalizedPaymentCycle, ratePerHour: parsedRatePerHour.toString(), + standardHoursPerDay: parsedStandardHoursPerDay, standardHoursPerWeek: parsedStandardHoursPerWeek, startDate: serializeTentativeAssignmentDate(startDate), }) @@ -404,10 +474,11 @@ export const EditAssignmentModal: FC = ( agreementRate, durationMonths, otherRemarks, + paymentCycle, props, ratePerHour, resetState, - standardHoursPerWeek, + standardHoursPerDay, startDate, ]) @@ -508,41 +579,53 @@ export const EditAssignmentModal: FC = (
{ - setStandardHoursPerWeek( + setStandardHoursPerDay( sanitizePositiveNumericInput(event.target.value, 2), ) setErrors(previous => ({ ...previous, - standardHoursPerWeek: undefined, + standardHoursPerDay: undefined, })) }} pattern='[0-9.]*' type='text' - value={standardHoursPerWeek} + value={standardHoursPerDay} /> - {errors.standardHoursPerWeek - ?

{errors.standardHoursPerWeek}

+ {errors.standardHoursPerDay + ?

{errors.standardHoursPerDay}

: undefined}
-
@@ -918,16 +1001,16 @@ export const EngagementPaymentPage: FC = () => {
- Standard Hours Per Week + Standard Hours Per Day - {assignment.standardHoursPerWeek || '-'} + {getAssignmentStandardHoursPerDay(assignment) || '-'}
- Rate Per Week - {formatCurrency(assignment.agreementRate)} + Payment Cycle + {formatPaymentCycle(assignment.paymentCycle)}
diff --git a/src/apps/work/src/pages/invitations/ProjectInvitationsPage/ProjectInvitationsPage.spec.tsx b/src/apps/work/src/pages/invitations/ProjectInvitationsPage/ProjectInvitationsPage.spec.tsx index 0e41f228e..96d0a6e18 100644 --- a/src/apps/work/src/pages/invitations/ProjectInvitationsPage/ProjectInvitationsPage.spec.tsx +++ b/src/apps/work/src/pages/invitations/ProjectInvitationsPage/ProjectInvitationsPage.spec.tsx @@ -212,6 +212,10 @@ function renderPage(options: RenderPageOptions = {}): jest.Mock { element={} path='/projects/:projectId/invitations/:action?' /> + } + path='/projects/:projectId/invitation/:action?' + /> Challenges Page} path='/projects/:projectId/challenges' @@ -382,4 +386,27 @@ describe('ProjectInvitationsPage', () => { .toHaveBeenCalledWith('Update failed') }) }) + + it('refuses the current user invite from the legacy email invitation route', async () => { + mockedCheckIsUserInvitedToProject.mockReturnValue(pendingInvitation) + + renderPage({ + route: '/projects/200/invitation/refused?source=email', + }) + + await waitFor(() => { + expect(mockedUpdateProjectMemberInvite) + .toHaveBeenCalledWith( + '200', + '77', + 'refused', + 'email', + ) + }) + + await waitFor(() => { + expect(screen.queryByText('Invitation Declined')) + .not.toBeNull() + }) + }) }) diff --git a/src/apps/work/src/work-app.routes.tsx b/src/apps/work/src/work-app.routes.tsx index 46ce6e58e..d85ffc9dd 100644 --- a/src/apps/work/src/work-app.routes.tsx +++ b/src/apps/work/src/work-app.routes.tsx @@ -401,6 +401,12 @@ export const workRoutes: ReadonlyArray = [ route: '/projects/:projectId/decline/:inviteId', title: 'Project Invitations', }, + { + authRequired: true, + element: , + route: '/projects/:projectId/invitation/:action?', + title: 'Project Invitations', + }, { authRequired: true, element: , diff --git a/src/libs/shared/lib/utils/index.ts b/src/libs/shared/lib/utils/index.ts index 9ffcb9309..ebdebdc1b 100644 --- a/src/libs/shared/lib/utils/index.ts +++ b/src/libs/shared/lib/utils/index.ts @@ -6,3 +6,4 @@ export * from './industry' export * from './rich-text' export * from './string' export * from './text-format' +export * from './payment-type-filter.utils' diff --git a/src/libs/shared/lib/utils/payment-type-filter.utils.ts b/src/libs/shared/lib/utils/payment-type-filter.utils.ts new file mode 100644 index 000000000..99911c510 --- /dev/null +++ b/src/libs/shared/lib/utils/payment-type-filter.utils.ts @@ -0,0 +1,20 @@ +export const PAYMENT_TYPE_CATEGORY_BY_FILTER_VALUE: Record = { + Contest: 'CONTEST_PAYMENT', + Copilot: 'COPILOT_PAYMENT', + Engagement: 'ENGAGEMENT_PAYMENT', + 'Review Board': 'REVIEW_BOARD_PAYMENT', + Task: 'TASK_PAYMENT', +} + +export const PAYMENT_TYPE_FILTER_VALUE_BY_CATEGORY: Record = Object.fromEntries( + Object.entries(PAYMENT_TYPE_CATEGORY_BY_FILTER_VALUE) + .map(([filterValue, category]) => [category, filterValue]), +) + +export function toPaymentTypeFilterValues(categories: string[]): string[] { + return categories.map(category => PAYMENT_TYPE_FILTER_VALUE_BY_CATEGORY[category] ?? category) +} + +export function toPaymentTypeCategories(filterValues: string[]): string[] { + return filterValues.map(value => PAYMENT_TYPE_CATEGORY_BY_FILTER_VALUE[value] ?? value) +} diff --git a/src/libs/ui/lib/components/form/form-groups/form-input/input-select-react/InputSelectReact.module.scss b/src/libs/ui/lib/components/form/form-groups/form-input/input-select-react/InputSelectReact.module.scss index 761a49e51..3c7200820 100644 --- a/src/libs/ui/lib/components/form/form-groups/form-input/input-select-react/InputSelectReact.module.scss +++ b/src/libs/ui/lib/components/form/form-groups/form-input/input-select-react/InputSelectReact.module.scss @@ -86,7 +86,7 @@ .sel { &:global(__menu-portal).sel:global(__menu-portal) { - z-index: 1001; + z-index: 10001; } &:global(__menu) { width: 100%; diff --git a/src/libs/ui/lib/components/form/form-groups/form-input/input-select/InputSelect.tsx b/src/libs/ui/lib/components/form/form-groups/form-input/input-select/InputSelect.tsx index c94fc36c9..a4a7b9f01 100644 --- a/src/libs/ui/lib/components/form/form-groups/form-input/input-select/InputSelect.tsx +++ b/src/libs/ui/lib/components/form/form-groups/form-input/input-select/InputSelect.tsx @@ -43,6 +43,7 @@ interface InputSelectProps { readonly tabIndex?: number readonly value?: string readonly classNameWrapper?: string + readonly displayValueInTrigger?: boolean readonly preventAutoFocus?: boolean } @@ -76,10 +77,27 @@ const InputSelect: FC = (props: InputSelectProps) => { const selectedOption: InputSelectOption | undefined = props.options.find(option => option.value === props.value) - const label: (option: InputSelectOption) => ReactNode = (option?: InputSelectOption) => ( + const menuLabel: (option: InputSelectOption) => ReactNode = (option?: InputSelectOption) => ( option ? option.label ?? option.value : '' ) + const triggerLabel: (option?: InputSelectOption) => ReactNode = (option?: InputSelectOption) => { + if (!option) { + return '' + } + + if ( + props.displayValueInTrigger + && option.label !== undefined + && option.value + && option.value !== option.label + ) { + return option.value + } + + return menuLabel(option) + } + const toggleMenu = (toggle?: boolean): void => { setTimeout(setMenuIsVisible, 150, (wasVisible: boolean) => { const isVisible = typeof toggle === 'boolean' ? toggle : !wasVisible @@ -170,7 +188,7 @@ const InputSelect: FC = (props: InputSelectProps) => { ref={buttonRef} > - {selectedOption ? label(selectedOption) : ''} + {selectedOption ? triggerLabel(selectedOption) : ''} {!selectedOption && !!props.placeholder @@ -204,7 +222,7 @@ const InputSelect: FC = (props: InputSelectProps) => { onClick={select(option)} key={option.value} > - {label(option)} + {menuLabel(option)} ))} diff --git a/src/libs/ui/lib/components/table/table-functions/table.functions.ts b/src/libs/ui/lib/components/table/table-functions/table.functions.ts index 2bb6f161c..0e1e92880 100644 --- a/src/libs/ui/lib/components/table/table-functions/table.functions.ts +++ b/src/libs/ui/lib/components/table/table-functions/table.functions.ts @@ -1,3 +1,5 @@ +import _ from 'lodash' + import { Sort } from '~/apps/admin/src/platform/gamification-admin/src/game-lib' import { TableColumn } from '../table-column.model' @@ -47,24 +49,32 @@ export function getSorted( return direction === 'asc' ? a - b : b - a } + function getValue(obj: T, path: string): unknown { + return path.includes('.') ? _.get(obj, path) : obj[path] + } + if (sortColumn.type === 'money' || sortColumn.type === 'number' || sortColumn.type === 'numberElement') { return sortedData - .sort((a: T, b: T) => sortNumbers(+a[sort.fieldName], +b[sort.fieldName], sort.direction)) + .sort((a: T, b: T) => sortNumbers( + Number(getValue(a, sort.fieldName)), + Number(getValue(b, sort.fieldName)), + sort.direction, + )) } if (sortColumn.type === 'date') { return sortedData .sort((a: T, b: T) => { - const aDate = new Date(a[sort.fieldName]) - const bDate = new Date(b[sort.fieldName]) + const aDate = new Date(getValue(a, sort.fieldName) as string) + const bDate = new Date(getValue(b, sort.fieldName) as string) return sortNumbers(aDate.getTime(), bDate.getTime(), sort.direction) }) } return sortedData .sort((a: T, b: T) => { - const aField: unknown = a[sort.fieldName] - const bField: unknown = b[sort.fieldName] + const aField: unknown = getValue(a, sort.fieldName) + const bField: unknown = getValue(b, sort.fieldName) // Keep nullish values at the bottom for both sort directions. const aValue = String(aField ?? '') diff --git a/yarn.lock b/yarn.lock index cfdce4785..d7bb311c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13,12 +13,11 @@ integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== "@apideck/better-ajv-errors@^0.3.1": - version "0.3.6" - resolved "https://registry.yarnpkg.com/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz#957d4c28e886a64a8141f7522783be65733ff097" - integrity sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA== + version "0.3.7" + resolved "https://registry.yarnpkg.com/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.7.tgz#89238f689d81a644139a47e0ebc2e236bca1ff2c" + integrity sha512-TajUJwGWbDwkCx/CZi7tRE8PVB7simCvKJfHUsSdvps+aTM/PDPP4gkLmKnc+x3CE//y9i/nj74GqdL/hwk7Iw== dependencies: - json-schema "^0.4.0" - jsonpointer "^5.0.0" + jsonpointer "^5.0.1" leven "^3.1.0" "@aw-web-design/x-default-browser@1.4.126": @@ -28,34 +27,34 @@ dependencies: default-browser-id "3.0.0" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.27.1", "@babel/code-frame@^7.8.3": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" - integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.27.1", "@babel/code-frame@^7.28.6", "@babel/code-frame@^7.29.0", "@babel/code-frame@^7.8.3": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.0.tgz#7cd7a59f15b3cc0dcd803038f7792712a7d0b15c" + integrity sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw== dependencies: - "@babel/helper-validator-identifier" "^7.27.1" + "@babel/helper-validator-identifier" "^7.28.5" js-tokens "^4.0.0" picocolors "^1.1.1" -"@babel/compat-data@^7.27.2", "@babel/compat-data@^7.27.7", "@babel/compat-data@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.5.tgz#a8a4962e1567121ac0b3b487f52107443b455c7f" - integrity sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA== +"@babel/compat-data@^7.28.6", "@babel/compat-data@^7.29.3": + version "7.29.3" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.3.tgz#e3f5347f0589596c91d227ccb6a541d37fb1307b" + integrity sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg== "@babel/core@^7.1.0", "@babel/core@^7.11.1", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.16.0", "@babel/core@^7.18.9", "@babel/core@^7.23.0", "@babel/core@^7.23.2", "@babel/core@^7.23.9", "@babel/core@^7.28.5", "@babel/core@^7.7.2", "@babel/core@^7.7.5", "@babel/core@^7.8.0": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.5.tgz#4c81b35e51e1b734f510c99b07dfbc7bbbb48f7e" - integrity sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw== - dependencies: - "@babel/code-frame" "^7.27.1" - "@babel/generator" "^7.28.5" - "@babel/helper-compilation-targets" "^7.27.2" - "@babel/helper-module-transforms" "^7.28.3" - "@babel/helpers" "^7.28.4" - "@babel/parser" "^7.28.5" - "@babel/template" "^7.27.2" - "@babel/traverse" "^7.28.5" - "@babel/types" "^7.28.5" + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.0.tgz#5286ad785df7f79d656e88ce86e650d16ca5f322" + integrity sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA== + dependencies: + "@babel/code-frame" "^7.29.0" + "@babel/generator" "^7.29.0" + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-module-transforms" "^7.28.6" + "@babel/helpers" "^7.28.6" + "@babel/parser" "^7.29.0" + "@babel/template" "^7.28.6" + "@babel/traverse" "^7.29.0" + "@babel/types" "^7.29.0" "@jridgewell/remapping" "^2.3.5" convert-source-map "^2.0.0" debug "^4.1.0" @@ -64,57 +63,57 @@ semver "^6.3.1" "@babel/eslint-parser@^7.16.3": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.28.5.tgz#0b8883a4a1c2cbed7b3cd9d7765d80e8f480b9ae" - integrity sha512-fcdRcWahONYo+JRnJg1/AekOacGvKx12Gu0qXJXFi2WBqQA1i7+O5PaxRB7kxE/Op94dExnCiiar6T09pvdHpA== + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.28.6.tgz#6a294a4add732ebe7ded8a8d2792dd03dd81dc3f" + integrity sha512-QGmsKi2PBO/MHSQk+AAgA9R6OHQr+VqnniFE0eMWZcVcfBZoA2dKn2hUsl3Csg/Plt9opRUWdY7//VXsrIlEiA== dependencies: "@nicolo-ribaudo/eslint-scope-5-internals" "5.1.1-v1" eslint-visitor-keys "^2.1.0" semver "^6.3.1" -"@babel/generator@^7.23.0", "@babel/generator@^7.28.5", "@babel/generator@^7.7.2": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.5.tgz#712722d5e50f44d07bc7ac9fe84438742dd61298" - integrity sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ== +"@babel/generator@^7.23.0", "@babel/generator@^7.29.0", "@babel/generator@^7.7.2": + version "7.29.1" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.1.tgz#d09876290111abbb00ef962a7b83a5307fba0d50" + integrity sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw== dependencies: - "@babel/parser" "^7.28.5" - "@babel/types" "^7.28.5" + "@babel/parser" "^7.29.0" + "@babel/types" "^7.29.0" "@jridgewell/gen-mapping" "^0.3.12" "@jridgewell/trace-mapping" "^0.3.28" jsesc "^3.0.2" -"@babel/helper-annotate-as-pure@^7.18.6", "@babel/helper-annotate-as-pure@^7.22.5", "@babel/helper-annotate-as-pure@^7.27.1", "@babel/helper-annotate-as-pure@^7.27.3": +"@babel/helper-annotate-as-pure@^7.18.6", "@babel/helper-annotate-as-pure@^7.25.9", "@babel/helper-annotate-as-pure@^7.27.1", "@babel/helper-annotate-as-pure@^7.27.3": version "7.27.3" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz#f31fd86b915fc4daf1f3ac6976c59be7084ed9c5" integrity sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg== dependencies: "@babel/types" "^7.27.3" -"@babel/helper-compilation-targets@^7.27.1", "@babel/helper-compilation-targets@^7.27.2": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" - integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== +"@babel/helper-compilation-targets@^7.27.1", "@babel/helper-compilation-targets@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz#32c4a3f41f12ed1532179b108a4d746e105c2b25" + integrity sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA== dependencies: - "@babel/compat-data" "^7.27.2" + "@babel/compat-data" "^7.28.6" "@babel/helper-validator-option" "^7.27.1" browserslist "^4.24.0" lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.21.0", "@babel/helper-create-class-features-plugin@^7.27.1", "@babel/helper-create-class-features-plugin@^7.28.3", "@babel/helper-create-class-features-plugin@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz#472d0c28028850968979ad89f173594a6995da46" - integrity sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ== +"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.21.0", "@babel/helper-create-class-features-plugin@^7.28.6": + version "7.29.3" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.3.tgz#67328947d956f06fc7b48def269bf0489155fd42" + integrity sha512-RpLYy2sb51oNLjuu1iD3bwBqCBWUzjO0ocp+iaCP/lJtb2CPLcnC2Fftw+4sAzaMELGeWTgExSKADbdo0GFVzA== dependencies: "@babel/helper-annotate-as-pure" "^7.27.3" "@babel/helper-member-expression-to-functions" "^7.28.5" "@babel/helper-optimise-call-expression" "^7.27.1" - "@babel/helper-replace-supers" "^7.27.1" + "@babel/helper-replace-supers" "^7.28.6" "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" - "@babel/traverse" "^7.28.5" + "@babel/traverse" "^7.29.0" semver "^6.3.1" -"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.27.1": +"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.27.1", "@babel/helper-create-regexp-features-plugin@^7.28.5": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz#7c1ddd64b2065c7f78034b25b43346a7e19ed997" integrity sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw== @@ -123,23 +122,23 @@ regexpu-core "^6.3.1" semver "^6.3.1" -"@babel/helper-define-polyfill-provider@^0.6.5": - version "0.6.5" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz#742ccf1cb003c07b48859fc9fa2c1bbe40e5f753" - integrity sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg== +"@babel/helper-define-polyfill-provider@^0.6.5", "@babel/helper-define-polyfill-provider@^0.6.8": + version "0.6.8" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz#cf1e4462b613f2b54c41e6ff758d5dfcaa2c85d1" + integrity sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA== dependencies: - "@babel/helper-compilation-targets" "^7.27.2" - "@babel/helper-plugin-utils" "^7.27.1" - debug "^4.4.1" + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + debug "^4.4.3" lodash.debounce "^4.0.8" - resolve "^1.22.10" + resolve "^1.22.11" "@babel/helper-globals@^7.28.0": version "7.28.0" resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== -"@babel/helper-member-expression-to-functions@^7.27.1", "@babel/helper-member-expression-to-functions@^7.28.5": +"@babel/helper-member-expression-to-functions@^7.28.5": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz#f3e07a10be37ed7a63461c63e6929575945a6150" integrity sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg== @@ -147,22 +146,22 @@ "@babel/traverse" "^7.28.5" "@babel/types" "^7.28.5" -"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.22.5", "@babel/helper-module-imports@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204" - integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== +"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.25.9", "@babel/helper-module-imports@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz#60632cbd6ffb70b22823187201116762a03e2d5c" + integrity sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw== dependencies: - "@babel/traverse" "^7.27.1" - "@babel/types" "^7.27.1" + "@babel/traverse" "^7.28.6" + "@babel/types" "^7.28.6" -"@babel/helper-module-transforms@^7.27.1", "@babel/helper-module-transforms@^7.28.3": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz#a2b37d3da3b2344fe085dab234426f2b9a2fa5f6" - integrity sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw== +"@babel/helper-module-transforms@^7.27.1", "@babel/helper-module-transforms@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz#9312d9d9e56edc35aeb6e95c25d4106b50b9eb1e" + integrity sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA== dependencies: - "@babel/helper-module-imports" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" - "@babel/traverse" "^7.28.3" + "@babel/helper-module-imports" "^7.28.6" + "@babel/helper-validator-identifier" "^7.28.5" + "@babel/traverse" "^7.28.6" "@babel/helper-optimise-call-expression@^7.27.1": version "7.27.1" @@ -171,10 +170,10 @@ dependencies: "@babel/types" "^7.27.1" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.27.1", "@babel/helper-plugin-utils@^7.8.0": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" - integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.27.1", "@babel/helper-plugin-utils@^7.28.6", "@babel/helper-plugin-utils@^7.8.0": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz#6f13ea251b68c8532e985fd532f28741a8af9ac8" + integrity sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug== "@babel/helper-remap-async-to-generator@^7.27.1": version "7.27.1" @@ -185,14 +184,14 @@ "@babel/helper-wrap-function" "^7.27.1" "@babel/traverse" "^7.27.1" -"@babel/helper-replace-supers@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz#b1ed2d634ce3bdb730e4b52de30f8cccfd692bc0" - integrity sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA== +"@babel/helper-replace-supers@^7.27.1", "@babel/helper-replace-supers@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz#94aa9a1d7423a00aead3f204f78834ce7d53fe44" + integrity sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg== dependencies: - "@babel/helper-member-expression-to-functions" "^7.27.1" + "@babel/helper-member-expression-to-functions" "^7.28.5" "@babel/helper-optimise-call-expression" "^7.27.1" - "@babel/traverse" "^7.27.1" + "@babel/traverse" "^7.28.6" "@babel/helper-skip-transparent-expression-wrappers@^7.20.0", "@babel/helper-skip-transparent-expression-wrappers@^7.27.1": version "7.27.1" @@ -207,7 +206,7 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== -"@babel/helper-validator-identifier@^7.19.1", "@babel/helper-validator-identifier@^7.27.1", "@babel/helper-validator-identifier@^7.28.5": +"@babel/helper-validator-identifier@^7.19.1", "@babel/helper-validator-identifier@^7.28.5": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4" integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== @@ -218,28 +217,28 @@ integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== "@babel/helper-wrap-function@^7.27.1": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz#fe4872092bc1438ffd0ce579e6f699609f9d0a7a" - integrity sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g== + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz#4e349ff9222dab69a93a019cc296cdd8442e279a" + integrity sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ== dependencies: - "@babel/template" "^7.27.2" - "@babel/traverse" "^7.28.3" - "@babel/types" "^7.28.2" + "@babel/template" "^7.28.6" + "@babel/traverse" "^7.28.6" + "@babel/types" "^7.28.6" -"@babel/helpers@^7.28.4": - version "7.28.4" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.4.tgz#fe07274742e95bdf7cf1443593eeb8926ab63827" - integrity sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w== +"@babel/helpers@^7.28.6": + version "7.29.2" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.29.2.tgz#9cfbccb02b8e229892c0b07038052cc1a8709c49" + integrity sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw== dependencies: - "@babel/template" "^7.27.2" - "@babel/types" "^7.28.4" + "@babel/template" "^7.28.6" + "@babel/types" "^7.29.0" -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.0", "@babel/parser@^7.23.9", "@babel/parser@^7.27.2", "@babel/parser@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.5.tgz#0b0225ee90362f030efd644e8034c99468893b08" - integrity sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ== +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.0", "@babel/parser@^7.23.9", "@babel/parser@^7.28.6", "@babel/parser@^7.29.0": + version "7.29.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.3.tgz#116f70a77958307fceac27747573032f8a62f88e" + integrity sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA== dependencies: - "@babel/types" "^7.28.5" + "@babel/types" "^7.29.0" "@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.28.5": version "7.28.5" @@ -263,6 +262,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" +"@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@^7.29.3": + version "7.29.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array/-/plugin-bugfix-safari-rest-destructuring-rhs-array-7.29.3.tgz#2e14f9335803d892ccb67ef487e23cf9726156fe" + integrity sha512-SRS46DFR4HqzUzCVgi90/xMoL+zeBDBvWdKYXSEzh79kXswNFEglUpMKxR04//dPqwYXWUBJ3mpUd933ru9Kmg== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz#e134a5479eb2ba9c02714e8c1ebf1ec9076124fd" @@ -272,13 +279,13 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" "@babel/plugin-transform-optional-chaining" "^7.27.1" -"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.28.3": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz#373f6e2de0016f73caf8f27004f61d167743742a" - integrity sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw== +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz#0e8289cec28baaf05d54fd08d81ae3676065f69f" + integrity sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/traverse" "^7.28.3" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/traverse" "^7.28.6" "@babel/plugin-proposal-class-properties@^7.16.0": version "7.18.6" @@ -289,13 +296,13 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-proposal-decorators@^7.16.4": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.28.0.tgz#419c8acc31088e05a774344c021800f7ddc39bf0" - integrity sha512-zOiZqvANjWDUaUS9xMxbMcK/Zccztbe/6ikvUXaG9nsPH3w6qh5UaPGAnirI/WhIbZ8m3OHU0ReyPrknG+ZKeg== + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.29.0.tgz#d159f26f78740e47bf3ef075882b155b2d54ca81" + integrity sha512-CVBVv3VY/XRMxRYq5dwr2DS7/MvqPm23cOCjbwNnVrfOqcWlnefua1uUs0sjdKOGjvPUG633o07uWzJq4oI6dA== dependencies: - "@babel/helper-create-class-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/plugin-syntax-decorators" "^7.27.1" + "@babel/helper-create-class-features-plugin" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/plugin-syntax-decorators" "^7.28.6" "@babel/plugin-proposal-nullish-coalescing-operator@^7.16.0": version "7.18.6" @@ -373,33 +380,33 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-decorators@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.27.1.tgz#ee7dd9590aeebc05f9d4c8c0560007b05979a63d" - integrity sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A== +"@babel/plugin-syntax-decorators@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.28.6.tgz#8c3293a0fef033e4c786b35ce1e159fc1d676153" + integrity sha512-71EYI0ONURHJBL4rSFXnITXqXrrY8q4P0q006DPfN+Rk+ASM+++IBXem/ruokgBZR8YNEWZ8R6B+rCb8VcUTqA== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-syntax-flow@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.27.1.tgz#6c83cf0d7d635b716827284b7ecd5aead9237662" - integrity sha512-p9OkPbZ5G7UT1MofwYFigGebnrzGJacoBSQM0/6bi/PUMVE+qlWDD/OalvQKbwgQzU6dl0xAv6r4X7Jme0RYxA== + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.28.6.tgz#447559a225e66c4cd477a3ffb1a74d8c1fe25a62" + integrity sha512-D+OrJumc9McXNEBI/JmFnc/0uCM2/Y3PEBG3gfV3QIYkKv5pvnpzFrl1kYCrcHJP8nOeFB/SHi1IHz29pNGuew== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-syntax-import-assertions@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz#88894aefd2b03b5ee6ad1562a7c8e1587496aecd" - integrity sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg== +"@babel/plugin-syntax-import-assertions@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz#ae9bc1923a6ba527b70104dd2191b0cd872c8507" + integrity sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-syntax-import-attributes@^7.24.7", "@babel/plugin-syntax-import-attributes@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz#34c017d54496f9b11b61474e7ea3dfd5563ffe07" - integrity sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww== +"@babel/plugin-syntax-import-attributes@^7.24.7", "@babel/plugin-syntax-import-attributes@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz#b71d5914665f60124e133696f17cd7669062c503" + integrity sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-syntax-import-meta@^7.10.4": version "7.10.4" @@ -415,12 +422,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-jsx@^7.0.0", "@babel/plugin-syntax-jsx@^7.22.5", "@babel/plugin-syntax-jsx@^7.27.1", "@babel/plugin-syntax-jsx@^7.7.2": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz#2f9beb5eff30fa507c5532d107daac7b888fa34c" - integrity sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w== +"@babel/plugin-syntax-jsx@^7.0.0", "@babel/plugin-syntax-jsx@^7.25.9", "@babel/plugin-syntax-jsx@^7.27.1", "@babel/plugin-syntax-jsx@^7.28.6", "@babel/plugin-syntax-jsx@^7.7.2": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz#f8ca28bbd84883b5fea0e447c635b81ba73997ee" + integrity sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-syntax-logical-assignment-operators@^7.10.4": version "7.10.4" @@ -478,12 +485,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-typescript@^7.27.1", "@babel/plugin-syntax-typescript@^7.7.2": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz#5147d29066a793450f220c63fa3a9431b7e6dd18" - integrity sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ== +"@babel/plugin-syntax-typescript@^7.28.6", "@babel/plugin-syntax-typescript@^7.7.2": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz#c7b2ddf1d0a811145b1de800d1abd146af92e3a2" + integrity sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-syntax-unicode-sets-regex@^7.18.6": version "7.18.6" @@ -500,22 +507,22 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-async-generator-functions@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz#1276e6c7285ab2cd1eccb0bc7356b7a69ff842c2" - integrity sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q== +"@babel/plugin-transform-async-generator-functions@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz#63ed829820298f0bf143d5a4a68fb8c06ffd742f" + integrity sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/helper-remap-async-to-generator" "^7.27.1" - "@babel/traverse" "^7.28.0" + "@babel/traverse" "^7.29.0" -"@babel/plugin-transform-async-to-generator@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz#9a93893b9379b39466c74474f55af03de78c66e7" - integrity sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA== +"@babel/plugin-transform-async-to-generator@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz#bd97b42237b2d1bc90d74bcb486c39be5b4d7e77" + integrity sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g== dependencies: - "@babel/helper-module-imports" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-module-imports" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/helper-remap-async-to-generator" "^7.27.1" "@babel/plugin-transform-block-scoped-functions@^7.27.1": @@ -525,50 +532,50 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-block-scoping@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz#e0d3af63bd8c80de2e567e690a54e84d85eb16f6" - integrity sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g== +"@babel/plugin-transform-block-scoping@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz#e1ef5633448c24e76346125c2534eeb359699a99" + integrity sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-transform-class-properties@^7.22.5", "@babel/plugin-transform-class-properties@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz#dd40a6a370dfd49d32362ae206ddaf2bb082a925" - integrity sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA== +"@babel/plugin-transform-class-properties@^7.22.5", "@babel/plugin-transform-class-properties@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz#d274a4478b6e782d9ea987fda09bdb6d28d66b72" + integrity sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw== dependencies: - "@babel/helper-create-class-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-create-class-features-plugin" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-transform-class-static-block@^7.28.3": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz#d1b8e69b54c9993bc558203e1f49bfc979bfd852" - integrity sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg== +"@babel/plugin-transform-class-static-block@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz#1257491e8259c6d125ac4d9a6f39f9d2bf3dba70" + integrity sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ== dependencies: - "@babel/helper-create-class-features-plugin" "^7.28.3" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-create-class-features-plugin" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-transform-classes@^7.28.4": - version "7.28.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz#75d66175486788c56728a73424d67cbc7473495c" - integrity sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA== +"@babel/plugin-transform-classes@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz#8f6fb79ba3703978e701ce2a97e373aae7dda4b7" + integrity sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q== dependencies: "@babel/helper-annotate-as-pure" "^7.27.3" - "@babel/helper-compilation-targets" "^7.27.2" + "@babel/helper-compilation-targets" "^7.28.6" "@babel/helper-globals" "^7.28.0" - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/helper-replace-supers" "^7.27.1" - "@babel/traverse" "^7.28.4" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/helper-replace-supers" "^7.28.6" + "@babel/traverse" "^7.28.6" -"@babel/plugin-transform-computed-properties@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz#81662e78bf5e734a97982c2b7f0a793288ef3caa" - integrity sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw== +"@babel/plugin-transform-computed-properties@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz#936824fc71c26cb5c433485776d79c8e7b0202d2" + integrity sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/template" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/template" "^7.28.6" -"@babel/plugin-transform-destructuring@^7.28.0", "@babel/plugin-transform-destructuring@^7.28.5": +"@babel/plugin-transform-destructuring@^7.28.5": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz#b8402764df96179a2070bb7b501a1586cf8ad7a7" integrity sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw== @@ -576,13 +583,13 @@ "@babel/helper-plugin-utils" "^7.27.1" "@babel/traverse" "^7.28.5" -"@babel/plugin-transform-dotall-regex@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz#aa6821de864c528b1fecf286f0a174e38e826f4d" - integrity sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw== +"@babel/plugin-transform-dotall-regex@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz#def31ed84e0fb6e25c71e53c124e7b76a4ab8e61" + integrity sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-create-regexp-features-plugin" "^7.28.5" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-duplicate-keys@^7.27.1": version "7.27.1" @@ -591,13 +598,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz#5043854ca620a94149372e69030ff8cb6a9eb0ec" - integrity sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ== +"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz#8014b8a6cfd0e7b92762724443bf0d2400f26df1" + integrity sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-create-regexp-features-plugin" "^7.28.5" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-dynamic-import@^7.27.1": version "7.27.1" @@ -606,20 +613,20 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-explicit-resource-management@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz#45be6211b778dbf4b9d54c4e8a2b42fa72e09a1a" - integrity sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ== +"@babel/plugin-transform-explicit-resource-management@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz#dd6788f982c8b77e86779d1d029591e39d9d8be7" + integrity sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/plugin-transform-destructuring" "^7.28.0" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/plugin-transform-destructuring" "^7.28.5" -"@babel/plugin-transform-exponentiation-operator@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.5.tgz#7cc90a8170e83532676cfa505278e147056e94fe" - integrity sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw== +"@babel/plugin-transform-exponentiation-operator@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz#5e477eb7eafaf2ab5537a04aaafcf37e2d7f1091" + integrity sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-export-namespace-from@^7.27.1": version "7.27.1" @@ -653,12 +660,12 @@ "@babel/helper-plugin-utils" "^7.27.1" "@babel/traverse" "^7.27.1" -"@babel/plugin-transform-json-strings@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz#a2e0ce6ef256376bd527f290da023983527a4f4c" - integrity sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q== +"@babel/plugin-transform-json-strings@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz#4c8c15b2dc49e285d110a4cf3dac52fd2dfc3038" + integrity sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-literals@^7.27.1": version "7.27.1" @@ -667,12 +674,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-logical-assignment-operators@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.5.tgz#d028fd6db8c081dee4abebc812c2325e24a85b0e" - integrity sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA== +"@babel/plugin-transform-logical-assignment-operators@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz#53028a3d77e33c50ef30a8fce5ca17065936e605" + integrity sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-member-expression-literals@^7.27.1": version "7.27.1" @@ -689,23 +696,23 @@ "@babel/helper-module-transforms" "^7.27.1" "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-modules-commonjs@^7.23.0", "@babel/plugin-transform-modules-commonjs@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz#8e44ed37c2787ecc23bdc367f49977476614e832" - integrity sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw== +"@babel/plugin-transform-modules-commonjs@^7.23.0", "@babel/plugin-transform-modules-commonjs@^7.27.1", "@babel/plugin-transform-modules-commonjs@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz#c0232e0dfe66a734cc4ad0d5e75fc3321b6fdef1" + integrity sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA== dependencies: - "@babel/helper-module-transforms" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-module-transforms" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-transform-modules-systemjs@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz#7439e592a92d7670dfcb95d0cbc04bd3e64801d2" - integrity sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew== +"@babel/plugin-transform-modules-systemjs@^7.29.4": + version "7.29.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.4.tgz#f621105da99919c15cf4bde6fcc7346ef95e7b20" + integrity sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w== dependencies: - "@babel/helper-module-transforms" "^7.28.3" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-module-transforms" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/helper-validator-identifier" "^7.28.5" - "@babel/traverse" "^7.28.5" + "@babel/traverse" "^7.29.0" "@babel/plugin-transform-modules-umd@^7.27.1": version "7.27.1" @@ -715,13 +722,13 @@ "@babel/helper-module-transforms" "^7.27.1" "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-named-capturing-groups-regex@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz#f32b8f7818d8fc0cc46ee20a8ef75f071af976e1" - integrity sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng== +"@babel/plugin-transform-named-capturing-groups-regex@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz#a26cd51e09c4718588fc4cce1c5d1c0152102d6a" + integrity sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-create-regexp-features-plugin" "^7.28.5" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-new-target@^7.27.1": version "7.27.1" @@ -730,30 +737,30 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-nullish-coalescing-operator@^7.22.11", "@babel/plugin-transform-nullish-coalescing-operator@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz#4f9d3153bf6782d73dd42785a9d22d03197bc91d" - integrity sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA== +"@babel/plugin-transform-nullish-coalescing-operator@^7.22.11", "@babel/plugin-transform-nullish-coalescing-operator@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz#9bc62096e90ab7a887f3ca9c469f6adec5679757" + integrity sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-transform-numeric-separator@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz#614e0b15cc800e5997dadd9bd6ea524ed6c819c6" - integrity sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw== +"@babel/plugin-transform-numeric-separator@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz#1310b0292762e7a4a335df5f580c3320ee7d9e9f" + integrity sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-transform-object-rest-spread@^7.28.4": - version "7.28.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz#9ee1ceca80b3e6c4bac9247b2149e36958f7f98d" - integrity sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew== +"@babel/plugin-transform-object-rest-spread@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz#fdd4bc2d72480db6ca42aed5c051f148d7b067f7" + integrity sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA== dependencies: - "@babel/helper-compilation-targets" "^7.27.2" - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/plugin-transform-destructuring" "^7.28.0" + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/plugin-transform-destructuring" "^7.28.5" "@babel/plugin-transform-parameters" "^7.27.7" - "@babel/traverse" "^7.28.4" + "@babel/traverse" "^7.28.6" "@babel/plugin-transform-object-super@^7.27.1": version "7.27.1" @@ -763,19 +770,19 @@ "@babel/helper-plugin-utils" "^7.27.1" "@babel/helper-replace-supers" "^7.27.1" -"@babel/plugin-transform-optional-catch-binding@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz#84c7341ebde35ccd36b137e9e45866825072a30c" - integrity sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q== +"@babel/plugin-transform-optional-catch-binding@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz#75107be14c78385978201a49c86414a150a20b4c" + integrity sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-transform-optional-chaining@^7.23.0", "@babel/plugin-transform-optional-chaining@^7.27.1", "@babel/plugin-transform-optional-chaining@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz#8238c785f9d5c1c515a90bf196efb50d075a4b26" - integrity sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ== +"@babel/plugin-transform-optional-chaining@^7.23.0", "@babel/plugin-transform-optional-chaining@^7.27.1", "@babel/plugin-transform-optional-chaining@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz#926cf150bd421fc8362753e911b4a1b1ce4356cd" + integrity sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" "@babel/plugin-transform-parameters@^7.27.7": @@ -785,22 +792,22 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-private-methods@^7.22.5", "@babel/plugin-transform-private-methods@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz#fdacbab1c5ed81ec70dfdbb8b213d65da148b6af" - integrity sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA== +"@babel/plugin-transform-private-methods@^7.22.5", "@babel/plugin-transform-private-methods@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz#c76fbfef3b86c775db7f7c106fff544610bdb411" + integrity sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg== dependencies: - "@babel/helper-create-class-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-create-class-features-plugin" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-transform-private-property-in-object@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz#4dbbef283b5b2f01a21e81e299f76e35f900fb11" - integrity sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ== +"@babel/plugin-transform-private-property-in-object@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz#4fafef1e13129d79f1d75ac180c52aafefdb2811" + integrity sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA== dependencies: - "@babel/helper-annotate-as-pure" "^7.27.1" - "@babel/helper-create-class-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-annotate-as-pure" "^7.27.3" + "@babel/helper-create-class-features-plugin" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-property-literals@^7.27.1": version "7.27.1" @@ -831,15 +838,15 @@ "@babel/plugin-transform-react-jsx" "^7.27.1" "@babel/plugin-transform-react-jsx@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz#1023bc94b78b0a2d68c82b5e96aed573bcfb9db0" - integrity sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw== + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.28.6.tgz#f51cb70a90b9529fbb71ee1f75ea27b7078eed62" + integrity sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow== dependencies: - "@babel/helper-annotate-as-pure" "^7.27.1" - "@babel/helper-module-imports" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/plugin-syntax-jsx" "^7.27.1" - "@babel/types" "^7.27.1" + "@babel/helper-annotate-as-pure" "^7.27.3" + "@babel/helper-module-imports" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/plugin-syntax-jsx" "^7.28.6" + "@babel/types" "^7.28.6" "@babel/plugin-transform-react-pure-annotations@^7.27.1": version "7.27.1" @@ -849,20 +856,20 @@ "@babel/helper-annotate-as-pure" "^7.27.1" "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-regenerator@^7.28.4": - version "7.28.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz#9d3fa3bebb48ddd0091ce5729139cd99c67cea51" - integrity sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA== +"@babel/plugin-transform-regenerator@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz#dec237cec1b93330876d6da9992c4abd42c9d18b" + integrity sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-transform-regexp-modifiers@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz#df9ba5577c974e3f1449888b70b76169998a6d09" - integrity sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA== +"@babel/plugin-transform-regexp-modifiers@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz#7ef0163bd8b4a610481b2509c58cf217f065290b" + integrity sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-create-regexp-features-plugin" "^7.28.5" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-reserved-words@^7.27.1": version "7.27.1" @@ -872,12 +879,12 @@ "@babel/helper-plugin-utils" "^7.27.1" "@babel/plugin-transform-runtime@^7.16.4", "@babel/plugin-transform-runtime@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.5.tgz#ae3e21fbefe2831ebac04dfa6b463691696afe17" - integrity sha512-20NUVgOrinudkIBzQ2bNxP08YpKprUkRTiRSd2/Z5GOdPImJGkoN4Z7IQe1T5AdyKI1i5L6RBmluqdSzvaq9/w== + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.0.tgz#a5fded13cc656700804bfd6e5ebd7fffd5266803" + integrity sha512-jlaRT5dJtMaMCV6fAuLbsQMSwz/QkvaHOHOSXRitGGwSpR1blCY4KUKoyP2tYO8vJcqYe8cEj96cqSztv3uF9w== dependencies: - "@babel/helper-module-imports" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-module-imports" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" babel-plugin-polyfill-corejs2 "^0.4.14" babel-plugin-polyfill-corejs3 "^0.13.0" babel-plugin-polyfill-regenerator "^0.6.5" @@ -890,12 +897,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-spread@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz#1a264d5fc12750918f50e3fe3e24e437178abb08" - integrity sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q== +"@babel/plugin-transform-spread@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz#40a2b423f6db7b70f043ad027a58bcb44a9757b6" + integrity sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" "@babel/plugin-transform-sticky-regex@^7.27.1": @@ -920,15 +927,15 @@ "@babel/helper-plugin-utils" "^7.27.1" "@babel/plugin-transform-typescript@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.5.tgz#441c5f9a4a1315039516c6c612fc66d5f4594e72" - integrity sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA== + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz#1e93d96da8adbefdfdade1d4956f73afa201a158" + integrity sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw== dependencies: "@babel/helper-annotate-as-pure" "^7.27.3" - "@babel/helper-create-class-features-plugin" "^7.28.5" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-create-class-features-plugin" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" - "@babel/plugin-syntax-typescript" "^7.27.1" + "@babel/plugin-syntax-typescript" "^7.28.6" "@babel/plugin-transform-unicode-escapes@^7.27.1": version "7.27.1" @@ -937,13 +944,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-unicode-property-regex@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz#bdfe2d3170c78c5691a3c3be934c8c0087525956" - integrity sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q== +"@babel/plugin-transform-unicode-property-regex@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz#63a7a6c21a0e75dae9b1861454111ea5caa22821" + integrity sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-create-regexp-features-plugin" "^7.28.5" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-unicode-regex@^7.27.1": version "7.27.1" @@ -953,88 +960,89 @@ "@babel/helper-create-regexp-features-plugin" "^7.27.1" "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-unicode-sets-regex@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz#6ab706d10f801b5c72da8bb2548561fa04193cd1" - integrity sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw== +"@babel/plugin-transform-unicode-sets-regex@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz#924912914e5df9fe615ec472f88ff4788ce04d4e" + integrity sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-create-regexp-features-plugin" "^7.28.5" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/preset-env@^7.11.0", "@babel/preset-env@^7.12.1", "@babel/preset-env@^7.16.4", "@babel/preset-env@^7.23.2", "@babel/preset-env@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.28.5.tgz#82dd159d1563f219a1ce94324b3071eb89e280b0" - integrity sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg== + version "7.29.5" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.29.5.tgz#c48b7ed94582c8b685e21b8b42de8633ec289268" + integrity sha512-/69t2aEzGKHD76DyLbHysF/QH2LJOB8iFnYO37unDTKBTubzcMRv0f3H5EiN1Q6ajOd/eB7dAInF0qdFVS06kA== dependencies: - "@babel/compat-data" "^7.28.5" - "@babel/helper-compilation-targets" "^7.27.2" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/compat-data" "^7.29.3" + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/helper-validator-option" "^7.27.1" "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.28.5" "@babel/plugin-bugfix-safari-class-field-initializer-scope" "^7.27.1" "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.27.1" + "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array" "^7.29.3" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.27.1" - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.28.3" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.28.6" "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" - "@babel/plugin-syntax-import-assertions" "^7.27.1" - "@babel/plugin-syntax-import-attributes" "^7.27.1" + "@babel/plugin-syntax-import-assertions" "^7.28.6" + "@babel/plugin-syntax-import-attributes" "^7.28.6" "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" "@babel/plugin-transform-arrow-functions" "^7.27.1" - "@babel/plugin-transform-async-generator-functions" "^7.28.0" - "@babel/plugin-transform-async-to-generator" "^7.27.1" + "@babel/plugin-transform-async-generator-functions" "^7.29.0" + "@babel/plugin-transform-async-to-generator" "^7.28.6" "@babel/plugin-transform-block-scoped-functions" "^7.27.1" - "@babel/plugin-transform-block-scoping" "^7.28.5" - "@babel/plugin-transform-class-properties" "^7.27.1" - "@babel/plugin-transform-class-static-block" "^7.28.3" - "@babel/plugin-transform-classes" "^7.28.4" - "@babel/plugin-transform-computed-properties" "^7.27.1" + "@babel/plugin-transform-block-scoping" "^7.28.6" + "@babel/plugin-transform-class-properties" "^7.28.6" + "@babel/plugin-transform-class-static-block" "^7.28.6" + "@babel/plugin-transform-classes" "^7.28.6" + "@babel/plugin-transform-computed-properties" "^7.28.6" "@babel/plugin-transform-destructuring" "^7.28.5" - "@babel/plugin-transform-dotall-regex" "^7.27.1" + "@babel/plugin-transform-dotall-regex" "^7.28.6" "@babel/plugin-transform-duplicate-keys" "^7.27.1" - "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.27.1" + "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.29.0" "@babel/plugin-transform-dynamic-import" "^7.27.1" - "@babel/plugin-transform-explicit-resource-management" "^7.28.0" - "@babel/plugin-transform-exponentiation-operator" "^7.28.5" + "@babel/plugin-transform-explicit-resource-management" "^7.28.6" + "@babel/plugin-transform-exponentiation-operator" "^7.28.6" "@babel/plugin-transform-export-namespace-from" "^7.27.1" "@babel/plugin-transform-for-of" "^7.27.1" "@babel/plugin-transform-function-name" "^7.27.1" - "@babel/plugin-transform-json-strings" "^7.27.1" + "@babel/plugin-transform-json-strings" "^7.28.6" "@babel/plugin-transform-literals" "^7.27.1" - "@babel/plugin-transform-logical-assignment-operators" "^7.28.5" + "@babel/plugin-transform-logical-assignment-operators" "^7.28.6" "@babel/plugin-transform-member-expression-literals" "^7.27.1" "@babel/plugin-transform-modules-amd" "^7.27.1" - "@babel/plugin-transform-modules-commonjs" "^7.27.1" - "@babel/plugin-transform-modules-systemjs" "^7.28.5" + "@babel/plugin-transform-modules-commonjs" "^7.28.6" + "@babel/plugin-transform-modules-systemjs" "^7.29.4" "@babel/plugin-transform-modules-umd" "^7.27.1" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.27.1" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.29.0" "@babel/plugin-transform-new-target" "^7.27.1" - "@babel/plugin-transform-nullish-coalescing-operator" "^7.27.1" - "@babel/plugin-transform-numeric-separator" "^7.27.1" - "@babel/plugin-transform-object-rest-spread" "^7.28.4" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.28.6" + "@babel/plugin-transform-numeric-separator" "^7.28.6" + "@babel/plugin-transform-object-rest-spread" "^7.28.6" "@babel/plugin-transform-object-super" "^7.27.1" - "@babel/plugin-transform-optional-catch-binding" "^7.27.1" - "@babel/plugin-transform-optional-chaining" "^7.28.5" + "@babel/plugin-transform-optional-catch-binding" "^7.28.6" + "@babel/plugin-transform-optional-chaining" "^7.28.6" "@babel/plugin-transform-parameters" "^7.27.7" - "@babel/plugin-transform-private-methods" "^7.27.1" - "@babel/plugin-transform-private-property-in-object" "^7.27.1" + "@babel/plugin-transform-private-methods" "^7.28.6" + "@babel/plugin-transform-private-property-in-object" "^7.28.6" "@babel/plugin-transform-property-literals" "^7.27.1" - "@babel/plugin-transform-regenerator" "^7.28.4" - "@babel/plugin-transform-regexp-modifiers" "^7.27.1" + "@babel/plugin-transform-regenerator" "^7.29.0" + "@babel/plugin-transform-regexp-modifiers" "^7.28.6" "@babel/plugin-transform-reserved-words" "^7.27.1" "@babel/plugin-transform-shorthand-properties" "^7.27.1" - "@babel/plugin-transform-spread" "^7.27.1" + "@babel/plugin-transform-spread" "^7.28.6" "@babel/plugin-transform-sticky-regex" "^7.27.1" "@babel/plugin-transform-template-literals" "^7.27.1" "@babel/plugin-transform-typeof-symbol" "^7.27.1" "@babel/plugin-transform-unicode-escapes" "^7.27.1" - "@babel/plugin-transform-unicode-property-regex" "^7.27.1" + "@babel/plugin-transform-unicode-property-regex" "^7.28.6" "@babel/plugin-transform-unicode-regex" "^7.27.1" - "@babel/plugin-transform-unicode-sets-regex" "^7.27.1" + "@babel/plugin-transform-unicode-sets-regex" "^7.28.6" "@babel/preset-modules" "0.1.6-no-external-plugins" - babel-plugin-polyfill-corejs2 "^0.4.14" - babel-plugin-polyfill-corejs3 "^0.13.0" - babel-plugin-polyfill-regenerator "^0.6.5" - core-js-compat "^3.43.0" + babel-plugin-polyfill-corejs2 "^0.4.15" + babel-plugin-polyfill-corejs3 "^0.14.0" + babel-plugin-polyfill-regenerator "^0.6.6" + core-js-compat "^3.48.0" semver "^6.3.1" "@babel/preset-flow@^7.22.15": @@ -1079,9 +1087,9 @@ "@babel/plugin-transform-typescript" "^7.28.5" "@babel/register@^7.22.15": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.28.3.tgz#abd8a3753480c799bdaf9c9092d6745d16e052c2" - integrity sha512-CieDOtd8u208eI49bYl4z1J22ySFw87IGwE+IswFEExH7e3rLgKb0WNQeumnacQ1+VoDJLYI5QFA3AJZuyZQfA== + version "7.29.3" + resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.29.3.tgz#726233ed488d52bb67aa8fd0dc5cdf19d10a2584" + integrity sha512-F6C1KpIdoImKQfsD6HSxZ+mS4YY/2Q+JsqrmTC5ApVkTR2rG+nnbpjhWwzA5bDNu8mJjB3AryqDaWFLd4gCbJQ== dependencies: clone-deep "^4.0.1" find-cache-dir "^2.0.0" @@ -1089,42 +1097,37 @@ pirates "^4.0.6" source-map-support "^0.5.16" -"@babel/runtime@^7.10.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.21.0", "@babel/runtime@^7.26.7", "@babel/runtime@^7.28.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": - version "7.28.4" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.4.tgz#a70226016fabe25c5783b2f22d3e1c9bc5ca3326" - integrity sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ== +"@babel/runtime@^7.10.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.21.0", "@babel/runtime@^7.26.7", "@babel/runtime@^7.28.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": + version "7.29.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.29.2.tgz#9a6e2d05f4b6692e1801cd4fb176ad823930ed5e" + integrity sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g== -"@babel/runtime@^7.18.6": +"@babel/template@^7.28.6", "@babel/template@^7.3.3": version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.6.tgz#d267a43cb1836dc4d182cce93ae75ba954ef6d2b" - integrity sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA== - -"@babel/template@^7.27.1", "@babel/template@^7.27.2", "@babel/template@^7.3.3": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" - integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.28.6.tgz#0e7e56ecedb78aeef66ce7972b082fce76a23e57" + integrity sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ== dependencies: - "@babel/code-frame" "^7.27.1" - "@babel/parser" "^7.27.2" - "@babel/types" "^7.27.1" + "@babel/code-frame" "^7.28.6" + "@babel/parser" "^7.28.6" + "@babel/types" "^7.28.6" -"@babel/traverse@^7.18.9", "@babel/traverse@^7.23.2", "@babel/traverse@^7.27.1", "@babel/traverse@^7.28.0", "@babel/traverse@^7.28.3", "@babel/traverse@^7.28.4", "@babel/traverse@^7.28.5", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.2": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.5.tgz#450cab9135d21a7a2ca9d2d35aa05c20e68c360b" - integrity sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ== +"@babel/traverse@^7.18.9", "@babel/traverse@^7.23.2", "@babel/traverse@^7.27.1", "@babel/traverse@^7.28.5", "@babel/traverse@^7.28.6", "@babel/traverse@^7.29.0", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.2": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.0.tgz#f323d05001440253eead3c9c858adbe00b90310a" + integrity sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA== dependencies: - "@babel/code-frame" "^7.27.1" - "@babel/generator" "^7.28.5" + "@babel/code-frame" "^7.29.0" + "@babel/generator" "^7.29.0" "@babel/helper-globals" "^7.28.0" - "@babel/parser" "^7.28.5" - "@babel/template" "^7.27.2" - "@babel/types" "^7.28.5" + "@babel/parser" "^7.29.0" + "@babel/template" "^7.28.6" + "@babel/types" "^7.29.0" debug "^4.3.1" -"@babel/types@^7.0.0", "@babel/types@^7.12.6", "@babel/types@^7.18.9", "@babel/types@^7.20.7", "@babel/types@^7.23.0", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.28.4", "@babel/types@^7.28.5", "@babel/types@^7.3.3", "@babel/types@^7.4.4": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.5.tgz#10fc405f60897c35f07e85493c932c7b5ca0592b" - integrity sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA== +"@babel/types@^7.0.0", "@babel/types@^7.12.6", "@babel/types@^7.18.9", "@babel/types@^7.20.7", "@babel/types@^7.23.0", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.28.5", "@babel/types@^7.28.6", "@babel/types@^7.29.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.0.tgz#9f5b1e838c446e72cf3cd4b918152b8c605e37c7" + integrity sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A== dependencies: "@babel/helper-string-parser" "^7.27.1" "@babel/helper-validator-identifier" "^7.28.5" @@ -1151,10 +1154,15 @@ resolved "https://registry.yarnpkg.com/@bedrock-layout/use-stateful-ref/-/use-stateful-ref-1.4.1.tgz#c543c61d15885e19506f068618a4d6427a5bf817" integrity sha512-4eKO2KdQEXcR5LI4QcxqlJykJUDQJWDeWYAukIn6sRQYoabcfI5kDl61PUi6FR6o8VFgQ8IEP7HleKqWlSe8SQ== +"@borewit/text-codec@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@borewit/text-codec/-/text-codec-0.2.2.tgz#75025f735c0983b3a871668804a57387e3649375" + integrity sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ== + "@codemirror/autocomplete@^6.0.0", "@codemirror/autocomplete@^6.20.1": - version "6.20.1" - resolved "https://registry.yarnpkg.com/@codemirror/autocomplete/-/autocomplete-6.20.1.tgz#4cfbc8b2e1e25f890ec34a081037e58b4e44143e" - integrity sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A== + version "6.20.2" + resolved "https://registry.yarnpkg.com/@codemirror/autocomplete/-/autocomplete-6.20.2.tgz#36200d33f3e403ef4f76cf67b4cc7ec560e61c7b" + integrity sha512-G5FPkgIiLjOgZMjqVjvuKQ1rGPtHogLldJr33eFJdVLtmwY+giGrlv/ewljLz6b9BSQLkjxuwBc6g6omDM+YxQ== dependencies: "@codemirror/language" "^6.0.0" "@codemirror/state" "^6.0.0" @@ -1180,9 +1188,9 @@ "@lezer/java" "^1.0.0" "@codemirror/language@^6.0.0", "@codemirror/language@^6.12.2": - version "6.12.2" - resolved "https://registry.yarnpkg.com/@codemirror/language/-/language-6.12.2.tgz#7db5a46757411cf251e8f450474c05710c27d42c" - integrity sha512-jEPmz2nGGDxhRTg3lTpzmIyGKxz3Gp3SJES4b0nAuE5SWQoKdT5GoQ69cwMmFd+wvFUhYirtDTr0/DRHpQAyWg== + version "6.12.3" + resolved "https://registry.yarnpkg.com/@codemirror/language/-/language-6.12.3.tgz#0b220182973a4c19850b29f7dd82aec1bbae3d7e" + integrity sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA== dependencies: "@codemirror/state" "^6.0.0" "@codemirror/view" "^6.23.0" @@ -1192,18 +1200,18 @@ style-mod "^4.0.0" "@codemirror/lint@^6.0.0", "@codemirror/lint@^6.9.5": - version "6.9.5" - resolved "https://registry.yarnpkg.com/@codemirror/lint/-/lint-6.9.5.tgz#c7da006f3335a33014799a7375c82df558e89f90" - integrity sha512-GElsbU9G7QT9xXhpUg1zWGmftA/7jamh+7+ydKRuT0ORpWS3wOSP0yT1FOlIZa7mIJjpVPipErsyvVqB9cfTFA== + version "6.9.6" + resolved "https://registry.yarnpkg.com/@codemirror/lint/-/lint-6.9.6.tgz#eae25d5dfac28595521a2b38ebad223f14b7b6d1" + integrity sha512-6Kp7r6XfCi/D/5sdXieMfg9pJU1bUEx96WITuLU6ESaKizCz0QHFMjY/TaFSbigDdEAIgi93itLBIUETP4oK+A== dependencies: "@codemirror/state" "^6.0.0" - "@codemirror/view" "^6.35.0" + "@codemirror/view" "^6.42.0" crelt "^1.0.5" "@codemirror/search@^6.0.0", "@codemirror/search@^6.6.0": - version "6.6.0" - resolved "https://registry.yarnpkg.com/@codemirror/search/-/search-6.6.0.tgz#3b83a1e35391e1575a83a3b485e3f95263ddaa0b" - integrity sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw== + version "6.7.0" + resolved "https://registry.yarnpkg.com/@codemirror/search/-/search-6.7.0.tgz#c6bea0ab0e882395d03a760b1a7d3959b1f3a4ac" + integrity sha512-ZvGm99wc/s2cITtMT15LFdn8aH/aS+V+DqyGq/N5ZlV5vWtH+nILvC2nw0zX7ByNoHHDZ2IxxdW38O0tc5nVHg== dependencies: "@codemirror/state" "^6.0.0" "@codemirror/view" "^6.37.0" @@ -1226,10 +1234,10 @@ "@codemirror/view" "^6.0.0" "@lezer/highlight" "^1.0.0" -"@codemirror/view@^6.0.0", "@codemirror/view@^6.17.0", "@codemirror/view@^6.23.0", "@codemirror/view@^6.27.0", "@codemirror/view@^6.35.0", "@codemirror/view@^6.37.0", "@codemirror/view@^6.40.0": - version "6.40.0" - resolved "https://registry.yarnpkg.com/@codemirror/view/-/view-6.40.0.tgz#97198fd717ebf471ef594a5bd557a9f2d1d4d165" - integrity sha512-WA0zdU7xfF10+5I3HhUUq3kqOx3KjqmtQ9lqZjfK7jtYk4G72YW9rezcSywpaUMCWOMlq+6E0pO1IWg1TNIhtg== +"@codemirror/view@^6.0.0", "@codemirror/view@^6.17.0", "@codemirror/view@^6.23.0", "@codemirror/view@^6.27.0", "@codemirror/view@^6.37.0", "@codemirror/view@^6.40.0", "@codemirror/view@^6.42.0": + version "6.43.0" + resolved "https://registry.yarnpkg.com/@codemirror/view/-/view-6.43.0.tgz#a577da65f1d5d8f7cbf08e14849284c12f38365a" + integrity sha512-V7ZCLQO3Jus9hzh2jVCCPW3mO4IBMr43O37PqSUYautJSnnJF41YlgLw21x0fLJTYvJ+Vkm6Gp+qKGH9pltgXA== dependencies: "@codemirror/state" "^6.6.0" crelt "^1.0.6" @@ -1403,25 +1411,25 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== -"@emnapi/core@^1.4.3": - version "1.7.1" - resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.7.1.tgz#3a79a02dbc84f45884a1806ebb98e5746bdfaac4" - integrity sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg== +"@emnapi/core@1.10.0": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.10.0.tgz#380ccc8f2412ea22d1d972df7f8ee23a3b9c7467" + integrity sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw== dependencies: - "@emnapi/wasi-threads" "1.1.0" + "@emnapi/wasi-threads" "1.2.1" tslib "^2.4.0" -"@emnapi/runtime@^1.4.3": - version "1.7.1" - resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.7.1.tgz#a73784e23f5d57287369c808197288b52276b791" - integrity sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA== +"@emnapi/runtime@1.10.0": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.10.0.tgz#4b260c0d3534204e98c6110b8db1a987d26ec87c" + integrity sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA== dependencies: tslib "^2.4.0" -"@emnapi/wasi-threads@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz#60b2102fddc9ccb78607e4a3cf8403ea69be41bf" - integrity sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ== +"@emnapi/wasi-threads@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz#28fed21a1ba1ce797c44a070abc94d42f3ae8548" + integrity sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w== dependencies: tslib "^2.4.0" @@ -1646,9 +1654,9 @@ integrity sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ== "@eslint-community/eslint-utils@^4.1.2", "@eslint-community/eslint-utils@^4.2.0": - version "4.9.0" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz#7308df158e064f0dd8b8fdb58aa14fa2a7f913b3" - integrity sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g== + version "4.9.1" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595" + integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ== dependencies: eslint-visitor-keys "^3.4.3" @@ -1687,32 +1695,32 @@ resolved "https://registry.yarnpkg.com/@filestack/loader/-/loader-1.0.9.tgz#eaab7e6536a41c8fd1e918ba064e24fd7a12bb32" integrity sha512-zvXbZSgeybT1p3ds5NZ5GQYPVnKacgb2YGWe7psdPs/JE1v3SL1j2SXYaHA/f/Qwc8Y1fjzz53maKP0vwDHrvA== -"@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== +"@floating-ui/core@^1.7.5": + version "1.7.5" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.7.5.tgz#d4af157a03330af5a60e69da7a4692507ada0622" + integrity sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ== dependencies: - "@floating-ui/utils" "^0.2.10" + "@floating-ui/utils" "^0.2.11" -"@floating-ui/dom@^1.0.1", "@floating-ui/dom@^1.6.1", "@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== +"@floating-ui/dom@^1.0.1", "@floating-ui/dom@^1.6.1", "@floating-ui/dom@^1.7.6": + version "1.7.6" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.7.6.tgz#f915bba5abbb177e1f227cacee1b4d0634b187bf" + integrity sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ== dependencies: - "@floating-ui/core" "^1.7.3" - "@floating-ui/utils" "^0.2.10" + "@floating-ui/core" "^1.7.5" + "@floating-ui/utils" "^0.2.11" "@floating-ui/react-dom@^2.0.0": - 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== + version "2.1.8" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.8.tgz#5fb5a20d10aafb9505f38c24f38d00c8e1598893" + integrity sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A== dependencies: - "@floating-ui/dom" "^1.7.4" + "@floating-ui/dom" "^1.7.6" -"@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== +"@floating-ui/utils@^0.2.11": + version "0.2.11" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.11.tgz#a269e055e40e2f45873bae9d1a2fdccbd314ea3f" + integrity sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg== "@hapi/address@^5.1.1": version "5.1.1" @@ -1737,9 +1745,9 @@ integrity sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q== "@hapi/tlds@^1.1.1": - version "1.1.4" - resolved "https://registry.yarnpkg.com/@hapi/tlds/-/tlds-1.1.4.tgz#df4a7b59082b54ba4f3b7b38f781e2ac3cbc359a" - integrity sha512-Fq+20dxsxLaUn5jSSWrdtSRcIUba2JquuorF9UW1wIJS5cSUwxIsO2GIhaWynPRflvxSzFN+gxKte2HEW1OuoA== + version "1.1.6" + resolved "https://registry.yarnpkg.com/@hapi/tlds/-/tlds-1.1.6.tgz#c98ed89ca76aa030352d6d7102d0f250aed89cfb" + integrity sha512-xdi7A/4NZokvV0ewovme3aUO5kQhW9pQ2YD1hRqZGhhSi5rBv4usHYidVocXSi9eihYsznZxLtAiEYYUL6VBGw== "@hapi/topo@^6.0.2": version "6.0.2" @@ -1795,18 +1803,6 @@ resolved "https://registry.yarnpkg.com/@icons/material/-/material-0.2.4.tgz#e90c9f71768b3736e76d7dd6783fc6c2afa88bc8" integrity sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw== -"@isaacs/balanced-match@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz#3081dadbc3460661b751e7591d7faea5df39dd29" - integrity sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ== - -"@isaacs/brace-expansion@^5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz#4b3dabab7d8e75a429414a96bd67bf4c1d13e0f3" - integrity sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA== - dependencies: - "@isaacs/balanced-match" "^4.0.1" - "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" @@ -1831,9 +1827,9 @@ resolve-from "^5.0.0" "@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" - integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + version "0.1.6" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.6.tgz#8dc9afa2ac1506cb1a58f89940f1c124446c8df3" + integrity sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw== "@jackwilsdon/craco-use-babelrc@1.0.0": version "1.0.0" @@ -1946,10 +1942,10 @@ slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/diff-sequences@30.0.1": - version "30.0.1" - resolved "https://registry.yarnpkg.com/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz#0ededeae4d071f5c8ffe3678d15f3a1be09156be" - integrity sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw== +"@jest/diff-sequences@30.4.0": + version "30.4.0" + resolved "https://registry.yarnpkg.com/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz#8be2d260e6241d6cddddd102c304fe13b4fc8e3e" + integrity sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g== "@jest/environment@^27.5.1": version "27.5.1" @@ -1971,10 +1967,10 @@ "@types/node" "*" jest-mock "^29.7.0" -"@jest/expect-utils@30.2.0": - version "30.2.0" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-30.2.0.tgz#4f95413d4748454fdb17404bf1141827d15e6011" - integrity sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA== +"@jest/expect-utils@30.4.1": + version "30.4.1" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-30.4.1.tgz#e0c7436d52b08610de9027841912dc3734ae80b2" + integrity sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ== dependencies: "@jest/get-type" "30.1.0" @@ -2041,13 +2037,13 @@ "@jest/types" "^29.6.3" jest-mock "^29.7.0" -"@jest/pattern@30.0.1": - version "30.0.1" - resolved "https://registry.yarnpkg.com/@jest/pattern/-/pattern-30.0.1.tgz#d5304147f49a052900b4b853dedb111d080e199f" - integrity sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA== +"@jest/pattern@30.4.0": + version "30.4.0" + resolved "https://registry.yarnpkg.com/@jest/pattern/-/pattern-30.4.0.tgz#fcb519eeacc25caa3768f787595a27afa15302ae" + integrity sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg== dependencies: "@types/node" "*" - jest-regex-util "30.0.1" + jest-regex-util "30.4.0" "@jest/reporters@^27.5.1": version "27.5.1" @@ -2110,10 +2106,10 @@ strip-ansi "^6.0.0" v8-to-istanbul "^9.0.1" -"@jest/schemas@30.0.5": - version "30.0.5" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-30.0.5.tgz#7bdf69fc5a368a5abdb49fd91036c55225846473" - integrity sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA== +"@jest/schemas@30.4.1": + version "30.4.1" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-30.4.1.tgz#c3703fdd71357e2c83aa59bd38469e60a11529c6" + integrity sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q== dependencies: "@sinclair/typebox" "^0.34.0" @@ -2241,13 +2237,13 @@ slash "^3.0.0" write-file-atomic "^4.0.2" -"@jest/types@30.2.0": - version "30.2.0" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-30.2.0.tgz#1c678a7924b8f59eafd4c77d56b6d0ba976d62b8" - integrity sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg== +"@jest/types@30.4.1": + version "30.4.1" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-30.4.1.tgz#f79b647a85cb2ff4a90cc55984b31dae820db1f7" + integrity sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ== dependencies: - "@jest/pattern" "30.0.1" - "@jest/schemas" "30.0.5" + "@jest/pattern" "30.4.0" + "@jest/schemas" "30.4.1" "@types/istanbul-lib-coverage" "^2.0.6" "@types/istanbul-reports" "^3.0.4" "@types/node" "*" @@ -2350,9 +2346,9 @@ integrity sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw== "@lezer/common@^1.0.0", "@lezer/common@^1.1.0", "@lezer/common@^1.2.0", "@lezer/common@^1.3.0", "@lezer/common@^1.5.0": - version "1.5.1" - resolved "https://registry.yarnpkg.com/@lezer/common/-/common-1.5.1.tgz#6e8c114ff5d36a41148e146a253734d3bb8807d3" - integrity sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw== + version "1.5.2" + resolved "https://registry.yarnpkg.com/@lezer/common/-/common-1.5.2.tgz#d6840db13779e3f1b42e70c9a97c4086d12fae22" + integrity sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ== "@lezer/highlight@^1.0.0": version "1.2.3" @@ -2371,9 +2367,9 @@ "@lezer/lr" "^1.0.0" "@lezer/lr@^1.0.0": - version "1.4.8" - resolved "https://registry.yarnpkg.com/@lezer/lr/-/lr-1.4.8.tgz#333de9bc9346057323ff09beb4cda47ccc38a498" - integrity sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA== + version "1.4.10" + resolved "https://registry.yarnpkg.com/@lezer/lr/-/lr-1.4.10.tgz#b3acc36e5ad049b74ddb7719594e7e74d9161ff5" + integrity sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A== dependencies: "@lezer/common" "^1.0.0" @@ -2390,14 +2386,12 @@ "@types/mdx" "^2.0.0" "@types/react" ">=16" -"@napi-rs/wasm-runtime@^0.2.11": - version "0.2.12" - resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz#3e78a8b96e6c33a6c517e1894efbd5385a7cb6f2" - integrity sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ== +"@napi-rs/wasm-runtime@^1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz#a46bbfedc29751b7170c5d23bc1d8ee8c7e3c1e1" + integrity sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow== dependencies: - "@emnapi/core" "^1.4.3" - "@emnapi/runtime" "^1.4.3" - "@tybys/wasm-util" "^0.10.0" + "@tybys/wasm-util" "^0.10.1" "@ndelangen/get-tarball@^3.0.7": version "3.0.9" @@ -2415,6 +2409,11 @@ dependencies: eslint-scope "5.1.1" +"@nodable/entities@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@nodable/entities/-/entities-2.1.0.tgz#f543e5c6446720d4cf9e498a83019dd159973bc2" + integrity sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -2441,94 +2440,94 @@ resolved "https://registry.yarnpkg.com/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz#3dc35ba0f1e66b403c00b39344f870298ebb1c8e" integrity sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA== -"@parcel/watcher-android-arm64@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz#507f836d7e2042f798c7d07ad19c3546f9848ac1" - integrity sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA== - -"@parcel/watcher-darwin-arm64@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz#3d26dce38de6590ef79c47ec2c55793c06ad4f67" - integrity sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw== - -"@parcel/watcher-darwin-x64@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz#99f3af3869069ccf774e4ddfccf7e64fd2311ef8" - integrity sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg== - -"@parcel/watcher-freebsd-x64@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz#14d6857741a9f51dfe51d5b08b7c8afdbc73ad9b" - integrity sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ== - -"@parcel/watcher-linux-arm-glibc@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz#43c3246d6892381db473bb4f663229ad20b609a1" - integrity sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA== - -"@parcel/watcher-linux-arm-musl@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz#663750f7090bb6278d2210de643eb8a3f780d08e" - integrity sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q== - -"@parcel/watcher-linux-arm64-glibc@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz#ba60e1f56977f7e47cd7e31ad65d15fdcbd07e30" - integrity sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w== - -"@parcel/watcher-linux-arm64-musl@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz#f7fbcdff2f04c526f96eac01f97419a6a99855d2" - integrity sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg== - -"@parcel/watcher-linux-x64-glibc@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz#4d2ea0f633eb1917d83d483392ce6181b6a92e4e" - integrity sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A== - -"@parcel/watcher-linux-x64-musl@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz#277b346b05db54f55657301dd77bdf99d63606ee" - integrity sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg== - -"@parcel/watcher-win32-arm64@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz#7e9e02a26784d47503de1d10e8eab6cceb524243" - integrity sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw== - -"@parcel/watcher-win32-ia32@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz#2d0f94fa59a873cdc584bf7f6b1dc628ddf976e6" - integrity sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ== - -"@parcel/watcher-win32-x64@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz#ae52693259664ba6f2228fa61d7ee44b64ea0947" - integrity sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA== +"@parcel/watcher-android-arm64@2.5.6": + version "2.5.6" + resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz#5f32e0dba356f4ac9a11068d2a5c134ca3ba6564" + integrity sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A== + +"@parcel/watcher-darwin-arm64@2.5.6": + version "2.5.6" + resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz#88d3e720b59b1eceffce98dac46d7c40e8be5e8e" + integrity sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA== + +"@parcel/watcher-darwin-x64@2.5.6": + version "2.5.6" + resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz#bf05d76a78bc15974f15ec3671848698b0838063" + integrity sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg== + +"@parcel/watcher-freebsd-x64@2.5.6": + version "2.5.6" + resolved "https://registry.yarnpkg.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz#8bc26e9848e7303ac82922a5ae1b1ef1bdb48a53" + integrity sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng== + +"@parcel/watcher-linux-arm-glibc@2.5.6": + version "2.5.6" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz#1328fee1deb0c2d7865079ef53a2ba4cc2f8b40a" + integrity sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ== + +"@parcel/watcher-linux-arm-musl@2.5.6": + version "2.5.6" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz#bad0f45cb3e2157746db8b9d22db6a125711f152" + integrity sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg== + +"@parcel/watcher-linux-arm64-glibc@2.5.6": + version "2.5.6" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz#b75913fbd501d9523c5f35d420957bf7d0204809" + integrity sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA== + +"@parcel/watcher-linux-arm64-musl@2.5.6": + version "2.5.6" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz#da5621a6a576070c8c0de60dea8b46dc9c3827d4" + integrity sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA== + +"@parcel/watcher-linux-x64-glibc@2.5.6": + version "2.5.6" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz#ce437accdc4b30f93a090b4a221fd95cd9b89639" + integrity sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ== + +"@parcel/watcher-linux-x64-musl@2.5.6": + version "2.5.6" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz#02400c54b4a67efcc7e2327b249711920ac969e2" + integrity sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg== + +"@parcel/watcher-win32-arm64@2.5.6": + version "2.5.6" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz#caae3d3c7583ca0a7171e6bd142c34d20ea1691e" + integrity sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q== + +"@parcel/watcher-win32-ia32@2.5.6": + version "2.5.6" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz#9ac922550896dfe47bfc5ae3be4f1bcaf8155d6d" + integrity sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g== + +"@parcel/watcher-win32-x64@2.5.6": + version "2.5.6" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz#73fdafba2e21c448f0e456bbe13178d8fe11739d" + integrity sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw== "@parcel/watcher@^2.4.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.5.1.tgz#342507a9cfaaf172479a882309def1e991fb1200" - integrity sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg== + version "2.5.6" + resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.5.6.tgz#3f932828c894f06d0ad9cfefade1756ecc6ef1f1" + integrity sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ== dependencies: - detect-libc "^1.0.3" + detect-libc "^2.0.3" is-glob "^4.0.3" - micromatch "^4.0.5" node-addon-api "^7.0.0" + picomatch "^4.0.3" optionalDependencies: - "@parcel/watcher-android-arm64" "2.5.1" - "@parcel/watcher-darwin-arm64" "2.5.1" - "@parcel/watcher-darwin-x64" "2.5.1" - "@parcel/watcher-freebsd-x64" "2.5.1" - "@parcel/watcher-linux-arm-glibc" "2.5.1" - "@parcel/watcher-linux-arm-musl" "2.5.1" - "@parcel/watcher-linux-arm64-glibc" "2.5.1" - "@parcel/watcher-linux-arm64-musl" "2.5.1" - "@parcel/watcher-linux-x64-glibc" "2.5.1" - "@parcel/watcher-linux-x64-musl" "2.5.1" - "@parcel/watcher-win32-arm64" "2.5.1" - "@parcel/watcher-win32-ia32" "2.5.1" - "@parcel/watcher-win32-x64" "2.5.1" + "@parcel/watcher-android-arm64" "2.5.6" + "@parcel/watcher-darwin-arm64" "2.5.6" + "@parcel/watcher-darwin-x64" "2.5.6" + "@parcel/watcher-freebsd-x64" "2.5.6" + "@parcel/watcher-linux-arm-glibc" "2.5.6" + "@parcel/watcher-linux-arm-musl" "2.5.6" + "@parcel/watcher-linux-arm64-glibc" "2.5.6" + "@parcel/watcher-linux-arm64-musl" "2.5.6" + "@parcel/watcher-linux-x64-glibc" "2.5.6" + "@parcel/watcher-linux-x64-musl" "2.5.6" + "@parcel/watcher-win32-arm64" "2.5.6" + "@parcel/watcher-win32-ia32" "2.5.6" + "@parcel/watcher-win32-x64" "2.5.6" "@pkgjs/parseargs@^0.11.0": version "0.11.0" @@ -2919,10 +2918,10 @@ dependencies: "@babel/runtime" "^7.13.10" -"@remix-run/router@1.23.1": - version "1.23.1" - resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.23.1.tgz#0ce8857b024e24fc427585316383ad9d295b3a7f" - integrity sha512-vDbaOzF7yT2Qs4vO6XV1MHcJv+3dgR1sT+l3B8xxOVhUC336prMvqrvsLL/9Dnw2xr6Qhz4J0dmS0llNAbnUmQ== +"@remix-run/router@1.23.2": + version "1.23.2" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.23.2.tgz#156c4b481c0bee22a19f7924728a67120de06971" + integrity sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w== "@rollup/plugin-babel@^5.2.0": version "5.3.1" @@ -2967,55 +2966,55 @@ integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g== "@rushstack/eslint-patch@^1.1.0": - version "1.15.0" - resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.15.0.tgz#8184bcb37791e6d3c3c13a9bfbe4af263f66665f" - integrity sha512-ojSshQPKwVvSMR8yT2L/QtUkV5SXi/IfDiJ4/8d6UbTPjiHVmxZzUAzGD8Tzks1b9+qQkZa0isUOvYObedITaw== + version "1.16.1" + resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.16.1.tgz#4f97581e114fc79f246cee3723a5c4edd3b62415" + integrity sha512-TvZbIpeKqGQQ7X0zSCvPH9riMSFQFSggnfBjFZ1mEoILW+UuXCKwOoPcgjMwiUtRqFZ8jWhPJc4um14vC6I4ag== -"@sentry-internal/browser-utils@8.55.0": - version "8.55.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.55.0.tgz#d89bae423edd29c39f01285c8e2d59ce9289d9a6" - integrity sha512-ROgqtQfpH/82AQIpESPqPQe0UyWywKJsmVIqi3c5Fh+zkds5LUxnssTj3yNd1x+kxaPDVB023jAP+3ibNgeNDw== +"@sentry-internal/browser-utils@8.55.2": + version "8.55.2" + resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.55.2.tgz#3463115ac048813d71565c6fa2a48e6d60f15e8b" + integrity sha512-GnKod+gL/Y+1FUM/RGV8q6le1CoyiGbT40MitEK7eVwWe+bfTRq1gN7ioupyHFMUg1RlQkDQ4/sENmio/uow5A== dependencies: - "@sentry/core" "8.55.0" + "@sentry/core" "8.55.2" -"@sentry-internal/feedback@8.55.0": - version "8.55.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.55.0.tgz#170b8e96a36ce6f71f53daad680f1a0c98381314" - integrity sha512-cP3BD/Q6pquVQ+YL+rwCnorKuTXiS9KXW8HNKu4nmmBAyf7urjs+F6Hr1k9MXP5yQ8W3yK7jRWd09Yu6DHWOiw== +"@sentry-internal/feedback@8.55.2": + version "8.55.2" + resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.55.2.tgz#16608bf305beb840b751f63473558cb57e9c9503" + integrity sha512-XQy//NWbL0mLLM5w8wNDWMNpXz39VUyW2397dUrH8++kR63WhUVAvTOtL0o0GMVadSAzl1b08oHP9zSUNFQwcg== dependencies: - "@sentry/core" "8.55.0" + "@sentry/core" "8.55.2" -"@sentry-internal/replay-canvas@8.55.0": - version "8.55.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.55.0.tgz#e65430207a2f18e4a07c25c669ec758d11282aaf" - integrity sha512-nIkfgRWk1091zHdu4NbocQsxZF1rv1f7bbp3tTIlZYbrH62XVZosx5iHAuZG0Zc48AETLE7K4AX9VGjvQj8i9w== +"@sentry-internal/replay-canvas@8.55.2": + version "8.55.2" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.55.2.tgz#66138ae5e31c8e77a72ceab12673bfd7ae99dd26" + integrity sha512-P/jGiuR7dRLG9IzD/463fLgiibyYceauav/9prRG0ZxJm1AtuO02OKball2Fs3bbzdzwHCTlcsUuL2ivDF4b5A== dependencies: - "@sentry-internal/replay" "8.55.0" - "@sentry/core" "8.55.0" + "@sentry-internal/replay" "8.55.2" + "@sentry/core" "8.55.2" -"@sentry-internal/replay@8.55.0": - version "8.55.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.55.0.tgz#4c00b22cdf58cac5b3e537f8d4f675f2b021f475" - integrity sha512-roCDEGkORwolxBn8xAKedybY+Jlefq3xYmgN2fr3BTnsXjSYOPC7D1/mYqINBat99nDtvgFvNfRcZPiwwZ1hSw== +"@sentry-internal/replay@8.55.2": + version "8.55.2" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.55.2.tgz#c00cfa8117a6bad93f5522671049760e3780d0c8" + integrity sha512-+W43Z697EVe/OgpGW07B773sa8xO1UbpnW0Cr+E+3FMDb6ZbXlaBUoagPTUkkQPdwBe35SDh6r8y2M3EOPGbxg== dependencies: - "@sentry-internal/browser-utils" "8.55.0" - "@sentry/core" "8.55.0" + "@sentry-internal/browser-utils" "8.55.2" + "@sentry/core" "8.55.2" "@sentry/browser@8.x.x": - version "8.55.0" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.55.0.tgz#9a489e2a54d29c65e6271b4ee594b43679cab7bd" - integrity sha512-1A31mCEWCjaMxJt6qGUK+aDnLDcK6AwLAZnqpSchNysGni1pSn1RWSmk9TBF8qyTds5FH8B31H480uxMPUJ7Cw== + version "8.55.2" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.55.2.tgz#95eff656e5a61f4a22605f88c03746c20c51cf47" + integrity sha512-xHuPIEKhx9zw5quWvv4YgZprnwoVMCfxIhmOIf6KJ9iizyUHeUDcKpLS59xERroqwX4RpvK+l/27AZu4zfZlzQ== dependencies: - "@sentry-internal/browser-utils" "8.55.0" - "@sentry-internal/feedback" "8.55.0" - "@sentry-internal/replay" "8.55.0" - "@sentry-internal/replay-canvas" "8.55.0" - "@sentry/core" "8.55.0" + "@sentry-internal/browser-utils" "8.55.2" + "@sentry-internal/feedback" "8.55.2" + "@sentry-internal/replay" "8.55.2" + "@sentry-internal/replay-canvas" "8.55.2" + "@sentry/core" "8.55.2" -"@sentry/core@8.55.0": - version "8.55.0" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.55.0.tgz#4964920229fcf649237ef13b1533dfc4b9f6b22e" - integrity sha512-6g7jpbefjHYs821Z+EBJ8r4Z7LT5h80YSWRJaylGS4nW5W5Z2KXzpdnyFarv37O7QjauzVC2E+PABmpkw5/JGA== +"@sentry/core@8.55.2": + version "8.55.2" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.55.2.tgz#1a841aefc9b96f7a8a05c30162fa6ebc7e1dc47a" + integrity sha512-YlEBwybUcOQ/KjMHDmof1vwweVnBtBxYlQp7DE3fOdtW4pqqdHWTnTntQs4VgYfxzjJYgtkd9LHlGtg8qy+JVQ== "@sinclair/typebox@^0.24.1": version "0.24.51" @@ -3023,14 +3022,14 @@ integrity sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA== "@sinclair/typebox@^0.27.8": - version "0.27.8" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" - integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + version "0.27.10" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.10.tgz#beefe675f1853f73676aecc915b2bd2ac98c4fc6" + integrity sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA== "@sinclair/typebox@^0.34.0": - version "0.34.41" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.34.41.tgz#aa51a6c1946df2c5a11494a2cdb9318e026db16c" - integrity sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g== + version "0.34.49" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.34.49.tgz#4f1369234f2ecf693866476c3b2e1b54d2a9d68e" + integrity sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A== "@sindresorhus/is@^4.0.0": version "4.6.0" @@ -3066,14 +3065,14 @@ "@sinonjs/commons" "^1.7.0" "@sprig-technologies/sprig-browser@^2.39.0": - version "2.39.0" - resolved "https://registry.yarnpkg.com/@sprig-technologies/sprig-browser/-/sprig-browser-2.39.0.tgz#214b0bbdd63d3815b8443f4013829bca80d3a98d" - integrity sha512-66mdsxc7DlkfWmBacTesvbaXMqgzJRR5B/tRpy2a7T0s5L8+ehAUj+SaivddtjGDClWD4Tf3rk6K7rV33U8PTA== + version "2.45.0" + resolved "https://registry.yarnpkg.com/@sprig-technologies/sprig-browser/-/sprig-browser-2.45.0.tgz#66f482f0881fc3b0b78cc25d078cd5b8ecaca6d8" + integrity sha512-hG+oVH5ncr9EEkjGZxtKfm71QhDL4LElb7wf0oK8eM+ozE7qyszzz94+2OkfRlBWhzTk39XUJdVwo4fwr2Oa9g== -"@standard-schema/spec@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@standard-schema/spec/-/spec-1.0.0.tgz#f193b73dc316c4170f2e82a881da0f550d551b9c" - integrity sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA== +"@standard-schema/spec@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@standard-schema/spec/-/spec-1.1.0.tgz#a79b55dbaf8604812f52d140b2c9ab41bc150bb8" + integrity sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w== "@standard-schema/utils@^0.3.0": version "0.3.0" @@ -3998,84 +3997,96 @@ "@svgr/plugin-svgo" "^5.5.0" loader-utils "^2.0.0" -"@swc/core-darwin-arm64@1.15.3": - version "1.15.3" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.3.tgz#bd0bd3ab7730e3ffa64cf200c0ed7c572cbaba97" - integrity sha512-AXfeQn0CvcQ4cndlIshETx6jrAM45oeUrK8YeEY6oUZU/qzz0Id0CyvlEywxkWVC81Ajpd8TQQ1fW5yx6zQWkQ== - -"@swc/core-darwin-x64@1.15.3": - version "1.15.3" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.15.3.tgz#502b1e1c680df6b962265ca81a0c1a23e6ff070f" - integrity sha512-p68OeCz1ui+MZYG4wmfJGvcsAcFYb6Sl25H9TxWl+GkBgmNimIiRdnypK9nBGlqMZAcxngNPtnG3kEMNnvoJ2A== - -"@swc/core-linux-arm-gnueabihf@1.15.3": - version "1.15.3" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.3.tgz#e32cc6a2e06a75060d6f598ba2ca6f96c5c0cc43" - integrity sha512-Nuj5iF4JteFgwrai97mUX+xUOl+rQRHqTvnvHMATL/l9xE6/TJfPBpd3hk/PVpClMXG3Uvk1MxUFOEzM1JrMYg== - -"@swc/core-linux-arm64-gnu@1.15.3": - version "1.15.3" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.3.tgz#9b9861bc44059e393d4baf98b3cd3d6c4ea6f521" - integrity sha512-2Nc/s8jE6mW2EjXWxO/lyQuLKShcmTrym2LRf5Ayp3ICEMX6HwFqB1EzDhwoMa2DcUgmnZIalesq2lG3krrUNw== - -"@swc/core-linux-arm64-musl@1.15.3": - version "1.15.3" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.3.tgz#f6388743e5a159018bd468e8f710940b2614384b" - integrity sha512-j4SJniZ/qaZ5g8op+p1G9K1z22s/EYGg1UXIb3+Cg4nsxEpF5uSIGEE4mHUfA70L0BR9wKT2QF/zv3vkhfpX4g== - -"@swc/core-linux-x64-gnu@1.15.3": - version "1.15.3" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.3.tgz#15fea551c7a3aeb1bdc3ad5c652d73c9321ddba8" - integrity sha512-aKttAZnz8YB1VJwPQZtyU8Uk0BfMP63iDMkvjhJzRZVgySmqt/apWSdnoIcZlUoGheBrcqbMC17GGUmur7OT5A== - -"@swc/core-linux-x64-musl@1.15.3": - version "1.15.3" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.3.tgz#d3f17bab4ffcadbb47f135e6a14d6f3e401af289" - integrity sha512-oe8FctPu1gnUsdtGJRO2rvOUIkkIIaHqsO9xxN0bTR7dFTlPTGi2Fhk1tnvXeyAvCPxLIcwD8phzKg6wLv9yug== - -"@swc/core-win32-arm64-msvc@1.15.3": - version "1.15.3" - resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.3.tgz#9da386df7fed00b3473bcf4281ff3fcd14726d2c" - integrity sha512-L9AjzP2ZQ/Xh58e0lTRMLvEDrcJpR7GwZqAtIeNLcTK7JVE+QineSyHp0kLkO1rttCHyCy0U74kDTj0dRz6raA== - -"@swc/core-win32-ia32-msvc@1.15.3": - version "1.15.3" - resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.3.tgz#c398d4f0f10ffec2151a79733ee1ce86a945a1ea" - integrity sha512-B8UtogMzErUPDWUoKONSVBdsgKYd58rRyv2sHJWKOIMCHfZ22FVXICR4O/VwIYtlnZ7ahERcjayBHDlBZpR0aw== - -"@swc/core-win32-x64-msvc@1.15.3": - version "1.15.3" - resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.3.tgz#715596b034a654c82b03ef734a9b44c29bcd3a68" - integrity sha512-SpZKMR9QBTecHeqpzJdYEfgw30Oo8b/Xl6rjSzBt1g0ZsXyy60KLXrp6IagQyfTYqNYE/caDvwtF2FPn7pomog== +"@swc/core-darwin-arm64@1.15.33": + version "1.15.33" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.33.tgz#d84134fb80417d41128739f0b9014542e3ed9dd3" + integrity sha512-N+L0uXhuO7FIfzqwgxmzv0zIpV0qEp8wPX3QQs2p4atjMoywup2JTeDlXPw+z9pWJGCae3JjM+tZ6myclI+2gA== + +"@swc/core-darwin-x64@1.15.33": + version "1.15.33" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.15.33.tgz#0badb9834071f1c6005986571d4a96359c1d7cd0" + integrity sha512-/Il4QHSOhV4FekbsDtkrNmKbsX26oSysvgrRswa/RYOHXAkwXDbB4jaeKq6PsJLSPkzJ2KzQ061gtBnk0vNHfA== + +"@swc/core-linux-arm-gnueabihf@1.15.33": + version "1.15.33" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.33.tgz#b7577a825b59d98b6a9a5c991d842046efe1c34a" + integrity sha512-C64hBnBxq4viOPQ8hlx+2lJ23bzZBGnjw7ryALmS+0Q3zHmwO8lw1/DArLENw4Q18/0w5wdEO1k3m1wWNtKGqQ== + +"@swc/core-linux-arm64-gnu@1.15.33": + version "1.15.33" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.33.tgz#304c48321494a18c67b2913c273b08674ee70d8c" + integrity sha512-TRJfnJbX3jqpxRDRoieMzRiCBS5jOmXNb3iQXmcgjFEHKLnAgK1RZRU8Cq1MsPqO4jAJp/ld1G4O3fXuxv85uw== + +"@swc/core-linux-arm64-musl@1.15.33": + version "1.15.33" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.33.tgz#d116cbc04ccb4f4ee810da6bca79d4423605dbcd" + integrity sha512-il7tYM+CpUNzieQbwAjFT1P8zqAhmGWNAGhQZBnxurXZ0aNn+5nqYFTEUKNZl7QibtT0uQXzTZrNGHCIj6Y1Og== + +"@swc/core-linux-ppc64-gnu@1.15.33": + version "1.15.33" + resolved "https://registry.yarnpkg.com/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.33.tgz#f5354dba36db9414305bab344c817d57b8b457c2" + integrity sha512-ZtNBwN0Z7CFj9Il0FcPaKdjgP7URyKu/3RfH46vq+0paOBqLj4NYldD6Qo//Duif/7IOtAraUfDOmp0PLAufog== + +"@swc/core-linux-s390x-gnu@1.15.33": + version "1.15.33" + resolved "https://registry.yarnpkg.com/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.33.tgz#016df9f4c9d7fd65b85ca9c558c5aec341f06da0" + integrity sha512-De1IyajoOmhOYYjw/lx66bKlyDpHZTueqwpDrWgf5O7T6d1ODeJJO9/OqMBmrBQc5C+dNnlmIufHsp4QVCWufA== + +"@swc/core-linux-x64-gnu@1.15.33": + version "1.15.33" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.33.tgz#49f36558ede072e71999aa37f123367daed2a662" + integrity sha512-mGTH0YxmUN+x6vRN/I6NOk5X0ogNktkwPnJ94IMvR7QjhRDwL0O8RXEDhyUM0YtwWrryBOqaJQBX4zruxEPRGw== + +"@swc/core-linux-x64-musl@1.15.33": + version "1.15.33" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.33.tgz#b096665f5cfeee2612325f301da5c1590b10d8f3" + integrity sha512-hj628ZkSEJf6zMf5VMbYrG2O6QqyTIp2qwY6VlCjvIa9lAEZ5c2lfPblCLVGYubTeLJDxadLB/CxqQYOQABeEQ== + +"@swc/core-win32-arm64-msvc@1.15.33": + version "1.15.33" + resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.33.tgz#f3101263a0dbaa173ec47638c9719d0b89838bd2" + integrity sha512-GV2oohtN2/5+KSccl86VULu3aT+LrISC8uzgSq0FRnikpD+Zwc+sBlXmoKQ+Db6jI57ITUOIB8jRkdGMABC29g== + +"@swc/core-win32-ia32-msvc@1.15.33": + version "1.15.33" + resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.33.tgz#eb981ef5613d42c9220559bdb0c8bc58cf6c3eb9" + integrity sha512-gtyvzSNR8DHKfFEA2uqb8Ld1myqi6uEg2jyeUq3ikn5ytYs7H8RpZYC8mdy4NXr8hfcdJfCLXPlYaqqfBXpoEQ== + +"@swc/core-win32-x64-msvc@1.15.33": + version "1.15.33" + resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.33.tgz#a2fed9956933027ceb368857bac4bb4ee203d47c" + integrity sha512-d6fRqQSkJI+kmMEBWaDQ7TMl8+YjLYbwRUPZQ9DY0ORBJeTzOrG0twvfvlZ2xgw6jA0ScQKgfBm4vHLSLl5Hqg== "@swc/core@^1.3.82": - version "1.15.3" - resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.15.3.tgz#2d0a5c4ac4c180c3dbf2f6d5d958b9fcbaa9755f" - integrity sha512-Qd8eBPkUFL4eAONgGjycZXj1jFCBW8Fd+xF0PzdTlBCWQIV1xnUT7B93wUANtW3KGjl3TRcOyxwSx/u/jyKw/Q== + version "1.15.33" + resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.15.33.tgz#2a6571c8aca961925f14beae52b3f43c18370fc6" + integrity sha512-jOlwnFV2xhuuZeAUILGFULeR6vDPfijEJ57evfocwznQldLU3w2cZ9bSDryY9ip+AsM3r1NJKzf47V2NXebkeQ== dependencies: "@swc/counter" "^0.1.3" - "@swc/types" "^0.1.25" + "@swc/types" "^0.1.26" optionalDependencies: - "@swc/core-darwin-arm64" "1.15.3" - "@swc/core-darwin-x64" "1.15.3" - "@swc/core-linux-arm-gnueabihf" "1.15.3" - "@swc/core-linux-arm64-gnu" "1.15.3" - "@swc/core-linux-arm64-musl" "1.15.3" - "@swc/core-linux-x64-gnu" "1.15.3" - "@swc/core-linux-x64-musl" "1.15.3" - "@swc/core-win32-arm64-msvc" "1.15.3" - "@swc/core-win32-ia32-msvc" "1.15.3" - "@swc/core-win32-x64-msvc" "1.15.3" + "@swc/core-darwin-arm64" "1.15.33" + "@swc/core-darwin-x64" "1.15.33" + "@swc/core-linux-arm-gnueabihf" "1.15.33" + "@swc/core-linux-arm64-gnu" "1.15.33" + "@swc/core-linux-arm64-musl" "1.15.33" + "@swc/core-linux-ppc64-gnu" "1.15.33" + "@swc/core-linux-s390x-gnu" "1.15.33" + "@swc/core-linux-x64-gnu" "1.15.33" + "@swc/core-linux-x64-musl" "1.15.33" + "@swc/core-win32-arm64-msvc" "1.15.33" + "@swc/core-win32-ia32-msvc" "1.15.33" + "@swc/core-win32-x64-msvc" "1.15.33" "@swc/counter@^0.1.3": version "0.1.3" resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.3.tgz#cc7463bd02949611c6329596fccd2b0ec782b0e9" integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== -"@swc/types@^0.1.25": - version "0.1.25" - resolved "https://registry.yarnpkg.com/@swc/types/-/types-0.1.25.tgz#b517b2a60feb37dd933e542d93093719e4cf1078" - integrity sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g== +"@swc/types@^0.1.26": + version "0.1.26" + resolved "https://registry.yarnpkg.com/@swc/types/-/types-0.1.26.tgz#2a976a1870caef1992316dda1464150ee36968b5" + integrity sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw== dependencies: "@swc/counter" "^0.1.3" @@ -4150,6 +4161,14 @@ dependencies: prop-types "^15.6.2" +"@tokenizer/inflate@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@tokenizer/inflate/-/inflate-0.4.1.tgz#fa6cdb8366151b3cc8426bf9755c1ea03a2fba08" + integrity sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA== + dependencies: + debug "^4.4.3" + token-types "^6.1.1" + "@tokenizer/token@^0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276" @@ -4160,11 +4179,6 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== -"@trysound/sax@0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" - integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== - "@tsconfig/node10@^1.0.7": version "1.0.12" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.12.tgz#be57ceac1e4692b41be9de6be8c32a106636dba4" @@ -4185,10 +4199,10 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== -"@tybys/wasm-util@^0.10.0": - version "0.10.1" - resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.10.1.tgz#ecddd3205cf1e2d5274649ff0eedd2991ed7f414" - integrity sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg== +"@tybys/wasm-util@^0.10.1": + version "0.10.2" + resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.10.2.tgz#12b3a1b33db1f9cad4ddff1f604ab7dd00bf464e" + integrity sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg== dependencies: tslib "^2.4.0" @@ -4292,9 +4306,9 @@ "@types/node" "*" "@types/debug@^4.0.0": - version "4.1.12" - resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917" - integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ== + version "4.1.13" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.13.tgz#22d1cc9d542d3593caea764f974306ab36286ee7" + integrity sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw== dependencies: "@types/ms" "*" @@ -4340,22 +4354,6 @@ resolved "https://registry.yarnpkg.com/@types/escodegen/-/escodegen-0.0.6.tgz#5230a9ce796e042cda6f086dbf19f22ea330659c" integrity sha512-AjwI4MvWx3HAOaZqYsjKWyEObT9lcVV0Y0V8nXo6cXzN8ZiMxVhf6F3d/UNvXVGKrEzL/Dluc5p+y9GkzlTWig== -"@types/eslint-scope@^3.7.7": - version "3.7.7" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" - integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg== - dependencies: - "@types/eslint" "*" - "@types/estree" "*" - -"@types/eslint@*": - version "9.6.1" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-9.6.1.tgz#d5795ad732ce81715f27f75da913004a56751584" - integrity sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag== - dependencies: - "@types/estree" "*" - "@types/json-schema" "*" - "@types/eslint@^7.29.0 || ^8.4.1": version "8.56.12" resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.56.12.tgz#1657c814ffeba4d2f84c0d4ba0f44ca7ea1ca53a" @@ -4365,9 +4363,9 @@ "@types/json-schema" "*" "@types/estree@*", "@types/estree@^1.0.8": - version "1.0.8" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" - integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== + version "1.0.9" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.9.tgz#cf3f0e876d7bee15a93ab925b82bf570a3904a24" + integrity sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg== "@types/estree@0.0.39": version "0.0.39" @@ -4380,9 +4378,9 @@ integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== "@types/express-serve-static-core@*", "@types/express-serve-static-core@^5.0.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz#74f47555b3d804b54cb7030e6f9aa0c7485cfc5b" - integrity sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA== + version "5.1.1" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz#1a77faffee9572d39124933259be2523837d7eaa" + integrity sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A== dependencies: "@types/node" "*" "@types/qs" "*" @@ -4390,9 +4388,9 @@ "@types/send" "*" "@types/express-serve-static-core@^4.17.33": - version "4.19.7" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz#f1d306dcc03b1aafbfb6b4fe684cce8a31cffc10" - integrity sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg== + version "4.19.8" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz#99b960322a4d576b239a640ab52ef191989b036f" + integrity sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA== dependencies: "@types/node" "*" "@types/qs" "*" @@ -4467,9 +4465,9 @@ integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg== "@types/http-cache-semantics@*": - version "4.0.4" - resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz#b979ebad3919799c979b17c72621c0bc0a31c6c4" - integrity sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA== + version "4.2.0" + resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz#f6a7788f438cbfde15f29acad46512b4c01913b3" + integrity sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q== "@types/http-errors@*": version "2.0.5" @@ -4546,9 +4544,9 @@ integrity sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q== "@types/lodash@^4.14.167", "@types/lodash@^4.17.21": - version "4.17.21" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.21.tgz#b806831543d696b14f8112db600ea9d3a1df6ea4" - integrity sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ== + version "4.17.24" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.24.tgz#4ae334fc62c0e915ca8ed8e35dcc6d4eeb29215f" + integrity sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ== "@types/markdown-it@^12.2.3": version "12.2.3" @@ -4618,11 +4616,11 @@ "@types/node" "*" "@types/node@*": - version "25.0.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-25.0.0.tgz#c0e0022c3c7b41635c49322e6b3a0279fffa7d62" - integrity sha512-rl78HwuZlaDIUSeUKkmogkhebA+8K1Hy7tddZuJ3D0xV8pZSfsYGTsliGUol1JPzu9EKnTxPC4L1fiWouStRew== + version "25.9.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-25.9.1.tgz#3bda556db500ae4319c08e7fc9ab94f19013ba0b" + integrity sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg== dependencies: - undici-types "~7.16.0" + undici-types ">=7.24.0 <7.24.7" "@types/node@^18.0.0", "@types/node@^18.19.130": version "18.19.130" @@ -4667,9 +4665,9 @@ integrity sha512-hroOstUScF6zhIi+5+x0dzqrHA1EJi+Irri6b1fxolMTqqHIV/Cg77EtnQcZqZCu8hR3mX2BzIxN4/GzI68Kfw== "@types/qs@*", "@types/qs@^6.9.5": - version "6.14.0" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.14.0.tgz#d8b60cecf62f2db0fb68e5e006077b9178b85de5" - integrity sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ== + version "6.15.1" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.15.1.tgz#8606884272c63f0db96986bd3548650d8a9388bf" + integrity sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw== "@types/range-parser@*": version "1.2.7" @@ -4792,11 +4790,11 @@ integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== "@types/sanitize-html@^2.16.0": - version "2.16.0" - resolved "https://registry.yarnpkg.com/@types/sanitize-html/-/sanitize-html-2.16.0.tgz#860d72c1ba8a5d044946f37559cc359c0a13b24e" - integrity sha512-l6rX1MUXje5ztPT0cAFtUayXF06DqPhRyfVXareEN5gGCFaP/iwsxIyKODr9XDhfxPpN6vXUFNfo5kZMXCxBtw== + version "2.16.1" + resolved "https://registry.yarnpkg.com/@types/sanitize-html/-/sanitize-html-2.16.1.tgz#27b9ac6cc29838f7a048bfec0113e8ad00918d0a" + integrity sha512-n9wjs8bCOTyN/ynwD8s/nTcTreIHB1vf31vhLMGqUPNHaweKC4/fAl4Dj+hUlCTKYgm4P3k83fmiFfzkZ6sgMA== dependencies: - htmlparser2 "^8.0.0" + htmlparser2 "^10.1" "@types/semver@^7.3.12", "@types/semver@^7.3.4": version "7.7.1" @@ -5040,10 +5038,10 @@ "@typescript-eslint/types" "5.62.0" eslint-visitor-keys "^3.3.0" -"@uiw/codemirror-extensions-basic-setup@4.25.8": - version "4.25.8" - resolved "https://registry.yarnpkg.com/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.25.8.tgz#a3848e6e8ea13cec56cfe698e39701d4210ac0f7" - integrity sha512-9Rr+liiBmK4xzZHszL+twNRJApthqmITBwDP3emNTtTrkBFN4gHlqfp+nodKmoVt1+bUH1qQCtyqt+7dbDTHiw== +"@uiw/codemirror-extensions-basic-setup@4.25.9": + version "4.25.9" + resolved "https://registry.yarnpkg.com/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.25.9.tgz#67e584f437ea40eb2fbbc29a1b489d451c6164e2" + integrity sha512-QFAqr+pu6lDmNpAlecODcF49TlsrZ0bj15zPzfhiqSDl+Um3EsDLFLppixC7kFLn+rdDM2LTvVjn5CPvefpRgw== dependencies: "@codemirror/autocomplete" "^6.0.0" "@codemirror/commands" "^6.0.0" @@ -5054,118 +5052,135 @@ "@codemirror/view" "^6.0.0" "@uiw/react-codemirror@^4.25.8": - version "4.25.8" - resolved "https://registry.yarnpkg.com/@uiw/react-codemirror/-/react-codemirror-4.25.8.tgz#40e8ab9df1955052365520a785372d9097d5924b" - integrity sha512-A0aLOuJZm2yJ+U9GlMFwxwFciztjd5LhcAG4SMqFxdD58wH+sCQXuY4UU5J2hqgS390qAlShtUgREvJPUonbuQ== + version "4.25.9" + resolved "https://registry.yarnpkg.com/@uiw/react-codemirror/-/react-codemirror-4.25.9.tgz#f0c1f7c9b28c696dcb339e074fe8aa02912214c8" + integrity sha512-HftqCBUYShAOH0pGi1CHP8vfm5L8fQ3+0j0VI6lQD6QpK+UBu3J7nxfEN5O/BXMilMNf9ZyFJRvRcuMMOLHMng== dependencies: "@babel/runtime" "^7.18.6" "@codemirror/commands" "^6.1.0" "@codemirror/state" "^6.1.1" "@codemirror/theme-one-dark" "^6.0.0" - "@uiw/codemirror-extensions-basic-setup" "4.25.8" + "@uiw/codemirror-extensions-basic-setup" "4.25.9" codemirror "^6.0.0" "@ungap/structured-clone@^1.0.0", "@ungap/structured-clone@^1.2.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" - integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== - -"@unrs/resolver-binding-android-arm-eabi@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz#9f5b04503088e6a354295e8ea8fe3cb99e43af81" - integrity sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw== - -"@unrs/resolver-binding-android-arm64@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz#7414885431bd7178b989aedc4d25cccb3865bc9f" - integrity sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g== - -"@unrs/resolver-binding-darwin-arm64@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz#b4a8556f42171fb9c9f7bac8235045e82aa0cbdf" - integrity sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g== - -"@unrs/resolver-binding-darwin-x64@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz#fd4d81257b13f4d1a083890a6a17c00de571f0dc" - integrity sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ== - -"@unrs/resolver-binding-freebsd-x64@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz#d2513084d0f37c407757e22f32bd924a78cfd99b" - integrity sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw== - -"@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz#844d2605d057488d77fab09705f2866b86164e0a" - integrity sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw== - -"@unrs/resolver-binding-linux-arm-musleabihf@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz#204892995cefb6bd1d017d52d097193bc61ddad3" - integrity sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw== - -"@unrs/resolver-binding-linux-arm64-gnu@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz#023eb0c3aac46066a10be7a3f362e7b34f3bdf9d" - integrity sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ== - -"@unrs/resolver-binding-linux-arm64-musl@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz#9e6f9abb06424e3140a60ac996139786f5d99be0" - integrity sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w== - -"@unrs/resolver-binding-linux-ppc64-gnu@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz#b111417f17c9d1b02efbec8e08398f0c5527bb44" - integrity sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA== - -"@unrs/resolver-binding-linux-riscv64-gnu@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz#92ffbf02748af3e99873945c9a8a5ead01d508a9" - integrity sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ== - -"@unrs/resolver-binding-linux-riscv64-musl@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz#0bec6f1258fc390e6b305e9ff44256cb207de165" - integrity sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew== - -"@unrs/resolver-binding-linux-s390x-gnu@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz#577843a084c5952f5906770633ccfb89dac9bc94" - integrity sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg== - -"@unrs/resolver-binding-linux-x64-gnu@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz#36fb318eebdd690f6da32ac5e0499a76fa881935" - integrity sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w== - -"@unrs/resolver-binding-linux-x64-musl@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz#bfb9af75f783f98f6a22c4244214efe4df1853d6" - integrity sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA== - -"@unrs/resolver-binding-wasm32-wasi@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz#752c359dd875684b27429500d88226d7cc72f71d" - integrity sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ== - dependencies: - "@napi-rs/wasm-runtime" "^0.2.11" - -"@unrs/resolver-binding-win32-arm64-msvc@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz#ce5735e600e4c2fbb409cd051b3b7da4a399af35" - integrity sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw== - -"@unrs/resolver-binding-win32-ia32-msvc@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz#72fc57bc7c64ec5c3de0d64ee0d1810317bc60a6" - integrity sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ== - -"@unrs/resolver-binding-win32-x64-msvc@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz#538b1e103bf8d9864e7b85cc96fa8d6fb6c40777" - integrity sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g== + version "1.3.1" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.1.tgz#0e8f34854df7966b09304a18e808b23997bb9fc1" + integrity sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ== + +"@unrs/resolver-binding-android-arm-eabi@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz#98a9fee62c01f209747a4ab5855f1ced38a6d03a" + integrity sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w== + +"@unrs/resolver-binding-android-arm64@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz#46b7e8a1393f907462324f1576e8883529acf066" + integrity sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ== + +"@unrs/resolver-binding-darwin-arm64@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz#0ea07b00e2583ab004b853d4c02ec5f0745d490c" + integrity sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w== + +"@unrs/resolver-binding-darwin-x64@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz#a2a6901ed58449b91b4438e582f6890cba956049" + integrity sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA== + +"@unrs/resolver-binding-freebsd-x64@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz#ebe6fe7f6706b7378ea4a48a024602e9c2f48f89" + integrity sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg== + +"@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz#e6040fedaa240124419d35b25b69c5fa15ddb499" + integrity sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A== + +"@unrs/resolver-binding-linux-arm-musleabihf@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz#d217a8fb59f659c131539326c140e7b62e3e3c6a" + integrity sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g== + +"@unrs/resolver-binding-linux-arm64-gnu@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz#edab13c46a45783a7e01351e113825c04f352e24" + integrity sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg== + +"@unrs/resolver-binding-linux-arm64-musl@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz#e5e195db1130f7d3b6aa2fd67b3c9fe1ea4859a0" + integrity sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA== + +"@unrs/resolver-binding-linux-loong64-gnu@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz#f01d22e091bae13016f4636698d9dcbbda775c3e" + integrity sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q== + +"@unrs/resolver-binding-linux-loong64-musl@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz#7d23efcb98adf076bfbcecc27b4212c36aa6697d" + integrity sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew== + +"@unrs/resolver-binding-linux-ppc64-gnu@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz#1f35f1eaa322f33cf2d96dac27f0626a93ffe2f6" + integrity sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg== + +"@unrs/resolver-binding-linux-riscv64-gnu@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz#674faa696f5ce96f214873946a1e2d6ca96723dd" + integrity sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A== + +"@unrs/resolver-binding-linux-riscv64-musl@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz#37835fdd0b472ecdcffccd4288f19018454b138c" + integrity sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w== + +"@unrs/resolver-binding-linux-s390x-gnu@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz#b6edf13db4bb0accdcd1ad482a4eea0301de9224" + integrity sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw== + +"@unrs/resolver-binding-linux-x64-gnu@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz#daddad00bf65a405202284da1eb1db8eb83b218f" + integrity sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ== + +"@unrs/resolver-binding-linux-x64-musl@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz#dfdff1e0c2bad25420b41c76a746011c3983b9bb" + integrity sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A== + +"@unrs/resolver-binding-openharmony-arm64@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz#ce07c4f5e7b42f7bfce45e7629b8659063aefefe" + integrity sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ== + +"@unrs/resolver-binding-wasm32-wasi@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz#82514f0506cfaf65f17fe16095f92d450e487183" + integrity sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A== + dependencies: + "@emnapi/core" "1.10.0" + "@emnapi/runtime" "1.10.0" + "@napi-rs/wasm-runtime" "^1.1.4" + +"@unrs/resolver-binding-win32-arm64-msvc@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz#521427dd59a8f4740ddd1dc7c3bc6af1aa1d260d" + integrity sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g== + +"@unrs/resolver-binding-win32-ia32-msvc@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz#05b63286ff2da37e0ce3083b8390884385efff62" + integrity sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g== + +"@unrs/resolver-binding-win32-x64-msvc@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz#72da0da48d72b1e87831b9c0308931d3f4669027" + integrity sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA== "@wdio/junit-reporter@^7.40.0": version "7.40.0" @@ -5390,14 +5405,7 @@ abab@^2.0.3, abab@^2.0.5, abab@^2.0.6: resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== -abort-controller@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" - integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== - dependencies: - event-target-shim "^5.0.0" - -accepts@~1.3.4, accepts@~1.3.8: +accepts@~1.3.8: version "1.3.8" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== @@ -5429,9 +5437,9 @@ acorn-walk@^7.1.1, acorn-walk@^7.2.0: integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== acorn-walk@^8.0.0, acorn-walk@^8.0.2, acorn-walk@^8.1.1: - version "8.3.4" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" - integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== + version "8.3.5" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.5.tgz#8a6b8ca8fc5b34685af15dabb44118663c296496" + integrity sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw== dependencies: acorn "^8.11.0" @@ -5440,10 +5448,10 @@ acorn@^7.1.1, acorn@^7.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.0.4, acorn@^8.11.0, acorn@^8.14.0, acorn@^8.15.0, acorn@^8.2.4, acorn@^8.4.1, acorn@^8.9.0: - version "8.15.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" - integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== +acorn@^8.0.4, acorn@^8.11.0, acorn@^8.14.0, acorn@^8.15.0, acorn@^8.16.0, acorn@^8.2.4, acorn@^8.4.1, acorn@^8.9.0: + version "8.16.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.16.0.tgz#4ce79c89be40afe7afe8f3adb902a1f1ce9ac08a" + integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw== address@^1.0.1, address@^1.1.2: version "1.2.2" @@ -5505,20 +5513,20 @@ ajv-keywords@^5.1.0: dependencies: fast-deep-equal "^3.1.3" -ajv@8.12.0: - version "8.12.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" - integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== +ajv@8.18.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.18.0.tgz#8864186b6738d003eb3a933172bb3833e10cefbc" + integrity sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A== dependencies: - fast-deep-equal "^3.1.1" + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" json-schema-traverse "^1.0.0" require-from-string "^2.0.2" - uri-js "^4.2.2" ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.5.3: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + version "6.15.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.15.0.tgz#07e982c74626167aa7a2495c53817892d7139492" + integrity sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw== dependencies: fast-deep-equal "^3.1.1" fast-json-stable-stringify "^2.0.0" @@ -5526,9 +5534,9 @@ ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.5.3: uri-js "^4.2.2" ajv@^8.0.0, ajv@^8.6.0, ajv@^8.9.0: - version "8.17.1" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" - integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== + version "8.20.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.20.0.tgz#304b3636add88ba7d936760dd50ece006dea95f9" + integrity sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA== dependencies: fast-deep-equal "^3.1.3" fast-uri "^3.0.1" @@ -5581,7 +5589,7 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -ansi-regex@^6.0.1: +ansi-regex@^6.2.2: version "6.2.2" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.2.2.tgz#60216eea464d864597ce2832000738a0589650c1" integrity sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg== @@ -5895,14 +5903,13 @@ auto-css-modules-webpack-plugin@^1.1.1: acorn-walk "^8.0.2" autoprefixer@^10.4.12, autoprefixer@^10.4.13, autoprefixer@^10.4.22: - version "10.4.22" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.22.tgz#90b27ab55ec0cf0684210d1f056f7d65dac55f16" - integrity sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg== + version "10.5.0" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.5.0.tgz#33d87e443430f020a0f85319d6ff1593cb291be9" + integrity sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong== dependencies: - browserslist "^4.27.0" - caniuse-lite "^1.0.30001754" + browserslist "^4.28.2" + caniuse-lite "^1.0.30001787" fraction.js "^5.3.4" - normalize-range "^0.1.2" picocolors "^1.1.1" postcss-value-parser "^4.2.0" @@ -5914,17 +5921,18 @@ available-typed-arrays@^1.0.7: possible-typed-array-names "^1.0.0" axe-core@^4.10.0: - version "4.11.0" - resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.11.0.tgz#16f74d6482e343ff263d4f4503829e9ee91a86b6" - integrity sha512-ilYanEU8vxxBexpJd8cWM4ElSQq4QctCLKih0TSfjIfCQTeyH/6zVrmIJfLPrKTKJRbiG+cfnZbQIjAlJmF1jQ== + version "4.11.4" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.11.4.tgz#5b535e381ff1e61ffdd615e5483d16186d3b46a5" + integrity sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA== -axios@*, axios@^1.13.2, axios@^1.15.0, axios@^1.7.4: - version "1.15.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.15.0.tgz#0fcee91ef03d386514474904b27863b2c683bf4f" - integrity sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q== +axios@*, axios@^1.13.5, axios@^1.15.0, axios@^1.7.4: + version "1.16.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.16.1.tgz#517e29291d19d6e8cf919ff264f4fe157261ba12" + integrity sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A== dependencies: - follow-redirects "^1.15.11" + follow-redirects "^1.16.0" form-data "^4.0.5" + https-proxy-agent "^5.0.1" proxy-from-env "^2.1.0" axobject-query@^4.1.0: @@ -6068,13 +6076,13 @@ babel-plugin-named-exports-order@^0.0.2: resolved "https://registry.yarnpkg.com/babel-plugin-named-exports-order/-/babel-plugin-named-exports-order-0.0.2.tgz#ae14909521cf9606094a2048239d69847540cb09" integrity sha512-OgOYHOLoRK+/mvXU9imKHlG6GkPLYrUCvFXG/CM93R/aNNO8pOOF4aS+S8CCHMDQoNSeiOYEZb/G6RwL95Jktw== -babel-plugin-polyfill-corejs2@^0.4.14: - version "0.4.14" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz#8101b82b769c568835611542488d463395c2ef8f" - integrity sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg== +babel-plugin-polyfill-corejs2@^0.4.14, babel-plugin-polyfill-corejs2@^0.4.15: + version "0.4.17" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz#198f970f1c99a856b466d1187e88ce30bd199d91" + integrity sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w== dependencies: - "@babel/compat-data" "^7.27.7" - "@babel/helper-define-polyfill-provider" "^0.6.5" + "@babel/compat-data" "^7.28.6" + "@babel/helper-define-polyfill-provider" "^0.6.8" semver "^6.3.1" babel-plugin-polyfill-corejs3@^0.13.0: @@ -6085,12 +6093,20 @@ babel-plugin-polyfill-corejs3@^0.13.0: "@babel/helper-define-polyfill-provider" "^0.6.5" core-js-compat "^3.43.0" -babel-plugin-polyfill-regenerator@^0.6.5: - version "0.6.5" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz#32752e38ab6f6767b92650347bf26a31b16ae8c5" - integrity sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg== +babel-plugin-polyfill-corejs3@^0.14.0: + version "0.14.2" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz#6ac08d2f312affb70c4c69c0fbba4cb417ee5587" + integrity sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g== dependencies: - "@babel/helper-define-polyfill-provider" "^0.6.5" + "@babel/helper-define-polyfill-provider" "^0.6.8" + core-js-compat "^3.48.0" + +babel-plugin-polyfill-regenerator@^0.6.5, babel-plugin-polyfill-regenerator@^0.6.6: + version "0.6.8" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz#8a6bfd5dd54239362b3d06ce47ac52b2d95d7721" + integrity sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.8" babel-plugin-react-css-modules@^5.2.6: version "5.2.6" @@ -6111,15 +6127,14 @@ babel-plugin-react-css-modules@^5.2.6: postcss-modules-values "^1.3.0" "babel-plugin-styled-components@>= 1.12.0": - version "2.1.4" - resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.4.tgz#9a1f37c7f32ef927b4b008b529feb4a2c82b1092" - integrity sha512-Xgp9g+A/cG47sUyRwwYxGM4bR/jDRg5N6it/8+HxCnbT5XNKSKDT9xm4oag/osgqjC2It/vH0yXsomOG6k558g== + version "2.3.0" + resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-2.3.0.tgz#5cd27eded23af609ff57048250e3f59d68e3aa31" + integrity sha512-nP/y6PbBqS/qtKROnJCgpGo8hYUzlBAVXN1QAjSBANL6vZiQXPQN7FYW/nUwoxY7nZhBEGm9T5tjL9gbzwulDw== dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-module-imports" "^7.22.5" - "@babel/plugin-syntax-jsx" "^7.22.5" - lodash "^4.17.21" - picomatch "^2.3.1" + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-module-imports" "^7.25.9" + "@babel/plugin-syntax-jsx" "^7.25.9" + picomatch "^4.0.2" babel-plugin-transform-react-remove-prop-types@^0.4.24: version "0.4.24" @@ -6196,6 +6211,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +balanced-match@^4.0.2: + version "4.0.4" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a" + integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== + base64-arraybuffer@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#1c37589a7c4b0746e34bd1feb951da2df01c1bdc" @@ -6206,10 +6226,10 @@ base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== -baseline-browser-mapping@^2.9.0: - version "2.9.6" - resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.6.tgz#82de0f7ee5860df86d60daf0d9524ae7227eeee7" - integrity sha512-v9BVVpOTLB59C9E7aSnmIF8h7qRsFpx+A2nugVMTszEOMcfjlZMsXRm4LF23I3Z9AJxc8ANpIvzbzONoX9VJlg== +baseline-browser-mapping@^2.10.12: + version "2.10.31" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.31.tgz#9c6825f052601ce6974a90dd49683b1726887b0b" + integrity sha512-MujYO3eP72uvmSE0i4wltsodRfIpZATP3jvzRNRGGxgzId7aVocVJJV3nf01qnzzKFGxQVC9bpWxl5cjxTr/7Q== batch@0.6.1: version "0.6.1" @@ -6263,10 +6283,10 @@ bluebird@3.7.2, bluebird@^3.7.2: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -body-parser@~1.20.3: - version "1.20.4" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.4.tgz#f8e20f4d06ca8a50a71ed329c15dccad1cdc547f" - integrity sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA== +body-parser@~1.20.5: + version "1.20.5" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.5.tgz#303c8c34423d1d6fa799bc764e93c1e4dc6ebf64" + integrity sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA== dependencies: bytes "~3.1.2" content-type "~1.0.5" @@ -6276,7 +6296,7 @@ body-parser@~1.20.3: http-errors "~2.0.1" iconv-lite "~0.4.24" on-finished "~2.4.1" - qs "~6.14.0" + qs "~6.15.1" raw-body "~2.5.3" type-is "~1.6.18" unpipe "~1.0.0" @@ -6321,20 +6341,27 @@ bplist-parser@^0.2.0: big-integer "^1.6.44" brace-expansion@^1.1.7: - version "1.1.12" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" - integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== + version "1.1.14" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.14.tgz#d9de602370d91347cd9ddad1224d4fd701eb348b" + integrity sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g== dependencies: balanced-match "^1.0.0" concat-map "0.0.1" -brace-expansion@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7" - integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== +brace-expansion@^2.0.1, brace-expansion@^2.0.2: + version "2.1.0" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.1.0.tgz#4f41a41190216ee36067ec381526fe9539c4f0ae" + integrity sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w== dependencies: balanced-match "^1.0.0" +brace-expansion@^5.0.5: + version "5.0.6" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.6.tgz#ec68fe0a641a29d8711579caf641d05bae1f2285" + integrity sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g== + dependencies: + balanced-match "^4.0.2" + braces@^3.0.2, braces@^3.0.3, braces@~3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" @@ -6364,16 +6391,16 @@ browserify-zlib@^0.1.4: dependencies: pako "~0.2.0" -browserslist@^4.0.0, browserslist@^4.18.1, browserslist@^4.21.4, browserslist@^4.24.0, browserslist@^4.26.3, browserslist@^4.27.0, browserslist@^4.28.0: - version "4.28.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.1.tgz#7f534594628c53c63101079e27e40de490456a95" - integrity sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA== +browserslist@^4.0.0, browserslist@^4.18.1, browserslist@^4.21.4, browserslist@^4.24.0, browserslist@^4.28.1, browserslist@^4.28.2: + version "4.28.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.2.tgz#f50b65362ef48974ca9f50b3680566d786b811d2" + integrity sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg== dependencies: - baseline-browser-mapping "^2.9.0" - caniuse-lite "^1.0.30001759" - electron-to-chromium "^1.5.263" - node-releases "^2.0.27" - update-browserslist-db "^1.2.0" + baseline-browser-mapping "^2.10.12" + caniuse-lite "^1.0.30001782" + electron-to-chromium "^1.5.328" + node-releases "^2.0.36" + update-browserslist-db "^1.2.3" bser@2.1.1: version "2.1.1" @@ -6400,14 +6427,6 @@ buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" -buffer@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" - integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.2.1" - builtin-modules@^3.1.0, builtin-modules@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" @@ -6465,7 +6484,7 @@ caching-transform@^4.0.0: package-hash "^4.0.0" write-file-atomic "^3.0.0" -call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: +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== @@ -6473,14 +6492,14 @@ call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply- es-errors "^1.3.0" function-bind "^1.1.2" -call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.7, call-bind@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" - integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== +call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.7, call-bind@^1.0.8, call-bind@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.9.tgz#39a644700c80bc7d0ca9102fc6d1d43b2fd7eee7" + integrity sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ== dependencies: - call-bind-apply-helpers "^1.0.0" - es-define-property "^1.0.0" - get-intrinsic "^1.2.4" + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + get-intrinsic "^1.3.0" set-function-length "^1.2.2" call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4: @@ -6539,10 +6558,10 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001754, caniuse-lite@^1.0.30001759: - version "1.0.30001760" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001760.tgz#bdd1960fafedf8d5f04ff16e81460506ff9b798f" - integrity sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001782, caniuse-lite@^1.0.30001787: + version "1.0.30001793" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz#238887ddf5fcfc8c36d872394d0a78a517312a72" + integrity sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA== capital-case@^1.0.4: version "1.0.4" @@ -6724,9 +6743,9 @@ ci-info@^3.2.0, ci-info@^3.6.1: integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== ci-info@^4.2.0: - version "4.3.1" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.3.1.tgz#355ad571920810b5623e11d40232f443f16f1daa" - integrity sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA== + version "4.4.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.4.0.tgz#7d54eff9f54b45b62401c26032696eb59c8bd18c" + integrity sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg== citty@^0.1.6: version "0.1.6" @@ -6736,11 +6755,9 @@ citty@^0.1.6: consola "^3.2.3" city-timezones@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/city-timezones/-/city-timezones-1.3.2.tgz#76ead53649ad7fff7bf8512bc2c217054d966f36" - integrity sha512-XztdL/2EWpfmgRIOzrKVOWFp6VdmaD9FNTZPINlez1etIn0mMNn01RMmSfOp6LUP/h1M2ZLX80N1O+WKwhzC+w== - dependencies: - lodash "^4.17.21" + version "1.3.4" + resolved "https://registry.yarnpkg.com/city-timezones/-/city-timezones-1.3.4.tgz#a8c9aa1c7630ed99a2acee33640a205e1c230433" + integrity sha512-yLmtCxU4y5HLAw9XGIMcGEXO+R2KH1sb595wEQC/E9BxSDpazCD7v9ZQRySO7367IXWsWqt3z2xFVh3OkZdgEQ== cjs-module-lexer@^1.0.0, cjs-module-lexer@^1.2.3: version "1.4.3" @@ -6896,9 +6913,9 @@ codemirror-spell-checker@1.1.2: typo-js "*" codemirror@^5.65.15: - version "5.65.20" - resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.65.20.tgz#8aa77c2dbfeb2df9b0828bf8e7d64fdd566eeff0" - integrity sha512-i5dLDDxwkFCbhjvL2pNjShsojoL3XHyDwsGv1jqETUoW+lzpBKKqNTUWgQwVAOa0tUm4BwekT455ujafi8payA== + version "5.65.21" + resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.65.21.tgz#cacf320606c5450ad3b3da34bb9c666afec21068" + integrity sha512-6teYk0bA0nR3QP0ihGMoxuKzpl5W80FpnHpBJpgy66NK3cZv5b/d/HY8PnRvfSsCG1MTfr92u2WUl+wT0E40mQ== codemirror@^6.0.0: version "6.0.2" @@ -7118,9 +7135,9 @@ content-type@~1.0.4, content-type@~1.0.5: integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== contentful-resolve-response@^1.3.12: - version "1.9.4" - resolved "https://registry.yarnpkg.com/contentful-resolve-response/-/contentful-resolve-response-1.9.4.tgz#a0206be0218af38db5280894b7fc748cbfeb68d9" - integrity sha512-8qu2dM2IMoVQhBhp81BVGB9ISantPmPTZ3EELGxuM8e/t+jSe+xmsVk5yHE+a9AYMBKSwgupBnkjGZE4RzFowQ== + version "1.9.8" + resolved "https://registry.yarnpkg.com/contentful-resolve-response/-/contentful-resolve-response-1.9.8.tgz#9daafd4d2c250558a24fa96dd3a66db903758712" + integrity sha512-gudcrg8Z2pt+Gf76miW0ebmamLhoIZkLAadLJPO5EHeSljwnbYDawffAeqiWnd8UR5Zi+cAzcWMEBtUT9GpBpg== dependencies: fast-copy "^3.0.2" @@ -7179,17 +7196,17 @@ cookie@~0.7.1: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== -core-js-compat@^3.43.0: - version "3.47.0" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.47.0.tgz#698224bbdbb6f2e3f39decdda4147b161e3772a3" - integrity sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ== +core-js-compat@^3.43.0, core-js-compat@^3.48.0: + version "3.49.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.49.0.tgz#06145447d92f4aaf258a0c44f24b47afaeaffef6" + integrity sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA== dependencies: - browserslist "^4.28.0" + browserslist "^4.28.1" core-js-pure@^3.23.3: - version "3.47.0" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.47.0.tgz#1104df8a3b6eb9189fcc559b5a65b90f66e7e887" - integrity sha512-BcxeDbzUrRnXGYIVAGFtcGQVNpFcUhVjr6W7F8XktvQW2iJP9e66GP6xdKotCRFlrxBvNIBrhwKteRXqMV86Nw== + version "3.49.0" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.49.0.tgz#ff8436b7251a3832f5fdbbe3e10f7f2e58e51fb1" + integrity sha512-XM4RFka59xATyJv/cS3O3Kml72hQXUeGRuuTmMYFxwzc9/7C8OYTaIR/Ji+Yt8DXzsFLNhat15cE/JP15HrCgw== core-js@^1.0.0: version "1.2.7" @@ -7197,9 +7214,9 @@ core-js@^1.0.0: integrity sha512-ZiPp9pZlgxpWRu0M+YWbm6+aQ84XEfH1JRXvfOc/fILWI0VKhLC2LX13X1NYq4fULzLMq7Hfh43CSo2/aIaUPA== core-js@^3.19.2, core-js@^3.6.4: - version "3.47.0" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.47.0.tgz#436ef07650e191afeb84c24481b298bd60eb4a17" - integrity sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg== + version "3.49.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.49.0.tgz#8b4d520ac034311fa21aa616f017ada0e0dbbddd" + integrity sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg== core-util-is@~1.0.0: version "1.0.3" @@ -7676,6 +7693,11 @@ date-format@4.0.3: resolved "https://registry.yarnpkg.com/date-format/-/date-format-4.0.3.tgz#f63de5dc08dc02efd8ef32bf2a6918e486f35873" integrity sha512-7P3FyqDcfeznLZp2b+OMitV9Sz2lUnsT87WaTat9nVwqsBkTzPG3lPLNwW3en6F4pHUiWzr6vb8CLhjdK9bcxQ== +dayjs@^1.11.7: + version "1.11.20" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.20.tgz#88d919fd639dc991415da5f4cb6f1b6650811938" + integrity sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ== + debounce@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" @@ -7688,7 +7710,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.6.0, debug@^2.6.9: dependencies: ms "2.0.0" -debug@4, debug@4.4.3, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.4.0, debug@^4.4.1: +debug@4, debug@4.4.3, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.4.0, debug@^4.4.3: version "4.4.3" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== @@ -7720,9 +7742,9 @@ decimal.js@^10.2.1: integrity sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg== decode-named-character-reference@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz#25c32ae6dd5e21889549d40f676030e9514cc0ed" - integrity sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q== + version "1.3.0" + resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz#3e40603760874c2e5867691b599d73a7da25b53f" + integrity sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q== dependencies: character-entities "^2.0.0" @@ -7749,9 +7771,9 @@ dedent@^0.7.0: integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== dedent@^1.0.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.7.0.tgz#c1f9445335f0175a96587be245a282ff451446ca" - integrity sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ== + version "1.7.2" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.7.2.tgz#34e2264ab538301e27cf7b07bf2369c19baa8dd9" + integrity sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA== deep-diff@^0.3.5: version "0.3.8" @@ -7903,10 +7925,10 @@ detect-indent@^6.1.0: resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6" integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA== -detect-libc@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== +detect-libc@^2.0.3: + version "2.1.2" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.1.2.tgz#689c5dcdc1900ef5583a4cb9f6d7b473742074ad" + integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ== detect-newline@^3.0.0: version "3.1.0" @@ -7969,14 +7991,14 @@ diff-sequences@^29.6.3: integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== diff@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + version "4.0.4" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.4.tgz#7a6dbfda325f25f07517e9b518f897c08332e07d" + integrity sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ== diff@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531" - integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A== + version "5.2.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.2.tgz#0a4742797281d09cfa699b79ea32d27723623bad" + integrity sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A== dir-glob@^3.0.1: version "3.0.1" @@ -8094,9 +8116,9 @@ domhandler@^5.0.2, domhandler@^5.0.3: domelementtype "^2.3.0" dompurify@^2.5.8: - version "2.5.8" - resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.5.8.tgz#2809d89d7e528dc7a071dea440d7376df676f824" - integrity sha512-o1vSNgrmYMQObbSSvF/1brBYEQPHhV1+gsmrusO7/GXtp1T9rCS8cXFqVxK/9crT1jA6Ccv+5MTSjBNqr7Sovw== + version "2.5.9" + resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.5.9.tgz#80481ec22dfac67285cc274cb51a45c1e9fa9c42" + integrity sha512-i6mvVmWN4xo9LrhCOZrDgSs9noW6nOahbrmzjRbPF36YPyj5Ue5lgok0MHDWkG7xzpWFO2OYttXdzM7rJxHvNA== domutils@^1.7.0: version "1.7.0" @@ -8115,7 +8137,7 @@ domutils@^2.5.2, domutils@^2.8.0: domelementtype "^2.2.0" domhandler "^4.2.0" -domutils@^3.0.1: +domutils@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.2.2.tgz#edbfe2b668b0c1d97c24baf0f1062b132221bc78" integrity sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw== @@ -8283,10 +8305,10 @@ ejs@^3.1.6, ejs@^3.1.8: dependencies: jake "^10.8.5" -electron-to-chromium@^1.5.263: - version "1.5.267" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz#5d84f2df8cdb6bfe7e873706bb21bd4bfb574dc7" - integrity sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw== +electron-to-chromium@^1.5.328: + version "1.5.360" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.360.tgz#7faf4231324c7f8d49c5c0938e8712f623d8b8d7" + integrity sha512-GkcBt6YYAw9SxFWn+xVar4cLVGlXVuswwtRLBozi2zp0GjXs4ZnOrqV4zbXzg35n7w81hCkyJNYicgXlVHAmBA== emittery@^0.10.2: version "0.10.2" @@ -8318,11 +8340,6 @@ emojis-list@^3.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== - encodeurl@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" @@ -8344,20 +8361,20 @@ endent@^2.0.1: fast-json-parse "^1.0.3" objectorarray "^1.0.5" -enhanced-resolve@^5.17.3, enhanced-resolve@^5.7.0: - version "5.18.3" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz#9b5f4c5c076b8787c78fe540392ce76a88855b44" - integrity sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww== +enhanced-resolve@^5.21.4, enhanced-resolve@^5.7.0: + version "5.21.6" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz#aa207b43cf658e6ab3ba06896edc00c13c3127c6" + integrity sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ== dependencies: graceful-fs "^4.2.4" - tapable "^2.2.0" + tapable "^2.3.3" entities@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== -entities@^4.2.0, entities@^4.4.0: +entities@^4.2.0: version "4.5.0" resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== @@ -8367,6 +8384,11 @@ entities@^6.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-6.0.1.tgz#c28c34a43379ca7f61d074130b2f5f7020a30694" integrity sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g== +entities@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-7.0.1.tgz#26e8a88889db63417dcb9a1e79a3f1bc92b5976b" + integrity sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA== + entities@~3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/entities/-/entities-3.0.1.tgz#2b887ca62585e96db3903482d336c1006c3001d4" @@ -8391,10 +8413,10 @@ error-stack-parser@^2.0.6: dependencies: stackframe "^1.3.4" -es-abstract@^1.17.2, es-abstract@^1.17.5, es-abstract@^1.23.2, es-abstract@^1.23.3, es-abstract@^1.23.5, es-abstract@^1.23.6, es-abstract@^1.23.9, es-abstract@^1.24.0: - version "1.24.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.24.0.tgz#c44732d2beb0acc1ed60df840869e3106e7af328" - integrity sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg== +es-abstract@^1.17.2, es-abstract@^1.17.5, es-abstract@^1.23.2, es-abstract@^1.23.3, es-abstract@^1.23.5, es-abstract@^1.23.6, es-abstract@^1.23.9, es-abstract@^1.24.0, es-abstract@^1.24.2: + version "1.24.2" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.24.2.tgz#2dbd38c180735ee983f77585140a2706a963ed9a" + integrity sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg== dependencies: array-buffer-byte-length "^1.0.2" arraybuffer.prototype.slice "^1.0.4" @@ -8482,32 +8504,37 @@ es-get-iterator@^1.1.3: stop-iteration-iterator "^1.0.0" es-iterator-helpers@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz#d1dd0f58129054c0ad922e6a9a1e65eef435fe75" - integrity sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w== + version "1.3.2" + resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.3.2.tgz#8f4ff1f3603cbd09fbdb72c747a679779a65cc7f" + integrity sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw== dependencies: - call-bind "^1.0.8" - call-bound "^1.0.3" + call-bind "^1.0.9" + call-bound "^1.0.4" define-properties "^1.2.1" - es-abstract "^1.23.6" + es-abstract "^1.24.2" es-errors "^1.3.0" - es-set-tostringtag "^2.0.3" + es-set-tostringtag "^2.1.0" function-bind "^1.1.2" - get-intrinsic "^1.2.6" + get-intrinsic "^1.3.0" globalthis "^1.0.4" gopd "^1.2.0" has-property-descriptors "^1.0.2" has-proto "^1.2.0" has-symbols "^1.1.0" internal-slot "^1.1.0" - iterator.prototype "^1.1.4" - safe-array-concat "^1.1.3" + iterator.prototype "^1.1.5" + math-intrinsics "^1.1.0" -es-module-lexer@^1.2.1, es-module-lexer@^1.4.1: +es-module-lexer@^1.4.1: version "1.7.0" resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz#9159601561880a85f2734560a9099b2c31e5372a" integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA== +es-module-lexer@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-2.1.0.tgz#1dfcbb5ea3bbfb63f28e1fc3676c3676d1c9624c" + integrity sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ== + 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" @@ -8515,7 +8542,7 @@ es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: dependencies: es-errors "^1.3.0" -es-set-tostringtag@^2.0.3, es-set-tostringtag@^2.1.0: +es-set-tostringtag@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== @@ -8835,13 +8862,13 @@ eslint-config-react-important-stuff@^3.0.0: eslint-plugin-react-hooks "^4.0.8" eslint-import-resolver-node@^0.3.9: - version "0.3.9" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac" - integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== + version "0.3.10" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz#84ce3005abfc300588cf23bbac1aabec1fc6e8c1" + integrity sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ== dependencies: debug "^3.2.7" - is-core-module "^2.13.0" - resolve "^1.22.4" + is-core-module "^2.16.1" + resolve "^2.0.0-next.6" eslint-import-resolver-typescript@^3.10.1: version "3.10.1" @@ -9111,9 +9138,9 @@ esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0: integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== esquery@^1.4.0, esquery@^1.4.2: - version "1.6.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" - integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + version "1.7.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.7.0.tgz#08d048f261f0ddedb5bae95f46809463d9c9496d" + integrity sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g== dependencies: estraverse "^5.1.0" @@ -9170,11 +9197,6 @@ event-stream@=3.3.4: stream-combiner "~0.0.4" through "~2.3.1" -event-target-shim@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" - integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== - eventemitter3@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" @@ -9186,11 +9208,11 @@ eventemitter3@^4.0.0, eventemitter3@^4.0.4: integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== eventemitter3@^5.0.0, eventemitter3@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" - integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== + version "5.0.4" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.4.tgz#a86d66170433712dde814707ac52b5271ceb1feb" + integrity sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw== -events@^3.2.0, events@^3.3.0: +events@^3.2.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== @@ -9267,16 +9289,16 @@ expect@^29.0.0, expect@^29.7.0: jest-util "^29.7.0" expect@^30.0.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-30.2.0.tgz#d4013bed267013c14bc1199cec8aa57cee9b5869" - integrity sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw== + version "30.4.1" + resolved "https://registry.yarnpkg.com/expect/-/expect-30.4.1.tgz#897e0390a0b6c333dbcf3a24dee3ad49553577e0" + integrity sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA== dependencies: - "@jest/expect-utils" "30.2.0" + "@jest/expect-utils" "30.4.1" "@jest/get-type" "30.1.0" - jest-matcher-utils "30.2.0" - jest-message-util "30.2.0" - jest-mock "30.2.0" - jest-util "30.2.0" + jest-matcher-utils "30.4.1" + jest-message-util "30.4.1" + jest-mock "30.4.1" + jest-util "30.4.1" express-fileupload@^1.5.2: version "1.5.2" @@ -9293,13 +9315,13 @@ express-interceptor@^1.2.0: debug "^2.2.0" express@^4.17.3, express@^4.22.1: - version "4.22.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.22.1.tgz#1de23a09745a4fffdb39247b344bb5eaff382069" - integrity sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g== + version "4.22.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.22.2.tgz#c17ae0981e5efc24b22272f0e041c4662503b700" + integrity sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q== dependencies: accepts "~1.3.8" array-flatten "1.1.1" - body-parser "~1.20.3" + body-parser "~1.20.5" content-disposition "~0.5.4" content-type "~1.0.4" cookie "~0.7.1" @@ -9318,7 +9340,7 @@ express@^4.17.3, express@^4.22.1: parseurl "~1.3.3" path-to-regexp "~0.1.12" proxy-addr "~2.0.7" - qs "~6.14.0" + qs "~6.15.1" range-parser "~1.2.1" safe-buffer "5.2.1" send "~0.19.0" @@ -9393,16 +9415,28 @@ fast-levenshtein@^2.0.6: integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fast-uri@^3.0.1: - version "3.1.0" - resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.0.tgz#66eecff6c764c0df9b762e62ca7edcfb53b4edfa" - integrity sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA== + version "3.1.2" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.2.tgz#8af3d4fc9d3e71b11572cc2673b514a7d1a8c8ec" + integrity sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ== + +fast-xml-builder@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz#abd2363145a7625d9789ad96da375fabe3cff28c" + integrity sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q== + dependencies: + path-expression-matcher "^1.5.0" + xml-naming "^0.1.0" -fast-xml-parser@^4.4.1: - version "4.5.6" - resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.5.6.tgz#4ff57d4aca13a2d11aa42ad460495cf00f32b655" - integrity sha512-Yd4vkROfJf8AuJrDIVMVmYfULKmIJszVsMv7Vo71aocsKgFxpdlpSHXSaInvyYfgw2PRuObQSW2GFpVMUjxu9A== +fast-xml-parser@^5.3.6: + version "5.8.0" + resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-5.8.0.tgz#64d71f0f8d4bf23621dffd762aef7e98c1884fc1" + integrity sha512-6bIM7fsJxeo3uXv7OncQYsBAMPJ7V16Slahl/6M98C/i2q+vB1+4a0MtrvYwDFEUrwDSbAmeLDRXsOBwrL7yAg== dependencies: - strnum "^1.0.5" + "@nodable/entities" "^2.1.0" + fast-xml-builder "^1.2.0" + path-expression-matcher "^1.5.0" + strnum "^2.3.0" + xml-naming "^0.1.0" fastest-levenshtein@^1.0.12: version "1.0.16" @@ -9415,9 +9449,9 @@ fastparse@^1.1.2: integrity sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ== fastq@^1.6.0: - version "1.19.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5" - integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== + version "1.20.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.20.1.tgz#ca750a10dc925bc8b18839fd203e3ef4b3ced675" + integrity sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw== dependencies: reusify "^1.0.4" @@ -9492,9 +9526,9 @@ fetch-retry@^5.0.2: integrity sha512-3yurQZ2hD9VISAhJJP9bpYFNQrHHBXE2JxxjY5aLEcDi46RmAzJE2OC9FAde0yis5ElW0jTTzs0zfg/Cca4XqQ== fflate@^0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea" - integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A== + version "0.8.3" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.3.tgz#bc27d8eb30343d4d512abb03480202ce65d825fc" + integrity sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA== file-entry-cache@^6.0.1: version "6.0.1" @@ -9526,19 +9560,20 @@ file-system-cache@2.3.0: fs-extra "11.1.1" ramda "0.29.0" -file-type@^16.5.4: - version "16.5.4" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-16.5.4.tgz#474fb4f704bee427681f98dd390058a172a6c2fd" - integrity sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw== +file-type@^22.0.0: + version "22.0.1" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-22.0.1.tgz#0184dbd6b6353bb25eb21a0c413a23885b6d1818" + integrity sha512-ww5Mhre0EE+jmBvOXTmXAbEMuZE7uX4a3+oRCQFNj8w++g3ev913N6tXQz0XTXbueQ5TWQfm6BdaViEHHn8bhA== dependencies: - readable-web-to-node-stream "^3.0.0" - strtok3 "^6.2.4" - token-types "^4.1.1" + "@tokenizer/inflate" "^0.4.1" + strtok3 "^10.3.5" + token-types "^6.1.2" + uint8array-extras "^1.5.0" filelist@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" - integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== + version "1.0.6" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.6.tgz#1e8870942a7c636c862f7c49b9394937b6a995a3" + integrity sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA== dependencies: minimatch "^5.0.1" @@ -9548,9 +9583,9 @@ filesize@^8.0.6: integrity sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ== filestack-js@^3.44.2: - version "3.44.2" - resolved "https://registry.yarnpkg.com/filestack-js/-/filestack-js-3.44.2.tgz#e3c01c8c411a85e842ae2cac21b439cd986fe990" - integrity sha512-/52q66fzisVvl+0/sZ9VqYPsDdHXVJhUwIPlH88Zhv3NGnRaynnoW5PffHNXObtV9wnkAgZGxbsZVZRtKVLzKQ== + version "3.51.5" + resolved "https://registry.yarnpkg.com/filestack-js/-/filestack-js-3.51.5.tgz#be67ed065dae55cf9e28162cecb1c7c325ce7e6a" + integrity sha512-OR8soNdQDwrp3pTgYpoMHNgz51fup18n15VJlutCGmnLj32WD+vexLIH17LTWIAtvNbL3BkEbtTkyqHjPlRqUA== dependencies: "@babel/runtime" "^7.8.4" "@filestack/loader" "^1.0.9" @@ -9558,8 +9593,8 @@ filestack-js@^3.44.2: abab "^2.0.6" debug "^4.3.4" eventemitter3 "^5.0.0" - fast-xml-parser "^4.4.1" - file-type "^16.5.4" + fast-xml-parser "^5.3.6" + file-type "^22.0.0" follow-redirects "^1.15.2" isutf8 "^4.0.0" jsonschema "^1.4.1" @@ -9683,9 +9718,9 @@ flatted@^3.2.9: integrity sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA== flow-parser@0.*: - version "0.293.0" - resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.293.0.tgz#1f8d96753e298b86915ae678ec83b874261b36ba" - integrity sha512-8tEGAcWpCqioajiSqrJr2+JSmkEI2vO/UACFGG378RO106ez9xugVxe9EpXD3aI1Vbf+mEUGhMt0gMpveJwVGA== + version "0.314.0" + resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.314.0.tgz#bf11bc9db676dd64a4049f55624b58080f850169" + integrity sha512-ayvpVFL/wibphkhjaz6PwL/F+Vz9lZB7qwFIHvsFiPQMfKmrqRXp1UyJgxMqyanW6QQDvsB12MLWFCc2cYBOtw== flux-standard-action@^2.0.3: version "2.1.2" @@ -9695,7 +9730,7 @@ flux-standard-action@^2.0.3: lodash.isplainobject "^4.0.6" lodash.isstring "^4.0.1" -follow-redirects@^1.0.0, follow-redirects@^1.15.11, follow-redirects@^1.15.2: +follow-redirects@^1.0.0, follow-redirects@^1.15.2, follow-redirects@^1.16.0: version "1.16.0" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.16.0.tgz#28474a159d3b9d11ef62050a14ed60e4df6d61bc" integrity sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw== @@ -9797,7 +9832,7 @@ fraction.js@^5.3.4: resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-5.3.4.tgz#8c0fcc6a9908262df4ed197427bdeef563e0699a" integrity sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ== -fresh@0.5.2, fresh@~0.5.2: +fresh@~0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== @@ -9836,9 +9871,9 @@ fs-extra@^10.0.0: universalify "^2.0.0" fs-extra@^11.1.0, fs-extra@^11.1.1: - version "11.3.2" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.2.tgz#c838aeddc6f4a8c74dd15f85e11fe5511bfe02a4" - integrity sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A== + version "11.3.5" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.5.tgz#07a44eff40bea53e719909a532f91a23bf0769ff" + integrity sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg== dependencies: graceful-fs "^4.2.0" jsonfile "^6.0.1" @@ -9991,9 +10026,9 @@ get-symbol-description@^1.1.0: get-intrinsic "^1.2.6" get-tsconfig@^4.10.0: - version "4.13.0" - resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.13.0.tgz#fcdd991e6d22ab9a600f00e91c318707a5d9a0d7" - integrity sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ== + version "4.14.0" + resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.14.0.tgz#985d85c52a9903864280ccc2448d413fbf1efed8" + integrity sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA== dependencies: resolve-pkg-maps "^1.0.0" @@ -10046,14 +10081,14 @@ glob@^10.0.0: package-json-from-dist "^1.0.0" path-scurry "^1.11.1" -glob@^13.0.0: - version "13.0.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-13.0.0.tgz#9d9233a4a274fc28ef7adce5508b7ef6237a1be3" - integrity sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA== +glob@^13.0.3: + version "13.0.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-13.0.6.tgz#078666566a425147ccacfbd2e332deb66a2be71d" + integrity sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw== dependencies: - minimatch "^10.1.1" - minipass "^7.1.2" - path-scurry "^2.0.0" + minimatch "^10.2.2" + minipass "^7.1.3" + path-scurry "^2.0.2" glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0: version "7.2.3" @@ -10259,10 +10294,10 @@ hasha@^5.0.0: dependencies: traverse ">=0.2.4" -hasown@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== +hasown@^2.0.2, hasown@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.3.tgz#5e5c2b15b60370a4c7930c383dfb76bf17bc403c" + integrity sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg== dependencies: function-bind "^1.1.2" @@ -10460,9 +10495,9 @@ html-void-elements@^3.0.0: integrity sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg== html-webpack-plugin@^5.5.0, html-webpack-plugin@^5.5.4: - version "5.6.5" - resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.6.5.tgz#d57defb83cabbf29bf56b2d4bf10b67b650066be" - integrity sha512-4xynFbKNNk+WlzXeQQ+6YYsH2g7mpfPszQZUi3ovKlj+pDmngQ7vRXjrrmGROabmKwyQkcgcX5hqfOwHbFmK5g== + version "5.6.7" + resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.6.7.tgz#429bab4e12abf3c07e1c608886608e2df2c06b11" + integrity sha512-md+vXtdCAe60s1k6AU3dUyMJnDxUyQAwfwPKoLisvgUF1IXjtlLsk2se54+qfL9Mdm26bbwvjJybpNx48NKRLw== dependencies: "@types/html-minifier-terser" "^6.0.0" html-minifier-terser "^6.0.2" @@ -10478,6 +10513,16 @@ html2canvas@^1.4.1: css-line-break "^2.1.0" text-segmentation "^1.0.3" +htmlparser2@^10.1, htmlparser2@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-10.1.0.tgz#fe3f2e12c73b6e462d4e10395db9c1119e4d6ae4" + integrity sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.2.2" + entities "^7.0.1" + htmlparser2@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" @@ -10488,16 +10533,6 @@ htmlparser2@^6.1.0: domutils "^2.5.2" entities "^2.0.0" -htmlparser2@^8.0.0: - version "8.0.2" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21" - integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA== - dependencies: - domelementtype "^2.3.0" - domhandler "^5.0.3" - domutils "^3.0.1" - entities "^4.4.0" - http-cache-semantics@^4.0.0: version "4.2.0" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz#205f4db64f8562b76a4ff9235aa5279839a09dd5" @@ -10508,27 +10543,17 @@ http-deceiver@^1.2.7: resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== -http-errors@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== +http-errors@~1.8.0: + version "1.8.1" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c" + integrity sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g== dependencies: - depd "2.0.0" + depd "~1.1.2" inherits "2.0.4" setprototypeof "1.2.0" - statuses "2.0.1" + statuses ">= 1.5.0 < 2" toidentifier "1.0.1" -http-errors@~1.6.2: - version "1.6.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" - integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" - http-errors@~2.0.0, http-errors@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.1.tgz#36d2f65bc909c8790018dd36fb4d93da6caae06b" @@ -10590,7 +10615,7 @@ https-proxy-agent@^4.0.0: agent-base "5" debug "4" -https-proxy-agent@^5.0.0: +https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== @@ -10681,10 +10706,10 @@ immer@^9.0.7: resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.21.tgz#1e025ea31a40f24fb064f1fef23e931496330176" integrity sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA== -immutable@*, immutable@^5.0.2: - version "5.1.4" - resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.1.4.tgz#e3f8c1fe7b567d56cf26698f31918c241dae8c1f" - integrity sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA== +immutable@*, immutable@^5.1.5: + version "5.1.5" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.1.5.tgz#93ee4db5c2a9ab42a4a783069f3c5d8847d40165" + integrity sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A== immutable@~3.7.4: version "3.7.6" @@ -10692,9 +10717,9 @@ immutable@~3.7.4: integrity sha512-AizQPcaofEtO11RZhPPHBOJRdo/20MKQF9mBLnVkBoyHi1/zXK8fzVdnEpSV9gxqtnh6Qomfp3F0xT5qP/vThw== immutable@~3.8.2: - version "3.8.2" - resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3" - integrity sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg== + version "3.8.3" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.3.tgz#0a8d2494a94d4b2d4f0e99986e74dd25d1e9a859" + integrity sha512-AUY/VyX0E5XlibOmWt10uabJzam1zlYjwiEgQSDc5+UIkFNaF9WM0JxXKaNMGf+F/ffUF+7kRKXM9A7C0xXqMg== import-fresh@^3.1.0, import-fresh@^3.2.1: version "3.3.1" @@ -10735,11 +10760,6 @@ inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, i resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== - ini@^1.3.5, ini@~1.3.0: version "1.3.8" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" @@ -10777,9 +10797,9 @@ ipaddr.js@1.9.1: integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== ipaddr.js@^2.0.1: - version "2.3.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.3.0.tgz#71dce70e1398122208996d1c22f2ba46a24b1abc" - integrity sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg== + version "2.4.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.4.0.tgz#038e9ceaf8219efc5bb76347b7eb787875d5095b" + integrity sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ== is-absolute-url@^3.0.0: version "3.0.3" @@ -10865,12 +10885,12 @@ is-callable@^1.2.7: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== -is-core-module@^2.13.0, is-core-module@^2.16.1: - version "2.16.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" - integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== +is-core-module@^2.16.1, is-core-module@^2.16.2: + version "2.16.2" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.2.tgz#3e07450a8080ebce3fbf0cac494f4d2ab324e082" + integrity sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA== dependencies: - hasown "^2.0.2" + hasown "^2.0.3" is-data-view@^1.0.1, is-data-view@^1.0.2: version "1.0.2" @@ -11265,7 +11285,7 @@ isutf8@^4.0.0: resolved "https://registry.yarnpkg.com/isutf8/-/isutf8-4.0.1.tgz#046799bca926e9438c650def6ba94a8d9871da90" integrity sha512-1pk2/2pE+G48eETnp4uOLxQ9WUCxD7oVauYwhFEAGREJPDxEO7iX9qstylrCcx3lNWa1RCS2DxGTxrHdWqS7/w== -iterator.prototype@^1.1.4: +iterator.prototype@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.5.tgz#12c959a29de32de0aa3bbbb801f4d777066dae39" integrity sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g== @@ -11457,15 +11477,15 @@ jest-config@^29.7.0: slash "^3.0.0" strip-json-comments "^3.1.1" -jest-diff@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-30.2.0.tgz#e3ec3a6ea5c5747f605c9e874f83d756cba36825" - integrity sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A== +jest-diff@30.4.1: + version "30.4.1" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-30.4.1.tgz#26691c73975768409af4a66b2754cea3182aa2dc" + integrity sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA== dependencies: - "@jest/diff-sequences" "30.0.1" + "@jest/diff-sequences" "30.4.0" "@jest/get-type" "30.1.0" chalk "^4.1.2" - pretty-format "30.2.0" + pretty-format "30.4.1" jest-diff@^27.5.1: version "27.5.1" @@ -11648,15 +11668,15 @@ jest-leak-detector@^29.7.0: jest-get-type "^29.6.3" pretty-format "^29.7.0" -jest-matcher-utils@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz#69a0d4c271066559ec8b0d8174829adc3f23a783" - integrity sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg== +jest-matcher-utils@30.4.1: + version "30.4.1" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-30.4.1.tgz#3fee8c89dbd8fc6e60eb590def9897e18f110ec4" + integrity sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A== dependencies: "@jest/get-type" "30.1.0" chalk "^4.1.2" - jest-diff "30.2.0" - pretty-format "30.2.0" + jest-diff "30.4.1" + pretty-format "30.4.1" jest-matcher-utils@^27.5.1: version "27.5.1" @@ -11678,18 +11698,19 @@ jest-matcher-utils@^29.7.0: jest-get-type "^29.6.3" pretty-format "^29.7.0" -jest-message-util@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-30.2.0.tgz#fc97bf90d11f118b31e6131e2b67fc4f39f92152" - integrity sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw== +jest-message-util@30.4.1: + version "30.4.1" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-30.4.1.tgz#40f6bfa5f564363edcba7ce0ca64277fd2ad6af7" + integrity sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ== dependencies: "@babel/code-frame" "^7.27.1" - "@jest/types" "30.2.0" + "@jest/types" "30.4.1" "@types/stack-utils" "^2.0.3" chalk "^4.1.2" graceful-fs "^4.2.11" - micromatch "^4.0.8" - pretty-format "30.2.0" + jest-util "30.4.1" + picomatch "^4.0.3" + pretty-format "30.4.1" slash "^3.0.0" stack-utils "^2.0.6" @@ -11738,14 +11759,14 @@ jest-message-util@^29.7.0: slash "^3.0.0" stack-utils "^2.0.3" -jest-mock@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-30.2.0.tgz#69f991614eeb4060189459d3584f710845bff45e" - integrity sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw== +jest-mock@30.4.1: + version "30.4.1" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-30.4.1.tgz#5e11a05d7719a1e3c7bba6348b70ff4e1bc5ea68" + integrity sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw== dependencies: - "@jest/types" "30.2.0" + "@jest/types" "30.4.1" "@types/node" "*" - jest-util "30.2.0" + jest-util "30.4.1" jest-mock@^27.0.6, jest-mock@^27.5.1: version "27.5.1" @@ -11769,10 +11790,10 @@ jest-pnp-resolver@^1.2.2: resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== -jest-regex-util@30.0.1: - version "30.0.1" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-30.0.1.tgz#f17c1de3958b67dfe485354f5a10093298f2a49b" - integrity sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA== +jest-regex-util@30.4.0: + version "30.4.0" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-30.4.0.tgz#f75ccc43857633df2563a03588b5cb45c7c2941b" + integrity sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg== jest-regex-util@^27.5.1: version "27.5.1" @@ -12009,17 +12030,17 @@ jest-snapshot@^29.7.0: pretty-format "^29.7.0" semver "^7.5.3" -jest-util@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-30.2.0.tgz#5142adbcad6f4e53c2776c067a4db3c14f913705" - integrity sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA== +jest-util@30.4.1: + version "30.4.1" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-30.4.1.tgz#979c9d014fdd12bb95d3dcde0192e1a9e0bc93d6" + integrity sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw== dependencies: - "@jest/types" "30.2.0" + "@jest/types" "30.4.1" "@types/node" "*" chalk "^4.1.2" ci-info "^4.2.0" graceful-fs "^4.2.11" - picomatch "^4.0.2" + picomatch "^4.0.3" jest-util@^27.5.1: version "27.5.1" @@ -12196,10 +12217,10 @@ jiti@^1.21.7: resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.7.tgz#9dd81043424a3d28458b193d965f0d18a2300ba9" integrity sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A== -joi@^18.0.1: - version "18.0.2" - resolved "https://registry.yarnpkg.com/joi/-/joi-18.0.2.tgz#30ced6aed00a7848cc11f92859515258301dc3a4" - integrity sha512-RuCOQMIt78LWnktPoeBL0GErkNaJPTBGcYuyaBvUOQSpcpcLfWrHPPihYdOGbV5pam9VTWbeoF7TsGiHugcjGA== +joi@^18.0.2: + version "18.2.1" + resolved "https://registry.yarnpkg.com/joi/-/joi-18.2.1.tgz#a022e39496e25b010a6d4649975c160a4315bd79" + integrity sha512-2/OKlogiESf2Nh3TFCrRjrr9z1DRHeW0I+KReF67+4J0Ns+8hBtHRmoWAZ2OFU6I5+TWLEe6sVlSdXPjHm5UbQ== dependencies: "@hapi/address" "^5.1.1" "@hapi/formula" "^3.0.2" @@ -12207,7 +12228,7 @@ joi@^18.0.1: "@hapi/pinpoint" "^2.0.1" "@hapi/tlds" "^1.1.1" "@hapi/topo" "^6.0.2" - "@standard-schema/spec" "^1.0.0" + "@standard-schema/spec" "^1.1.0" joycon@^3.1.1: version "3.1.1" @@ -12313,7 +12334,7 @@ json-buffer@3.0.1: resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== -json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: +json-parse-even-better-errors@^2.3.0: version "2.3.1" resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== @@ -12328,11 +12349,6 @@ json-schema-traverse@^1.0.0: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== -json-schema@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" - integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== - json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" @@ -12356,9 +12372,9 @@ json5@^2.1.2, json5@^2.2.0, json5@^2.2.2, json5@^2.2.3: integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== jsonfile@^6.0.1: - version "6.2.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.2.0.tgz#7c265bd1b65de6977478300087c99f1c84383f62" - integrity sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg== + version "6.2.1" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.2.1.tgz#b6e31717f22cc37330b081ce0051ed5de53af2f6" + integrity sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q== dependencies: universalify "^2.0.0" optionalDependencies: @@ -12373,7 +12389,7 @@ jsonpath@^1.1.1: static-eval "2.1.1" underscore "1.13.6" -jsonpointer@^5.0.0: +jsonpointer@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.1.tgz#2110e0af0900fd37467b5907ecd13a7884a1b559" integrity sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ== @@ -12448,13 +12464,20 @@ language-tags@^1.0.9: language-subtag-registry "^0.3.20" launch-editor@^2.6.0: - version "2.12.0" - resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.12.0.tgz#cc740f4e0263a6b62ead2485f9896e545321f817" - integrity sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg== + version "2.13.2" + resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.13.2.tgz#41d51baaf8afb393224b89bd2bcb4e02f2306405" + integrity sha512-4VVDnbOpLXy/s8rdRCSXb+zfMeFR0WlJWpET1iA9CQdlZDfwyLjUuGQzXU4VeOoey6AicSAluWan7Etga6Kcmg== dependencies: picocolors "^1.1.1" shell-quote "^1.8.3" +launder@^1.7.1: + version "1.7.1" + resolved "https://registry.yarnpkg.com/launder/-/launder-1.7.1.tgz#ef7155ab0c3ddec2323089c961d2e9a249aa5b0d" + integrity sha512-mU6WRz5EusL9ZZuiZ5SO4Y6C0P9PAUR9iwdb6bzj4KDihm28DiHFw+/yk9DBH4f+Pv1wuzQ4e2jV3oQ7mkIqvw== + dependencies: + dayjs "^1.11.7" + lazy-ass@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513" @@ -12537,10 +12560,10 @@ load-tsconfig@^0.2.0: resolved "https://registry.yarnpkg.com/load-tsconfig/-/load-tsconfig-0.2.5.tgz#453b8cd8961bfb912dea77eb6c168fe8cca3d3a1" integrity sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg== -loader-runner@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.1.tgz#6c76ed29b0ccce9af379208299f07f876de737e3" - integrity sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q== +loader-runner@^4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.2.tgz#9913d3a15971f8f635915e601fb5c9d495d918e9" + integrity sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w== loader-utils@^1.1.0, loader-utils@^1.2.3: version "1.4.2" @@ -12710,7 +12733,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== -lodash@^4.0.1, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.5, lodash@^4.18.1, lodash@^4.2.1, lodash@^4.7.0: +lodash@^4.0.1, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.23, lodash@^4.17.5, lodash@^4.18.1, lodash@^4.2.1, lodash@^4.7.0: version "4.18.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.18.1.tgz#ff2b66c1f6326d59513de2407bf881439812771c" integrity sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q== @@ -12764,9 +12787,9 @@ lru-cache@^10.2.0: integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== lru-cache@^11.0.0: - version "11.2.4" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.2.4.tgz#ecb523ebb0e6f4d837c807ad1abaea8e0619770d" - integrity sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg== + version "11.5.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.5.0.tgz#14117229fd25bc9c67936e32de90ca047488c97a" + integrity sha512-5YgH9UJd7wVb9hIouI2adWpgqrrICkt070Dnj8EUY1+B4B2P9eRLPAkAAo6NICA7CEhOIeBHl46u9zSNpNu7zA== lru-cache@^5.1.1: version "5.1.1" @@ -12926,9 +12949,9 @@ mdast-util-from-markdown@^1.0.0: uvu "^0.5.0" mdast-util-from-markdown@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz#4850390ca7cf17413a9b9a0fbefcd1bc0eb4160a" - integrity sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA== + version "2.0.3" + resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz#c95822b91aab75f18a4cbe8b2f51b873ed2cf0c7" + integrity sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q== dependencies: "@types/mdast" "^4.0.0" "@types/unist" "^3.0.0" @@ -13645,7 +13668,7 @@ mime-db@1.52.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -"mime-db@>= 1.43.0 < 2": +"mime-db@>= 1.43.0 < 2", mime-db@^1.54.0: version "1.54.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== @@ -13662,7 +13685,7 @@ mime-types@2.1.18: dependencies: mime-db "~1.33.0" -mime-types@^2.1.12, mime-types@^2.1.25, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@^2.1.35, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: +mime-types@^2.1.12, mime-types@^2.1.25, mime-types@^2.1.31, mime-types@^2.1.35, mime-types@~2.1.24, mime-types@~2.1.34, mime-types@~2.1.35: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -13712,9 +13735,9 @@ min-indent@^1.0.0: integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== mini-css-extract-plugin@^2.4.5: - version "2.9.4" - resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.4.tgz#cafa1a42f8c71357f49cd1566810d74ff1cb0200" - integrity sha512-ZWYT7ln73Hptxqxk2DxPU9MmapXRhxkJD6tkSR04dnQxm8BGu2hzgKLugK5yySD97u/8yy7Ma7E76k9ZdvtjkQ== + version "2.10.2" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.10.2.tgz#5c85ec9450c05d26e32531b465a15a08c3a57253" + integrity sha512-AOSS0IdEB95ayVkxn5oGzNQwqAi2J0Jb/kKm43t7H73s8+f5873g0yuj0PNvK4dO75mu5DHg4nlgp4k6Kga8eg== dependencies: schema-utils "^4.0.0" tapable "^2.2.1" @@ -13724,33 +13747,33 @@ minimalistic-assert@^1.0.0: resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimatch@3.1.2, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== +minimatch@3.1.5, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.5.tgz#580c88f8d5445f2bd6aa8f3cadefa0de79fbd69e" + integrity sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w== dependencies: brace-expansion "^1.1.7" -minimatch@^10.1.1: - version "10.1.1" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.1.1.tgz#e6e61b9b0c1dcab116b5a7d1458e8b6ae9e73a55" - integrity sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ== +minimatch@^10.2.2: + version "10.2.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.5.tgz#bd48687a0be38ed2961399105600f832095861d1" + integrity sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg== dependencies: - "@isaacs/brace-expansion" "^5.0.0" + brace-expansion "^5.0.5" minimatch@^5.0.1: - version "5.1.6" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" - integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + version "5.1.9" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.9.tgz#1293ef15db0098b394540e8f9f744f9fda8dee4b" + integrity sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw== dependencies: brace-expansion "^2.0.1" minimatch@^9.0.4: - version "9.0.5" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" - integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + version "9.0.9" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.9.tgz#9b0cb9fcb78087f6fd7eababe2511c4d3d60574e" + integrity sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg== dependencies: - brace-expansion "^2.0.1" + brace-expansion "^2.0.2" minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6, minimist@^1.2.8: version "1.2.8" @@ -13769,10 +13792,10 @@ minipass@^5.0.0: resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== -"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" - integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2, minipass@^7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.3.tgz#79389b4eb1bb2d003a9bba87d492f2bd37bdc65b" + integrity sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A== minizlib@^2.1.1: version "2.1.2" @@ -13800,14 +13823,14 @@ mkdirp@^1.0.3: integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== mlly@^1.7.4: - version "1.8.0" - resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.8.0.tgz#e074612b938af8eba1eaf43299cbc89cb72d824e" - integrity sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g== + version "1.8.2" + resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.8.2.tgz#e7f7919a82d13b174405613117249a3f449d78bb" + integrity sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA== dependencies: - acorn "^8.15.0" + acorn "^8.16.0" pathe "^2.0.3" pkg-types "^1.3.1" - ufo "^1.6.1" + ufo "^1.6.3" moment-duration-format@^2.3.2: version "2.3.2" @@ -13815,9 +13838,9 @@ moment-duration-format@^2.3.2: integrity sha512-cBMXjSW+fjOb4tyaVHuaVE/A5TqkukDWiOfxxAjY+PEqmmBQlLwn+8OzwPiG3brouXKY5Un4pBjAeB6UToXHaQ== moment-timezone@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.6.0.tgz#c5a6519171f31a64739ea75d33f5c136c08ff608" - integrity sha512-ldA5lRNm3iJCWZcBCab4pnNL3HSZYXVb/3TYr75/1WCTWYuTqYUb5f/S384pncYjJ88lbO8Z4uPDvmoluHJc8Q== + version "0.6.2" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.6.2.tgz#a704cca03ee502f0df6edd42efcdcc61ee03190e" + integrity sha512-lDsQv8FoGdBUdf0+TjGsq2orxKuXdwFlQ6Zw6TX3xIcTwTfEpCLyKqvEauvCHJ8iu3KBV8+uPhlv70YsNGdUBQ== dependencies: moment "^2.29.4" @@ -13873,12 +13896,12 @@ mz@^2.7.0: object-assign "^4.0.1" thenify-all "^1.0.0" -nanoid@^3.3.11: - version "3.3.11" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" - integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== +nanoid@^3.3.12: + version "3.3.12" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.12.tgz#ab3d912e217a6d0a514f00a72a16543a28982c05" + integrity sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ== -napi-postinstall@^0.3.0: +napi-postinstall@^0.3.4: version "0.3.4" resolved "https://registry.yarnpkg.com/napi-postinstall/-/napi-postinstall-0.3.4.tgz#7af256d6588b5f8e952b9190965d6b019653bbb9" integrity sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ== @@ -13943,6 +13966,16 @@ node-dir@^0.1.17: dependencies: minimatch "^3.0.2" +node-exports-info@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/node-exports-info/-/node-exports-info-1.6.0.tgz#1aedafb01a966059c9a5e791a94a94d93f5c2a13" + integrity sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw== + dependencies: + array.prototype.flatmap "^1.3.3" + es-errors "^1.3.0" + object.entries "^1.1.9" + semver "^6.3.1" + node-fetch-native@^1.6.6: version "1.6.7" resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-1.6.7.tgz#9d09ca63066cc48423211ed4caf5d70075d76a71" @@ -13972,10 +14005,10 @@ node-preload@^0.2.1: dependencies: process-on-spawn "^1.0.0" -node-releases@^2.0.27: - version "2.0.27" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.27.tgz#eedca519205cf20f650f61d56b070db111231e4e" - integrity sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA== +node-releases@^2.0.36: + version "2.0.45" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.45.tgz#d84e1efff3ed4ed0fdee86a840c0a8589f0ec527" + integrity sha512-iIbHXV9eBB2nB0wa7oTsrrXq+qQt+9SIlx9AX3T96YgobtEQfis5n6TJ6vV+3QP8DwdriEAcGhARaFCu37peBg== normalize-package-data@^2.5.0: version "2.5.0" @@ -13992,11 +14025,6 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -normalize-range@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" - integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== - normalize-url@^6.0.1: version "6.1.0" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" @@ -14175,7 +14203,7 @@ obuf@^1.0.0, obuf@^1.1.2: resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== -on-finished@2.4.1, on-finished@~2.4.1: +on-finished@~2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== @@ -14426,7 +14454,7 @@ parse5@^7.0.0: dependencies: entities "^6.0.0" -parseurl@~1.3.2, parseurl@~1.3.3: +parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== @@ -14467,6 +14495,11 @@ path-exists@^5.0.0: resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-5.0.0.tgz#a6aad9489200b21fab31e49cf09277e5116fb9e7" integrity sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ== +path-expression-matcher@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz#3b98545dc88ffebb593e2d8458d0929da9275f4a" + integrity sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ== + path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" @@ -14505,10 +14538,10 @@ path-scurry@^1.11.1: lru-cache "^10.2.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" -path-scurry@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.1.tgz#4b6572376cfd8b811fca9cd1f5c24b3cbac0fe10" - integrity sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA== +path-scurry@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.2.tgz#6be0d0ee02a10d9e0de7a98bae65e182c9061f85" + integrity sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg== dependencies: lru-cache "^11.0.0" minipass "^7.1.2" @@ -14519,9 +14552,9 @@ path-to-regexp@3.3.0: integrity sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw== path-to-regexp@~0.1.12: - version "0.1.12" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7" - integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ== + version "0.1.13" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.13.tgz#9b22ec16bc3ab88d05a0c7e369869421401ab17d" + integrity sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA== path-type@^4.0.0: version "4.0.0" @@ -14540,11 +14573,6 @@ pause-stream@0.0.11: dependencies: through "~2.3" -peek-readable@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-4.1.0.tgz#4ece1111bf5c2ad8867c314c81356847e8a62e72" - integrity sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg== - peek-stream@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/peek-stream/-/peek-stream-1.1.3.tgz#3b35d84b7ccbbd262fff31dc10da56856ead6d67" @@ -14575,19 +14603,19 @@ picocolors@^1.0.0, picocolors@^1.1.1: integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3, picomatch@^2.3.0, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + version "2.3.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.2.tgz#5a942915e26b372dc0f0e6753149a16e6b1c5601" + integrity sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA== picomatch@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-3.0.1.tgz#817033161def55ec9638567a2f3bbc876b3e7516" - integrity sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag== + version "3.0.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-3.0.2.tgz#8f543c114ddc037eab6b10fbd5a66cc8a51a02bc" + integrity sha512-cfDHL6LStTEKlNilboNtobT/kEa30PtAf2Q1OgszfrG/rpVl1xaFWT9ktfkS306GmHgmnad1Sw4wabhlvFtsTw== -picomatch@^4.0.2, picomatch@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" - integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== +picomatch@^4.0.2, picomatch@^4.0.3, picomatch@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.4.tgz#fd6f5e00a143086e074dffe4c924b8fb293b0589" + integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A== pidtree@0.6.0: version "0.6.0" @@ -15374,11 +15402,11 @@ postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.2, postcss@^7.0.32, postcss@^7.0.3 source-map "^0.6.1" postcss@^8.2.14, postcss@^8.2.7, postcss@^8.3.11, postcss@^8.3.5, postcss@^8.4.33, postcss@^8.4.4, postcss@^8.4.47: - version "8.5.6" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c" - integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== + version "8.5.15" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.15.tgz#d1eaf677a324e9ec02196da2d3fecf4a0b9a735c" + integrity sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A== dependencies: - nanoid "^3.3.11" + nanoid "^3.3.12" picocolors "^1.1.1" source-map-js "^1.2.1" @@ -15405,14 +15433,15 @@ pretty-error@^4.0.0: lodash "^4.17.20" renderkid "^3.0.0" -pretty-format@30.2.0, pretty-format@^30.0.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-30.2.0.tgz#2d44fe6134529aed18506f6d11509d8a62775ebe" - integrity sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA== +pretty-format@30.4.1, pretty-format@^30.0.0: + version "30.4.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-30.4.1.tgz#0911652e92e1e91f475e3e6a16e628e50649ea69" + integrity sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw== dependencies: - "@jest/schemas" "30.0.5" + "@jest/schemas" "30.4.1" ansi-styles "^5.2.0" - react-is "^18.3.1" + react-is-18 "npm:react-is@^18.3.1" + react-is-19 "npm:react-is@^19.2.5" pretty-format@^27.0.2, pretty-format@^27.5.1: version "27.5.1" @@ -15569,9 +15598,9 @@ pump@^2.0.0: once "^1.3.1" pump@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.3.tgz#151d979f1a29668dc0025ec589a455b53282268d" - integrity sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA== + version "3.0.4" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.4.tgz#1f313430527fa8b905622ebd22fe1444e757ab3c" + integrity sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA== dependencies: end-of-stream "^1.1.0" once "^1.3.1" @@ -15626,10 +15655,10 @@ qrcode.react@^3.2.0: resolved "https://registry.yarnpkg.com/qrcode.react/-/qrcode.react-3.2.0.tgz#97daabd4ff641a3f3c678f87be106ebc55f9cd07" integrity sha512-YietHHltOHA4+l5na1srdaMx4sVSOjV9tamHs+mwiLWAMr6QVACRUw1Neax5CptFILcNoITctJY0Ipyn5enQ8g== -qs@^6.10.0, qs@^6.11.2, qs@^6.12.3, qs@^6.14.0, qs@~6.14.0: - version "6.14.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930" - integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w== +qs@^6.10.0, qs@^6.11.2, qs@^6.12.3, qs@^6.14.0, qs@~6.15.1: + version "6.15.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.15.2.tgz#fd55426d710403ddccc45e0f9eab16db7727ece9" + integrity sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw== dependencies: side-channel "^1.1.0" @@ -15743,9 +15772,9 @@ react-color@^2.19.3: tinycolor2 "^1.4.1" react-colorful@^5.1.2: - version "5.6.1" - resolved "https://registry.yarnpkg.com/react-colorful/-/react-colorful-5.6.1.tgz#7dc2aed2d7c72fac89694e834d179e32f3da563b" - integrity sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw== + version "5.7.0" + resolved "https://registry.yarnpkg.com/react-colorful/-/react-colorful-5.7.0.tgz#82c0f31311606161ed13423979067c865e4a7877" + integrity sha512-fuesYIemttah97XmsIHmz4OORDHiSFzyc9HMAIrCHJou2jaRQmL8cFJ76K4zQhhj8jzwOBlOi4BaGTjjOZCfTg== react-contenteditable@^3.3.7: version "3.3.7" @@ -15888,9 +15917,9 @@ react-helmet@^6.1.0: react-side-effect "^2.1.0" react-hook-form@^7.68.0: - version "7.68.0" - resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.68.0.tgz#733c6871fa4ec5e5bfb13e7650a3a912eafe1721" - integrity sha512-oNN3fjrZ/Xo40SWlHf1yCjlMK417JxoSJVUXQjGdvdRCU07NTFei1i1f8ApUAts+IVh14e4EdakeLEA+BEAs/Q== + version "7.76.0" + resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.76.0.tgz#b58036cabfac756500115a5a6bebc53a8314f05f" + integrity sha512-eKtLGgFeSgkHqQD8J59AMZ9a4uD1D83iSIzt4YlTGD7liDen5rrjcUO1rVIGd9yC1gofryjtHbv+4ny4hkLWlw== react-hot-loader@^4.13.1: version "4.13.1" @@ -15906,6 +15935,16 @@ react-hot-loader@^4.13.1: shallowequal "^1.1.0" source-map "^0.7.3" +"react-is-18@npm:react-is@^18.3.1": + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + +"react-is-19@npm:react-is@^19.2.5": + version "19.2.6" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-19.2.6.tgz#aeee6159b159eb7f520d672cffcc69e7052d288f" + integrity sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw== + react-is@18.1.0: version "18.1.0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.1.0.tgz#61aaed3096d30eacf2a2127118b5b41387d32a67" @@ -15921,7 +15960,7 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-is@^18.0.0, react-is@^18.3.1: +react-is@^18.0.0: version "18.3.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== @@ -15996,9 +16035,9 @@ react-redux@^8.1.3: use-sync-external-store "^1.0.0" react-redux@^9.2.0: - version "9.2.0" - resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-9.2.0.tgz#96c3ab23fb9a3af2cb4654be4b51c989e32366f5" - integrity sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g== + version "9.3.0" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-9.3.0.tgz#a30113bb6d95c0a715d54dda4308d450fca6ce09" + integrity sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g== dependencies: "@types/use-sync-external-store" "^0.0.6" use-sync-external-store "^1.4.0" @@ -16052,19 +16091,19 @@ react-responsive@^9.0.2: shallow-equal "^1.2.1" react-router-dom@^6.30.2: - version "6.30.2" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.30.2.tgz#ee8c161bce4890d34484b552f8510f9af0e22b01" - integrity sha512-l2OwHn3UUnEVUqc6/1VMmR1cvZryZ3j3NzapC2eUXO1dB0sYp5mvwdjiXhpUbRb21eFow3qSxpP8Yv6oAU824Q== + version "6.30.3" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.30.3.tgz#42ae6dc4c7158bfb0b935f162b9621b29dddf740" + integrity sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag== dependencies: - "@remix-run/router" "1.23.1" - react-router "6.30.2" + "@remix-run/router" "1.23.2" + react-router "6.30.3" -react-router@6.30.2: - version "6.30.2" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.30.2.tgz#c78a3b40f7011f49a373b1df89492e7d4ec12359" - integrity sha512-H2Bm38Zu1bm8KUE5NVWRMzuIyAV8p/JrOaBJAwVmp37AXG72+CZJlEBw6pdn9i5TBgLMhNDgijS4ZlblpHyWTA== +react-router@6.30.3: + version "6.30.3" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.30.3.tgz#994b3ccdbe0e81fe84d4f998100f62584dfbf1cf" + integrity sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw== dependencies: - "@remix-run/router" "1.23.1" + "@remix-run/router" "1.23.2" react-scripts@5.0.1: version "5.0.1" @@ -16187,9 +16226,9 @@ react-transition-group@^4.3.0: prop-types "^15.6.2" react@*: - version "19.2.1" - resolved "https://registry.yarnpkg.com/react/-/react-19.2.1.tgz#8600fa205e58e2e807f6ef431c9f6492591a2700" - integrity sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw== + version "19.2.6" + resolved "https://registry.yarnpkg.com/react/-/react-19.2.6.tgz#3dadb8e12b2a7934c1d5317973e5dce1301f9a4d" + integrity sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q== react@^18.3.1: version "18.3.1" @@ -16253,24 +16292,6 @@ readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-stream@^4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.7.0.tgz#cedbd8a1146c13dfff8dab14068028d58c15ac91" - integrity sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg== - dependencies: - abort-controller "^3.0.0" - buffer "^6.0.3" - events "^3.3.0" - process "^0.11.10" - string_decoder "^1.3.0" - -readable-web-to-node-stream@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz#392ba37707af5bf62d725c36c1b5d6ef4119eefc" - integrity sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw== - dependencies: - readable-stream "^4.7.0" - readdirp@^4.0.1: version "4.1.2" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.2.tgz#eb85801435fbf2a7ee58f19e0921b068fc69948d" @@ -16465,9 +16486,9 @@ regjsgen@^0.8.0: integrity sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q== regjsparser@^0.13.0: - version "0.13.0" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.13.0.tgz#01f8351335cf7898d43686bc74d2dd71c847ecc0" - integrity sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q== + version "0.13.1" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.13.1.tgz#0593cbacb27527927692030928ae4d3b878d6f8d" + integrity sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw== dependencies: jsesc "~3.1.0" @@ -16719,21 +16740,25 @@ resolve.exports@^2.0.0: resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.3.tgz#41955e6f1b4013b7586f873749a635dea07ebe3f" integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== -resolve@^1.1.7, resolve@^1.10.0, resolve@^1.13.1, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.1, resolve@^1.22.10, resolve@^1.22.4, resolve@^1.22.8, resolve@^1.9.0: - version "1.22.11" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.11.tgz#aad857ce1ffb8bfa9b0b1ac29f1156383f68c262" - integrity sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ== +resolve@^1.1.7, resolve@^1.10.0, resolve@^1.13.1, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.1, resolve@^1.22.11, resolve@^1.22.8, resolve@^1.9.0: + version "1.22.12" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.12.tgz#f5b2a680897c69c238a13cd16b15671f8b73549f" + integrity sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA== dependencies: + es-errors "^1.3.0" is-core-module "^2.16.1" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -resolve@^2.0.0-next.4, resolve@^2.0.0-next.5: - version "2.0.0-next.5" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.5.tgz#6b0ec3107e671e52b68cd068ef327173b90dc03c" - integrity sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA== +resolve@^2.0.0-next.4, resolve@^2.0.0-next.5, resolve@^2.0.0-next.6: + version "2.0.0-next.7" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.7.tgz#ba3b035d4b1ee7c522426eee73cabcb0fd5515dd" + integrity sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ== dependencies: - is-core-module "^2.13.0" + es-errors "^1.3.0" + is-core-module "^2.16.2" + node-exports-info "^1.6.0" + object-keys "^1.1.1" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" @@ -16803,11 +16828,11 @@ rimraf@^3.0.0, rimraf@^3.0.2: glob "^7.1.3" rimraf@^6.1.2: - version "6.1.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-6.1.2.tgz#9a0f3cea2ab853e81291127422116ecf2a86ae89" - integrity sha512-cFCkPslJv7BAXJsYlK1dZsbP8/ZNLkCAQ0bi1hf5EKX2QHegmDFEFA6QhuYJlk7UDdc+02JjO80YSOrWPpw06g== + version "6.1.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-6.1.3.tgz#afbee236b3bd2be331d4e7ce4493bac1718981af" + integrity sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA== dependencies: - glob "^13.0.0" + glob "^13.0.3" package-json-from-dist "^1.0.1" rimraf@~2.6.2: @@ -16856,13 +16881,13 @@ sade@^1.7.3: mri "^1.1.0" safe-array-concat@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz#c9e54ec4f603b0bbb8e7e5007a5ee7aecd1538c3" - integrity sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q== + version "1.1.4" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.4.tgz#a54cc9b61a57f33b42abad3cbdda3a2b38cc5719" + integrity sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg== dependencies: - call-bind "^1.0.8" - call-bound "^1.0.2" - get-intrinsic "^1.2.6" + call-bind "^1.0.9" + call-bound "^1.0.4" + get-intrinsic "^1.3.0" has-symbols "^1.1.0" isarray "^2.0.5" @@ -16906,14 +16931,15 @@ safe-regex@^2.1.1: integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== sanitize-html@^2.17.0: - version "2.17.0" - resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.17.0.tgz#a8f66420a6be981d8fe412e3397cc753782598e4" - integrity sha512-dLAADUSS8rBwhaevT12yCezvioCA+bmUTPH/u57xKPT8d++voeYE6HeluA/bPbQ15TwDBG2ii+QZIEmYx8VdxA== + version "2.17.4" + resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.17.4.tgz#7db8b73b0024ccf543978c2a44da8223c4740cb1" + integrity sha512-2HW7v2ol/uAM7sX4hbD8Z59OGWmAPrvjL8E71UWlBcj6m+kcF6ilQBLny+cIgY214QJeJT5tQuxKKqX0SQqjGQ== dependencies: deepmerge "^4.2.2" escape-string-regexp "^4.0.0" - htmlparser2 "^8.0.0" + htmlparser2 "^10.1.0" is-plain-object "^5.0.0" + launder "^1.7.1" parse-srcset "^1.0.2" postcss "^8.3.11" @@ -16938,16 +16964,21 @@ sass-loader@^13.3.3: neo-async "^2.6.2" sass@^1.95.0: - version "1.96.0" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.96.0.tgz#dca45b6b08ae829500f448124afc7c15150bbb34" - integrity sha512-8u4xqqUeugGNCYwr9ARNtQKTOj4KmYiJAVKXf2CTIivTCR51j96htbMKWDru8H5SaQWpyVgTfOF8Ylyf5pun1Q== + version "1.99.0" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.99.0.tgz#ff9d1594da4886249dfaafabbeea2dea2dc74b26" + integrity sha512-kgW13M54DUB7IsIRM5LvJkNlpH+WhMpooUcaWGFARkF1Tc82v9mIWkCbCYf+MBvpIUBSeSOTilpZjEPr2VYE6Q== dependencies: chokidar "^4.0.0" - immutable "^5.0.2" + immutable "^5.1.5" source-map-js ">=0.6.2 <2.0.0" optionalDependencies: "@parcel/watcher" "^2.4.1" +sax@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.6.0.tgz#da59637629307b97e7c4cb28e080a7bc38560d5b" + integrity sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA== + sax@~1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" @@ -17028,33 +17059,14 @@ semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4, semver@^7.7.1: - version "7.7.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946" - integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q== + version "7.8.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.0.tgz#ed0661039fcbcda2ce71f01fa6adbefaa77040df" + integrity sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA== -send@0.19.0: - version "0.19.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" - integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" - mime "1.6.0" - ms "2.1.3" - on-finished "2.4.1" - range-parser "~1.2.1" - statuses "2.0.1" - -send@~0.19.0: - version "0.19.1" - resolved "https://registry.yarnpkg.com/send/-/send-0.19.1.tgz#1c2563b2ee4fe510b806b21ec46f355005a369f9" - integrity sha512-p4rRk4f23ynFEfcD9LA0xRYngj+IyGiEYyqqOak8kaN0TvNmuxC2dcVeBn62GpCeR2CpWqyHCNScTP91QbAVFg== +send@~0.19.0, send@~0.19.1: + version "0.19.2" + resolved "https://registry.yarnpkg.com/send/-/send-0.19.2.tgz#59bc0da1b4ea7ad42736fd642b1c4294e114ff29" + integrity sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg== dependencies: debug "2.6.9" depd "2.0.0" @@ -17062,13 +17074,13 @@ send@~0.19.0: encodeurl "~2.0.0" escape-html "~1.0.3" etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" + fresh "~0.5.2" + http-errors "~2.0.1" mime "1.6.0" ms "2.1.3" - on-finished "2.4.1" + on-finished "~2.4.1" range-parser "~1.2.1" - statuses "2.0.1" + statuses "~2.0.2" sentence-case@^3.0.4: version "3.0.4" @@ -17094,56 +17106,56 @@ serialize-javascript@^4.0.0: dependencies: randombytes "^2.1.0" -serialize-javascript@^6.0.0, serialize-javascript@^6.0.2: +serialize-javascript@^6.0.0: version "6.0.2" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== dependencies: randombytes "^2.1.0" -serve-handler@6.1.6: - version "6.1.6" - resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.6.tgz#50803c1d3e947cd4a341d617f8209b22bd76cfa1" - integrity sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ== +serve-handler@6.1.7: + version "6.1.7" + resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.7.tgz#e9bb864e87ee71e8dab874cde44d146b77e3fb78" + integrity sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg== dependencies: bytes "3.0.0" content-disposition "0.5.2" mime-types "2.1.18" - minimatch "3.1.2" + minimatch "3.1.5" path-is-inside "1.0.2" path-to-regexp "3.3.0" range-parser "1.2.0" serve-index@^1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" - integrity sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw== + version "1.9.2" + resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.2.tgz#2988e3612106d78a5e4849ddff552ce7bd3d9bcb" + integrity sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ== dependencies: - accepts "~1.3.4" + accepts "~1.3.8" batch "0.6.1" debug "2.6.9" escape-html "~1.0.3" - http-errors "~1.6.2" - mime-types "~2.1.17" - parseurl "~1.3.2" + http-errors "~1.8.0" + mime-types "~2.1.35" + parseurl "~1.3.3" serve-static@~1.16.2: - version "1.16.2" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296" - integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw== + version "1.16.3" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.3.tgz#a97b74d955778583f3862a4f0b841eb4d5d78cf9" + integrity sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA== dependencies: encodeurl "~2.0.0" escape-html "~1.0.3" parseurl "~1.3.3" - send "0.19.0" + send "~0.19.1" serve@^14.2.5: - version "14.2.5" - resolved "https://registry.yarnpkg.com/serve/-/serve-14.2.5.tgz#569e333b99a484b3a6d25acce4a569c8c4f96373" - integrity sha512-Qn/qMkzCcMFVPb60E/hQy+iRLpiU8PamOfOSYoAHmmF+fFFmpPpqa6Oci2iWYpTdOUM3VF+TINud7CfbQnsZbA== + version "14.2.6" + resolved "https://registry.yarnpkg.com/serve/-/serve-14.2.6.tgz#b5e520dfda9b1ed3b824a8e8d4fd6f69e4c6944c" + integrity sha512-QEjUSA+sD4Rotm1znR8s50YqA3kYpRGPmtd5GlFxbaL9n/FdUNbqMhxClqdditSk0LlZyA/dhud6XNRTOC9x2Q== dependencies: "@zeit/schemas" "2.36.0" - ajv "8.12.0" + ajv "8.18.0" arg "5.0.2" boxen "7.0.0" chalk "5.0.1" @@ -17151,7 +17163,7 @@ serve@^14.2.5: clipboardy "3.0.0" compression "1.8.1" is-port-reachable "4.0.0" - serve-handler "6.1.6" + serve-handler "6.1.7" update-check "1.5.4" set-blocking@^2.0.0: @@ -17195,11 +17207,6 @@ setimmediate@^1.0.5: resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== -setprototypeof@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" - integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== - setprototypeof@1.2.0, setprototypeof@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" @@ -17252,12 +17259,12 @@ shell-quote@^1.7.3, shell-quote@^1.8.3: integrity sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw== 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== + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.1.tgz#c2e0b5a14a540aebee3bbc6c3f8666cc9b509127" + integrity sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w== dependencies: es-errors "^1.3.0" - object-inspect "^1.13.3" + object-inspect "^1.13.4" side-channel-map@^1.0.1: version "1.0.1" @@ -17482,9 +17489,9 @@ spdx-expression-parse@^3.0.0: spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.22" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz#abf5a08a6f5d7279559b669f47f0a43e8f3464ef" - integrity sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ== + version "3.0.23" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz#b069e687b1291a32f126893ed76a27a745ee2133" + integrity sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw== spdy-transport@^3.0.0: version "3.0.0" @@ -17549,9 +17556,9 @@ standalone-single-spa-webpack-plugin@^4.0.0: integrity sha512-Gp6feJ5nNeHDayevTMmwDDi51wQxVPLV56Cwn4QrN0nAkveeTbGnJu7tObpO0lq7wUxhEjgmdwdwE9woyiCxbQ== start-server-and-test@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/start-server-and-test/-/start-server-and-test-2.1.3.tgz#1f8f8f2666131c502a7f8be69cf35418dde3f719" - integrity sha512-k4EcbNjeg0odaDkAMlIeDVDByqX9PIgL4tivgP2tES6Zd8o+4pTq/HgbWCyA3VHIoZopB+wGnNPKYGGSByNriQ== + version "2.1.5" + resolved "https://registry.yarnpkg.com/start-server-and-test/-/start-server-and-test-2.1.5.tgz#15eaa6c152dedccff942ad8e442311bf459f1430" + integrity sha512-A/SbXpgXE25ScSkpLLqvGvVZT0ykN6+AzS8tVqMBCTxbJy2Nwuen59opT+afalK5aS+AuQmZs0EsLwjnuDN+/g== dependencies: arg "^5.0.2" bluebird "3.7.2" @@ -17560,7 +17567,7 @@ start-server-and-test@^2.1.3: execa "5.1.1" lazy-ass "1.6.0" ps-tree "1.2.0" - wait-on "9.0.3" + wait-on "9.0.4" static-eval@2.1.1: version "2.1.1" @@ -17569,12 +17576,7 @@ static-eval@2.1.1: dependencies: escodegen "^2.1.0" -statuses@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - -"statuses@>= 1.4.0 < 2": +"statuses@>= 1.5.0 < 2": version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== @@ -17738,7 +17740,7 @@ string.prototype.trimstart@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" -string_decoder@^1.1.1, string_decoder@^1.3.0: +string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== @@ -17791,11 +17793,11 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: ansi-regex "^5.0.1" strip-ansi@^7.0.1: - version "7.1.2" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.2.tgz#132875abde678c7ea8d691533f2e7e22bb744dba" - integrity sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA== + version "7.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.2.0.tgz#d22a269522836a627af8d04b5c3fd2c7fa3e32e3" + integrity sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w== dependencies: - ansi-regex "^6.0.1" + ansi-regex "^6.2.2" strip-bom@^3.0.0: version "3.0.0" @@ -17844,18 +17846,17 @@ strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== -strnum@^1.0.5: - version "1.1.2" - resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.1.2.tgz#57bca4fbaa6f271081715dbc9ed7cee5493e28e4" - integrity sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA== +strnum@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/strnum/-/strnum-2.3.0.tgz#81bfbfef53db8c3217ea62a98c026886ec4a2761" + integrity sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q== -strtok3@^6.2.4: - version "6.3.0" - resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-6.3.0.tgz#358b80ffe6d5d5620e19a073aa78ce947a90f9a0" - integrity sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw== +strtok3@^10.3.5: + version "10.3.5" + resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-10.3.5.tgz#7213285da0dc3dec0fc8ce5df4b8b7a733f14360" + integrity sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA== dependencies: "@tokenizer/token" "^0.3.0" - peek-readable "^4.1.0" style-loader@^3.2.1, style-loader@^3.3.1, style-loader@^3.3.4: version "3.3.4" @@ -18049,22 +18050,22 @@ svgo@^1.2.2: util.promisify "~1.0.0" svgo@^2.7.0, svgo@^2.8.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" - integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg== + version "2.8.2" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.2.tgz#8e99b7ba5ac9ed7e3a446063865f61e03223fe6b" + integrity sha512-TyzE4NVGLUFy+H/Uy4N6c3G0HEeprsVfge6Lmq+0FdQQ/zqoVYB62IsBZORsiL+o96s6ff/V6/3UQo/C0cgCAA== dependencies: - "@trysound/sax" "0.2.0" commander "^7.2.0" css-select "^4.1.3" css-tree "^1.1.3" csso "^4.2.0" picocolors "^1.0.0" + sax "^1.5.0" stable "^0.1.8" swc-loader@^0.2.3: - version "0.2.6" - resolved "https://registry.yarnpkg.com/swc-loader/-/swc-loader-0.2.6.tgz#bf0cba8eeff34bb19620ead81d1277fefaec6bc8" - integrity sha512-9Zi9UP2YmDpgmQVbyOPJClY0dwf58JDyDMQ7uRc4krmc72twNI2fvlBWHLqVekBpPc7h5NJkGVT1zNDxFrqhvg== + version "0.2.7" + resolved "https://registry.yarnpkg.com/swc-loader/-/swc-loader-0.2.7.tgz#2d1611ab314c5d8342d74aa5e5901b3fbf490de2" + integrity sha512-nwYWw3Fh9ame3Rtm7StS9SBLpHRRnYcK7bnpF3UKZmesAK0gw2/ADvlURFAINmPvKtDLzp+GBiP9yLoEjg6S9w== dependencies: "@swc/counter" "^0.1.3" @@ -18126,10 +18127,10 @@ tapable@^1.0.0: resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== -tapable@^2.0.0, tapable@^2.2.0, tapable@^2.2.1, tapable@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.0.tgz#7e3ea6d5ca31ba8e078b560f0d83ce9a14aa8be6" - integrity sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg== +tapable@^2.0.0, tapable@^2.2.1, tapable@^2.3.0, tapable@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.3.tgz#5da7c9992c46038221267985ab28421a8879f160" + integrity sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A== tar-fs@^2.1.1: version "2.1.4" @@ -18218,21 +18219,20 @@ terminal-link@^2.0.0: ansi-escapes "^4.2.1" supports-hyperlinks "^2.0.0" -terser-webpack-plugin@^5.2.5, terser-webpack-plugin@^5.3.1, terser-webpack-plugin@^5.3.11: - version "5.3.15" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.15.tgz#0a26860b765eaffa8e840170aabc5b3a3f6f6bb9" - integrity sha512-PGkOdpRFK+rb1TzVz+msVhw4YMRT9txLF4kRqvJhGhCM324xuR3REBSHALN+l+sAhKUmz0aotnjp5D+P83mLhQ== +terser-webpack-plugin@^5.2.5, terser-webpack-plugin@^5.3.1, terser-webpack-plugin@^5.5.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.6.0.tgz#8e7caad248183ab9e91ff08a83b0fc9f0439c3c3" + integrity sha512-Eum+5ajkaOhf5KbM26osvv21kLD7BaGqQ1UA4Ami4arYwylmGUQTgHFpHDdmJod1q4QXa66p0to/FBKID+J1vA== dependencies: "@jridgewell/trace-mapping" "^0.3.25" jest-worker "^27.4.5" schema-utils "^4.3.0" - serialize-javascript "^6.0.2" terser "^5.31.1" terser@^5.0.0, terser@^5.10.0, terser@^5.31.1: - version "5.44.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.44.1.tgz#e391e92175c299b8c284ad6ded609e37303b0a9c" - integrity sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw== + version "5.47.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.47.1.tgz#99b298e51bc41214304847de1429ec92fd1f7648" + integrity sha512-tPbLXTI6ohPASb/1YViL428oEHu6/qv1OxqYnfaonVCFHqx4+wCd95pHrQWsL5X4pl90CTyW9piSAsS2L0VoMw== dependencies: "@jridgewell/source-map" "^0.3.3" acorn "^8.15.0" @@ -18318,17 +18318,17 @@ tinyexec@^0.3.2: integrity sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA== tinyglobby@^0.2.11, tinyglobby@^0.2.13: - version "0.2.15" - resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2" - integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== + version "0.2.16" + resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.16.tgz#1c3b7eb953fce42b226bc5a1ee06428281aff3d6" + integrity sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg== dependencies: fdir "^6.5.0" - picomatch "^4.0.3" + picomatch "^4.0.4" tinymce@^7.9.1: - version "7.9.1" - resolved "https://registry.yarnpkg.com/tinymce/-/tinymce-7.9.1.tgz#1b18bad9cb7a3b4b12e3e5a7f29fc7daad0713d7" - integrity sha512-zaOHwmiP1EqTeLRXAvVriDb00JYnfEjWGPdKEuac7MiZJ5aiDMZ4Unc98Gmajn+PBljOmO1GKV6G0KwWn3+k8A== + version "7.9.3" + resolved "https://registry.yarnpkg.com/tinymce/-/tinymce-7.9.3.tgz#74f0a04bdf662f90910512d3fe792f0d5f1111e3" + integrity sha512-Mtm54U5YJ6Pyo/GaAx+JSHXTGEuxrg2AowVWCD9zy1eBolp5Ub7S1rTtsyQdxhPegfhLuR3VLiTKGw1tacv09g== tmpl@1.0.5: version "1.0.5" @@ -18362,20 +18362,21 @@ to-space-case@^1.0.0: to-no-case "^1.0.0" tocbot@^4.20.1: - version "4.36.4" - resolved "https://registry.yarnpkg.com/tocbot/-/tocbot-4.36.4.tgz#5f82976a2455e5abff44bdff84c89443dc23180f" - integrity sha512-ffznkKnZ1NdghwR1y8hN6W7kjn4FwcXq32Z1mn35gA7jd8dt2cTVAwL3d0BXXZGPu0Hd0evverUvcYAb/7vn0g== + version "4.36.8" + resolved "https://registry.yarnpkg.com/tocbot/-/tocbot-4.36.8.tgz#b6e2e64399f64b65365491f1d7c4506d0928a213" + integrity sha512-TpN+Z2YP4DTboeZ3GkUjzkjmG5NhJhXTVuH9EWQ/F4HgONVje9GZ8eNZE4kk88umV5o7hm3faS+eF/7qYwsP2w== toidentifier@1.0.1, toidentifier@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== -token-types@^4.1.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/token-types/-/token-types-4.2.1.tgz#0f897f03665846982806e138977dbe72d44df753" - integrity sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ== +token-types@^6.1.1, token-types@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/token-types/-/token-types-6.1.2.tgz#18d0fd59b996d421f9f83914d6101c201bd08129" + integrity sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww== dependencies: + "@borewit/text-codec" "^0.2.1" "@tokenizer/token" "^0.3.0" ieee754 "^1.2.1" @@ -18686,9 +18687,9 @@ typescript@^4.9.5: integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== typo-js@*: - version "1.3.1" - resolved "https://registry.yarnpkg.com/typo-js/-/typo-js-1.3.1.tgz#c80f8c7b292caaa17a507226bf74c1cddf6a29e6" - integrity sha512-elJkpCL6Z77Ghw0Lv0lGnhBAjSTOQ5FhiVOCfOuxhaoTT2xtLVbqikYItK5HHchzPbHEUFAcjOH669T2ZzeCbg== + version "1.3.2" + resolved "https://registry.yarnpkg.com/typo-js/-/typo-js-1.3.2.tgz#03a0e0e20b06fede619ffee16d5f4e3e032b8eb2" + integrity sha512-Z1YkJ7IIYNrFeOxAlHUercY4Q2I+PhYD/3VkWpJGy/Oqudy3bFpNcQxnv6Oa9fTSXCHPGz1eDoX1bZYm2Z891A== ua-parser-js@^0.7.18, ua-parser-js@^0.7.30: version "0.7.41" @@ -18700,16 +18701,21 @@ uc.micro@^1.0.1, uc.micro@^1.0.5: resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== -ufo@^1.5.4, ufo@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.6.1.tgz#ac2db1d54614d1b22c1d603e3aef44a85d8f146b" - integrity sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA== +ufo@^1.5.4, ufo@^1.6.3: + version "1.6.4" + resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.6.4.tgz#7a8fb875fcc6382d2c7d0b3692738b0500a92467" + integrity sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA== uglify-js@^3.1.4: version "3.19.3" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.19.3.tgz#82315e9bbc6f2b25888858acd1fff8441035b77f" integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ== +uint8array-extras@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/uint8array-extras/-/uint8array-extras-1.5.0.tgz#10d2a85213de3ada304fea1c454f635c73839e86" + integrity sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A== + unbox-primitive@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz#8d9d2c9edeea8460c7f35033a88867944934d1e2" @@ -18725,16 +18731,16 @@ underscore@1.13.6: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.6.tgz#04786a1f589dc6c09f761fc5f45b89e935136441" integrity sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A== +"undici-types@>=7.24.0 <7.24.7": + version "7.24.6" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.24.6.tgz#61275b485d7fd4e9d269c7cf04ec2873c9cc0f91" + integrity sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg== + undici-types@~5.26.4: version "5.26.5" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== -undici-types@~7.16.0: - version "7.16.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46" - integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw== - unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz#cb3173fe47ca743e228216e4a3ddc4c84d628cc2" @@ -18891,9 +18897,9 @@ unist-util-visit@^4.0.0: unist-util-visit-parents "^5.1.1" unist-util-visit@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-5.0.0.tgz#a7de1f31f72ffd3519ea71814cccf5fd6a9217d6" - integrity sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg== + version "5.1.0" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-5.1.0.tgz#9a2a28b0aa76a15e0da70a08a5863a2f060e2468" + integrity sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg== dependencies: "@types/unist" "^3.0.0" unist-util-is "^6.0.0" @@ -18901,7 +18907,7 @@ unist-util-visit@^5.0.0: "universal-navigation@https://github.com/topcoder-platform/universal-navigation#master": version "1.0.22" - resolved "https://github.com/topcoder-platform/universal-navigation#23b4bfc61d81496e517049b67b3ea74cb69251fe" + resolved "https://github.com/topcoder-platform/universal-navigation#4e70c755fe8065c50b7702a8a52e2df5b310f7f8" universalify@^0.2.0: version "0.2.0" @@ -18932,31 +18938,34 @@ unquote@~1.1.1: integrity sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg== unrs-resolver@^1.6.2: - version "1.11.1" - resolved "https://registry.yarnpkg.com/unrs-resolver/-/unrs-resolver-1.11.1.tgz#be9cd8686c99ef53ecb96df2a473c64d304048a9" - integrity sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg== + version "1.12.2" + resolved "https://registry.yarnpkg.com/unrs-resolver/-/unrs-resolver-1.12.2.tgz#a6c6888396abba5adaac4cab6587df866f1d7afd" + integrity sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ== dependencies: - napi-postinstall "^0.3.0" + napi-postinstall "^0.3.4" optionalDependencies: - "@unrs/resolver-binding-android-arm-eabi" "1.11.1" - "@unrs/resolver-binding-android-arm64" "1.11.1" - "@unrs/resolver-binding-darwin-arm64" "1.11.1" - "@unrs/resolver-binding-darwin-x64" "1.11.1" - "@unrs/resolver-binding-freebsd-x64" "1.11.1" - "@unrs/resolver-binding-linux-arm-gnueabihf" "1.11.1" - "@unrs/resolver-binding-linux-arm-musleabihf" "1.11.1" - "@unrs/resolver-binding-linux-arm64-gnu" "1.11.1" - "@unrs/resolver-binding-linux-arm64-musl" "1.11.1" - "@unrs/resolver-binding-linux-ppc64-gnu" "1.11.1" - "@unrs/resolver-binding-linux-riscv64-gnu" "1.11.1" - "@unrs/resolver-binding-linux-riscv64-musl" "1.11.1" - "@unrs/resolver-binding-linux-s390x-gnu" "1.11.1" - "@unrs/resolver-binding-linux-x64-gnu" "1.11.1" - "@unrs/resolver-binding-linux-x64-musl" "1.11.1" - "@unrs/resolver-binding-wasm32-wasi" "1.11.1" - "@unrs/resolver-binding-win32-arm64-msvc" "1.11.1" - "@unrs/resolver-binding-win32-ia32-msvc" "1.11.1" - "@unrs/resolver-binding-win32-x64-msvc" "1.11.1" + "@unrs/resolver-binding-android-arm-eabi" "1.12.2" + "@unrs/resolver-binding-android-arm64" "1.12.2" + "@unrs/resolver-binding-darwin-arm64" "1.12.2" + "@unrs/resolver-binding-darwin-x64" "1.12.2" + "@unrs/resolver-binding-freebsd-x64" "1.12.2" + "@unrs/resolver-binding-linux-arm-gnueabihf" "1.12.2" + "@unrs/resolver-binding-linux-arm-musleabihf" "1.12.2" + "@unrs/resolver-binding-linux-arm64-gnu" "1.12.2" + "@unrs/resolver-binding-linux-arm64-musl" "1.12.2" + "@unrs/resolver-binding-linux-loong64-gnu" "1.12.2" + "@unrs/resolver-binding-linux-loong64-musl" "1.12.2" + "@unrs/resolver-binding-linux-ppc64-gnu" "1.12.2" + "@unrs/resolver-binding-linux-riscv64-gnu" "1.12.2" + "@unrs/resolver-binding-linux-riscv64-musl" "1.12.2" + "@unrs/resolver-binding-linux-s390x-gnu" "1.12.2" + "@unrs/resolver-binding-linux-x64-gnu" "1.12.2" + "@unrs/resolver-binding-linux-x64-musl" "1.12.2" + "@unrs/resolver-binding-openharmony-arm64" "1.12.2" + "@unrs/resolver-binding-wasm32-wasi" "1.12.2" + "@unrs/resolver-binding-win32-arm64-msvc" "1.12.2" + "@unrs/resolver-binding-win32-ia32-msvc" "1.12.2" + "@unrs/resolver-binding-win32-x64-msvc" "1.12.2" untildify@^4.0.0: version "4.0.0" @@ -18968,10 +18977,10 @@ upath@^1.2.0: resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== -update-browserslist-db@^1.2.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz#cfb4358afa08b3d5731a2ecd95eebf4ddef8033e" - integrity sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA== +update-browserslist-db@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d" + integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== dependencies: escalade "^3.2.0" picocolors "^1.1.1" @@ -19102,9 +19111,9 @@ utrie@^1.0.2: base64-arraybuffer "^1.0.2" uuid@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.1.0.tgz#9549028be1753bb934fc96e2bca09bb4105ae912" - integrity sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A== + version "11.1.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.1.1.tgz#f6d81d2e1c65d00762e5e29b16c5d2d995e208ad" + integrity sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ== uuid@^8.3.2: version "8.3.2" @@ -19158,9 +19167,9 @@ validate-npm-package-license@^3.0.1: spdx-expression-parse "^3.0.0" validator@^13.0.0: - version "13.15.23" - resolved "https://registry.yarnpkg.com/validator/-/validator-13.15.23.tgz#59a874f84e4594588e3409ab1edbe64e96d0c62d" - integrity sha512-4yoz1kEWqUjzi5zsPbAS/903QXSYp0UOtHsPpp7p9rHAw/W+dkInskAE386Fat3oKRROwO98d9ZB0G4cObgUyw== + version "13.15.35" + resolved "https://registry.yarnpkg.com/validator/-/validator-13.15.35.tgz#81cf455c51f15b69d8d340be5914f3fab00dbf7f" + integrity sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw== vary@~1.1.2: version "1.1.2" @@ -19228,14 +19237,14 @@ w3c-xmlserializer@^2.0.0: dependencies: xml-name-validator "^3.0.0" -wait-on@9.0.3: - version "9.0.3" - resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-9.0.3.tgz#3ea858db0b854039e6aff5f323885aaef25e32bf" - integrity sha512-13zBnyYvFDW1rBvWiJ6Av3ymAaq8EDQuvxZnPIw3g04UqGi4TyoIJABmfJ6zrvKo9yeFQExNkOk7idQbDJcuKA== +wait-on@9.0.4: + version "9.0.4" + resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-9.0.4.tgz#ddf3a44ebd18f380621855d52973069ff2cb5b54" + integrity sha512-k8qrgfwrPVJXTeFY8tl6BxVHiclK11u72DVKhpybHfUL/K6KM4bdyK9EhIVYGytB5MJe/3lq4Tf0hrjM+pvJZQ== dependencies: - axios "^1.13.2" - joi "^18.0.1" - lodash "^4.17.21" + axios "^1.13.5" + joi "^18.0.2" + lodash "^4.17.23" minimist "^1.2.8" rxjs "^7.8.2" @@ -19253,10 +19262,10 @@ warning@^4.0.2: dependencies: loose-envify "^1.0.0" -watchpack@^2.2.0, watchpack@^2.4.4: - version "2.4.4" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.4.tgz#473bda72f0850453da6425081ea46fc0d7602947" - integrity sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA== +watchpack@^2.2.0, watchpack@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.5.1.tgz#dd38b601f669e0cbf567cb802e75cead82cde102" + integrity sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg== dependencies: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" @@ -19463,10 +19472,10 @@ webpack-sources@^2.2.0: source-list-map "^2.0.1" source-map "^0.6.1" -webpack-sources@^3.3.3: - version "3.3.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.3.3.tgz#d4bf7f9909675d7a070ff14d0ef2a4f3c982c723" - integrity sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg== +webpack-sources@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.4.1.tgz#009d110999ebd9fb3a6fa8d32eec6f84d940e65d" + integrity sha512-eACpxRN02yaawnt+uUNIF7Qje6A9zArxBbcAJjK1PK3S9Ycg5jIuJ8pW4q8EMnwNZCEGltcjkRx1QzOxOkKD8A== webpack-virtual-modules@^0.5.0: version "0.5.0" @@ -19479,35 +19488,33 @@ webpack-virtual-modules@^0.6.2: integrity sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ== webpack@5, webpack@^5.103.0, webpack@^5.64.4: - version "5.103.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.103.0.tgz#17a7c5a5020d5a3a37c118d002eade5ee2c6f3da" - integrity sha512-HU1JOuV1OavsZ+mfigY0j8d1TgQgbZ6M+J75zDkpEAwYeXjWSqrGJtgnPblJjd/mAyTNQ7ygw0MiKOn6etz8yw== + version "5.107.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.107.0.tgz#9e0d8d8baf24e76f058103f4f06ac6bb528b645a" + integrity sha512-PSxeHk/dmLYZlnTU+vL1Gej6Evg5RNtl3flhxBresfznFnzxinHMzHKloHnywM/3ouQv7/AlZCswWDIkNSggUA== dependencies: - "@types/eslint-scope" "^3.7.7" "@types/estree" "^1.0.8" "@types/json-schema" "^7.0.15" "@webassemblyjs/ast" "^1.14.1" "@webassemblyjs/wasm-edit" "^1.14.1" "@webassemblyjs/wasm-parser" "^1.14.1" - acorn "^8.15.0" + acorn "^8.16.0" acorn-import-phases "^1.0.3" - browserslist "^4.26.3" + browserslist "^4.28.1" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.17.3" - es-module-lexer "^1.2.1" + enhanced-resolve "^5.21.4" + es-module-lexer "^2.1.0" eslint-scope "5.1.1" events "^3.2.0" glob-to-regexp "^0.4.1" graceful-fs "^4.2.11" - json-parse-even-better-errors "^2.3.1" - loader-runner "^4.3.1" - mime-types "^2.1.27" + loader-runner "^4.3.2" + mime-db "^1.54.0" neo-async "^2.6.2" schema-utils "^4.3.3" tapable "^2.3.0" - terser-webpack-plugin "^5.3.11" - watchpack "^2.4.4" - webpack-sources "^3.3.3" + terser-webpack-plugin "^5.5.0" + watchpack "^2.5.1" + webpack-sources "^3.4.1" websocket-driver@>=0.5.1, websocket-driver@^0.7.4: version "0.7.4" @@ -19612,9 +19619,9 @@ which-module@^2.0.0: integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== which-typed-array@^1.1.13, which-typed-array@^1.1.16, which-typed-array@^1.1.18, which-typed-array@^1.1.19, which-typed-array@^1.1.2: - version "1.1.19" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.19.tgz#df03842e870b6b88e117524a4b364b6fc689f956" - integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw== + version "1.1.20" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.20.tgz#3fdb7adfafe0ea69157b1509f3a1cd892bd1d122" + integrity sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg== dependencies: available-typed-arrays "^1.0.7" call-bind "^1.0.8" @@ -19910,15 +19917,20 @@ ws@^7.3.1, ws@^7.4.6: integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== ws@^8.13.0, ws@^8.2.3: - version "8.18.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.3.tgz#b56b88abffde62791c639170400c93dcb0c95472" - integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg== + version "8.20.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.20.1.tgz#91a9ae2b312ccf98e0a85ec499b48cef45ab0ddb" + integrity sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w== xml-name-validator@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== +xml-naming@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/xml-naming/-/xml-naming-0.1.0.tgz#8ab7106c5b8d23caa2fabac1cadf17136379fbd8" + integrity sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw== + xmlbuilder@^15.1.1: version "15.1.1" resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5" @@ -19960,9 +19972,9 @@ yaml@2.3.1: integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ== yaml@^1.10.0, yaml@^1.10.2, yaml@^1.7.2: - version "1.10.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" - integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + version "1.10.3" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.3.tgz#76e407ed95c42684fb8e14641e5de62fe65bbcb3" + integrity sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA== yargs-parser@^18.1.2: version "18.1.3"
+ = (props: PaymentTableProps) => /> HANDLEDESCRIPTIONCREATE DATEPAYMENTSTATUSRELEASE DATEDATE PAID HANDLEDESCRIPTIONPAYMENTTYPESTATUSCREATE DATERELEASE DATEDATE PAID
+ = (props: PaymentTableProps) => /> {payment.handle}{payment.description}{payment.createDate}{payment.grossAmount}{payment.status}{payment.releaseDate}{payment.datePaid} + {payment.handle} + {payment.description} + {payment.grossAmount}{payment.type}{payment.status}{payment.createDate}{payment.releaseDate}{payment.datePaid} +