diff --git a/frontend/actions/auth.ts b/frontend/actions/auth.ts index 56c2d4c5..76171f35 100644 --- a/frontend/actions/auth.ts +++ b/frontend/actions/auth.ts @@ -22,7 +22,15 @@ export async function login(prevState: loginInitialState, formData: FormData) { } const company = await getCompany(); - if (company?.externals) { + if ("error" in company) { + console.error("Error getting company:", company.error); + return { + success: false, + message: company.error || "Login failed", + }; + } + const companyData = company.data; + if (companyData?.externals) { importQuickbooksData(); } diff --git a/frontend/api/business-profile.ts b/frontend/api/business-profile.ts index a8fda1cd..f7e31492 100644 --- a/frontend/api/business-profile.ts +++ b/frontend/api/business-profile.ts @@ -3,10 +3,15 @@ import { DocumentResponse, PresignedUploadResponse, DocumentCategories, Document import { authHeader, authWrapper, getClient } from "./client"; import { getCompany } from "./company"; import { gzip } from "pako"; +import { ServerActionResult, isServerActionError } from "./types"; -export const getAllDocuments = async (): Promise => { - const req = async (token: string): Promise => { - const companyId = (await getCompany()).id; +export const getAllDocuments = async (): Promise> => { + const req = async (token: string): Promise> => { + const companyResult = await getCompany(); + if (isServerActionError(companyResult)) { + return { success: false, error: companyResult.error }; + } + const companyId = companyResult.data.id; const documentType = DocumentTypes.GENERAL_BUSINESS; const client = getClient(); @@ -22,21 +27,25 @@ export const getAllDocuments = async (): Promise => { }); if (!response.ok || !data) { - throw new Error(error?.error || "Failed to fetch documents"); + return { success: false, error: error?.error || "Failed to fetch documents" }; } - return data; + return { success: true, data }; }; - return authWrapper()(req); + return authWrapper>()(req); }; export async function getBusinessDocumentUploadUrl( fileName: string, fileType: string -): Promise { - const req = async (token: string): Promise => { - const companyId = (await getCompany()).id; +): Promise> { + const req = async (token: string): Promise> => { + const companyResult = await getCompany(); + if (isServerActionError(companyResult)) { + return { success: false, error: companyResult.error }; + } + const companyId = companyResult.data.id; const client = getClient(); const { data, error, response } = await client.POST("/s3/getUploadUrl", { @@ -50,23 +59,27 @@ export async function getBusinessDocumentUploadUrl( }); if (!response.ok || !data) { - throw new Error(error?.error || "Failed to get upload URL"); + return { success: false, error: error?.error || "Failed to get upload URL" }; } - return data; + return { success: true, data }; }; - return authWrapper()(req); + return authWrapper>()(req); } export async function confirmBusinessDocumentUpload( key: string, documentId: string, category?: DocumentCategories -): Promise { - const req = async (token: string): Promise => { +): Promise> { + const req = async (token: string): Promise> => { const client = getClient(); - const companyId = (await getCompany()).id; + const companyResult = await getCompany(); + if (isServerActionError(companyResult)) { + return { success: false, error: companyResult.error }; + } + const companyId = companyResult.data.id; const { error, response } = await client.POST("/s3/confirmUpload", { headers: authHeader(token), @@ -80,15 +93,20 @@ export async function confirmBusinessDocumentUpload( }); if (!response.ok) { - throw new Error(error?.error || "Failed to confirm upload"); + return { success: false, error: error?.error || "Failed to confirm upload" }; } + + return { success: true, data: undefined }; }; - return authWrapper()(req); + return authWrapper>()(req); } -export async function updateDocumentCategory(documentId: string, category: DocumentCategories): Promise { - const req = async (token: string): Promise => { +export async function updateDocumentCategory( + documentId: string, + category: DocumentCategories +): Promise> { + const req = async (token: string): Promise> => { const client = getClient(); const { error, response } = await client.PATCH("/s3/updateDocumentCategory", { @@ -97,15 +115,17 @@ export async function updateDocumentCategory(documentId: string, category: Docum }); if (!response.ok) { - throw new Error(error?.error || "Failed to update category"); + return { success: false, error: error?.error || "Failed to update category" }; } + + return { success: true, data: undefined }; }; - return authWrapper()(req); + return authWrapper>()(req); } -export async function deleteBusinessDocument(key: string, documentId: string): Promise { - const req = async (token: string): Promise => { +export async function deleteBusinessDocument(key: string, documentId: string): Promise> { + const req = async (token: string): Promise> => { const client = getClient(); const { error, response } = await client.DELETE("/s3/deleteDocument", { @@ -114,14 +134,16 @@ export async function deleteBusinessDocument(key: string, documentId: string): P }); if (!response.ok) { - throw new Error(error?.error || "Failed to delete document"); + return { success: false, error: error?.error || "Failed to delete document" }; } + + return { success: true, data: undefined }; }; - return authWrapper()(req); + return authWrapper>()(req); } -export async function uploadToS3(uploadUrl: string, file: File): Promise { +export async function uploadToS3(uploadUrl: string, file: File): Promise> { const arrayBuffer = await file.arrayBuffer(); const compressed = gzip(new Uint8Array(arrayBuffer)); @@ -135,6 +157,8 @@ export async function uploadToS3(uploadUrl: string, file: File): Promise { }); if (!response.ok) { - throw new Error(`Upload failed: ${response.statusText}`); + return { success: false, error: `Upload failed: ${response.statusText}` }; } + + return { success: true, data: undefined }; } diff --git a/frontend/api/claim-location.ts b/frontend/api/claim-location.ts index 4ea35e61..843e2248 100644 --- a/frontend/api/claim-location.ts +++ b/frontend/api/claim-location.ts @@ -2,21 +2,22 @@ import { authHeader, authWrapper, getClient } from "./client"; import { CreateClaimLocationRequest, CreateClaimLocationResponse } from "@/types/claim-location"; +import { ServerActionResult } from "./types"; export const createClaimLocationLink = async ( payload: CreateClaimLocationRequest -): Promise => { - const req = async (token: string): Promise => { +): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.POST("/claim-locations", { headers: authHeader(token), body: payload, }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to create claim location link" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; diff --git a/frontend/api/claim.ts b/frontend/api/claim.ts index 19df536a..ee72b0a3 100644 --- a/frontend/api/claim.ts +++ b/frontend/api/claim.ts @@ -18,47 +18,50 @@ import { UploadClaimRelatedDocumentsResponse, } from "@/types/claim"; import { authHeader, authWrapper, getClient } from "./client"; +import { ServerActionResult, isServerActionError } from "./types"; -export const createClaim = async (payload: CreateClaimRequest): Promise => { - const req = async (token: string): Promise => { +export const createClaim = async (payload: CreateClaimRequest): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.POST("/claims", { headers: authHeader(token), body: payload, }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to create claim" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; -export const getClaims = async (input: GetCompanyClaimRequest): Promise => { - const req = async (token: string): Promise => { +export const getClaims = async ( + input: GetCompanyClaimRequest +): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.POST("/claims/company", { headers: authHeader(token), body: input, }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to get claims" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; export const getPurchaseLineItemsFromClaim = async (params: { claimId: string; -}): Promise => { - const req = async (token: string): Promise => { +}): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const id = params.claimId; if (!id) { - return []; + return { success: true, data: [] }; } const { data, error, response } = await client.GET(`/claims/{id}/line-item`, { headers: authHeader(token), @@ -67,16 +70,16 @@ export const getPurchaseLineItemsFromClaim = async (params: { }, }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to get purchase line items from claim" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; -export const getClaimById = async (claimId: string): Promise => { - const req = async (token: string): Promise => { +export const getClaimById = async (claimId: string): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.GET("/claims/{id}", { headers: authHeader(token), @@ -85,19 +88,19 @@ export const getClaimById = async (claimId: string): Promise()(req); + return authWrapper>()(req); }; export const updateClaimStatus = async ( claimId: string, payload: UpdateClaimStatusRequest -): Promise => { - const req = async (token: string): Promise => { +): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.PATCH("/claims/{id}/status", { headers: authHeader(token), @@ -107,20 +110,26 @@ export const updateClaimStatus = async ( body: payload, }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to update claim status" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; export const uploadAndConfirmDocumentRelation = async ( claimId: string, payload: Omit, file: File -) => { - const upload = await uploadClaimRelatedDocuments(claimId, payload); +): Promise> => { + const uploadResult = await uploadClaimRelatedDocuments(claimId, payload); + + if (isServerActionError(uploadResult)) { + return { success: false, error: uploadResult.error }; + } + + const upload = uploadResult.data; const response = await fetch(upload.uploadUrl, { method: "PUT", @@ -130,90 +139,102 @@ export const uploadAndConfirmDocumentRelation = async ( if (!response.ok) { const errorText = await response.text(); console.error("Failed to upload file to S3:", response.status, errorText); - throw new Error(`S3 upload failed with status ${response.status}: ${errorText}`); + return { success: false, error: `S3 upload failed with status ${response.status}: ${errorText}` }; } - await conformUploadedDocument(claimId, { + const confirmResult = await conformUploadedDocument(claimId, { documentId: upload.documentId, key: upload.key, claimId: claimId, category: null, }); + + if (isServerActionError(confirmResult)) { + return { success: false, error: confirmResult.error }; + } + + return { success: true, data: undefined }; }; export const uploadClaimRelatedDocuments = async ( claimId: string, payload: Omit -): Promise => { - const req = async (token: string): Promise => { +): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.POST("/s3/getUploadUrl", { headers: authHeader(token), body: { ...payload, claimId: claimId, documentType: "CLAIM" }, }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { console.log(JSON.stringify(error)); - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to upload claim related documents" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; export const conformUploadedDocument = async ( selfDisasterId: string, payload: ConfirmDocumentUploadRequest -): Promise => { - const req = async (token: string): Promise => { +): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.POST("/s3/confirmUpload/selfDisaster", { headers: authHeader(token), body: { ...payload, selfDisasterId: selfDisasterId, documentType: "CLAIM" }, }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to confirm uploaded document" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; -export const linkLineItemToClaim = async (claimId: string, purchaseLineItemId: string) => { - const req = async (token: string) => { +export const linkLineItemToClaim = async ( + claimId: string, + purchaseLineItemId: string +): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.POST("/claims/line-item", { headers: authHeader(token), body: { claimId, purchaseLineItemId }, }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to link line item to claim" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; -export const linkPurchaseToClaim = async (claimId: string, purchaseId: string) => { - const req = async (token: string) => { +export const linkPurchaseToClaim = async ( + claimId: string, + purchaseId: string +): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.POST("/claims/purchase", { headers: authHeader(token), body: { claimId, purchaseId }, }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to link purchase to claim" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; -export const createClaimPDF = async (claimId: string) => { - const req = async (token: string) => { +export const createClaimPDF = async (claimId: string): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.GET("/claims/{id}/pdf", { headers: authHeader(token), @@ -222,16 +243,16 @@ export const createClaimPDF = async (claimId: string) => { }, }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to create claim PDF" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; -export const deleteClaim = async (claimId: string) => { - const req = async (token: string) => { +export const deleteClaim = async (claimId: string): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.DELETE("/claims/{id}", { headers: authHeader(token), @@ -240,10 +261,10 @@ export const deleteClaim = async (claimId: string) => { }, }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to delete claim" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; diff --git a/frontend/api/company.ts b/frontend/api/company.ts index 23178ae7..ac3e2085 100644 --- a/frontend/api/company.ts +++ b/frontend/api/company.ts @@ -9,95 +9,98 @@ import { UpdateCompanyResponse, } from "@/types/company"; import { authHeader, authWrapper, getClient } from "./client"; +import { ServerActionResult } from "./types"; -export const createCompany = async (payload: CreateCompanyRequest): Promise => { - const req = async (token: string): Promise => { +export const createCompany = async (payload: CreateCompanyRequest): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.POST("/companies", { headers: authHeader(token), body: payload, }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to create company" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; -export const getCompanyLocations = async (): Promise => { - const req = async (token: string): Promise => { +export const getCompanyLocations = async (): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.GET("/companies/location-address", { headers: authHeader(token), }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to get company locations" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; -export const getCompany = async (): Promise => { - const req = async (token: string): Promise => { +export const getCompany = async (): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.GET("/companies", { headers: authHeader(token), }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to get company" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; -export const getClaimInProgress = async (): Promise => { - const req = async (token: string): Promise => { +export const getClaimInProgress = async (): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.GET("/companies/claim-in-progress", { headers: authHeader(token), }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to get claim in progress" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; -export const companyHasData = async (): Promise => { - const req = async (token: string): Promise => { +export const companyHasData = async (): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.GET("/companies/has-company-data", { headers: authHeader(token), }); if (response.ok) { - return data ?? { hasExternalData: false, hasFinancialData: false }; + return { success: true, data: data ?? { hasExternalData: false, hasFinancialData: false } }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to check company data" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; -export const updateCompany = async (payload: UpdateCompanyRequest): Promise => { - const req = async (token: string): Promise => { +export const updateCompany = async ( + payload: UpdateCompanyRequest +): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.PATCH("/companies", { headers: authHeader(token), body: payload, }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to update company" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; diff --git a/frontend/api/dashboard.ts b/frontend/api/dashboard.ts index b3704f7c..cf612143 100644 --- a/frontend/api/dashboard.ts +++ b/frontend/api/dashboard.ts @@ -2,23 +2,37 @@ import { BannerData } from "@/types/user"; import { getNotifications } from "./notifications"; import { getClaimInProgress } from "./company"; -import { GetClaimInProgressForCompanyResponse } from "@/types/company"; +import { ServerActionResult, isServerActionError } from "./types"; -export const getDashboardBannerData = async (): Promise => { - const disasterNotifications = await getNotifications({ +export const getDashboardBannerData = async (): Promise> => { + const notificationsResult = await getNotifications({ type: "web", page: 1, limit: 1, status: "unread", }); + + if (isServerActionError(notificationsResult)) { + return { success: false, error: notificationsResult.error }; + } + + const disasterNotifications = notificationsResult.data; + if (disasterNotifications.length > 0) { const disaster = disasterNotifications[0].femaDisaster; - const claim: GetClaimInProgressForCompanyResponse = await getClaimInProgress(); + const claimResult = await getClaimInProgress(); + + if (isServerActionError(claimResult)) { + return { success: false, error: claimResult.error }; + } + + const claim = claimResult.data; + if (claim) { - return { status: "has-claim", disaster, claim }; + return { success: true, data: { status: "has-claim", disaster, claim } }; } else { - return { status: "no-claim", disaster }; + return { success: true, data: { status: "no-claim", disaster } }; } } - return { status: "no-disaster" }; + return { success: true, data: { status: "no-disaster" } }; }; diff --git a/frontend/api/fema-risk-index.ts b/frontend/api/fema-risk-index.ts index 6918f72d..a4a51353 100644 --- a/frontend/api/fema-risk-index.ts +++ b/frontend/api/fema-risk-index.ts @@ -2,31 +2,34 @@ import { FemaRisKIndexCountiesFemaDisaster } from "@/types/fema-risk-index"; import { authHeader, authWrapper, getClient } from "./client"; +import { ServerActionResult } from "./types"; -export const getFemaRiskIndexData = async (): Promise => { - const req = async (token: string): Promise => { +export const getFemaRiskIndexData = async (): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.GET("/fema-risk-index", { headers: authHeader(token), }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to get FEMA risk index data" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; -export const refreshFemaRiskIndexData = async (): Promise => { - const req = async (token: string): Promise => { +export const refreshFemaRiskIndexData = async (): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { error, response } = await client.POST("/fema-risk-index", { headers: authHeader(token), }); - if (!response.ok) { - throw Error(error?.error); + if (response.ok) { + return { success: true, data: undefined }; + } else { + return { success: false, error: error?.error || "Failed to refresh FEMA risk index data" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; diff --git a/frontend/api/insurance.ts b/frontend/api/insurance.ts index 0a8e8421..c53a8380 100644 --- a/frontend/api/insurance.ts +++ b/frontend/api/insurance.ts @@ -10,94 +10,97 @@ import { UpdateInsurancePolicyResponse, } from "@/types/insurance-policy"; import { getClient, authHeader, authWrapper } from "./client"; +import { ServerActionResult } from "./types"; -export const createInsurancePolicy = async (payload: CreateInsurancePolicyRequest): Promise => { - const req = async (token: string): Promise => { +export const createInsurancePolicy = async ( + payload: CreateInsurancePolicyRequest +): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.POST("/insurance", { headers: authHeader(token), body: payload, }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to create insurance policy" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; export const createInsurancePolicyBulk = async ( payload: CreateInsurancePolicyBulkRequest -): Promise => { - const req = async (token: string): Promise => { +): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.POST("/insurance/bulk", { headers: authHeader(token), body: payload, }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to create insurance policies" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; -export const getInsurancePolicies = async (): Promise => { - const req = async (token: string): Promise => { +export const getInsurancePolicies = async (): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.GET("/insurance", { headers: authHeader(token), }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to get insurance policies" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; export const updateInsurancePolicy = async ( payload: UpdateInsurancePolicyRequest -): Promise => { - const req = async (token: string): Promise => { +): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.PATCH("/insurance", { headers: authHeader(token), body: payload, }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to update insurance policy" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; export const updateInsurancePolicyBulk = async ( payload: UpdateInsurancePolicyBulkRequest -): Promise => { - const req = async (token: string): Promise => { +): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.PATCH("/insurance/bulk", { headers: authHeader(token), body: payload, }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to update insurance policies" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; -export const deleteInsurancePolicy = async (insurancePolicyId: string): Promise => { - const req = async (token: string): Promise => { +export const deleteInsurancePolicy = async (insurancePolicyId: string): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { error, response } = await client.DELETE("/insurance/{id}", { headers: authHeader(token), @@ -107,9 +110,11 @@ export const deleteInsurancePolicy = async (insurancePolicyId: string): Promise< }, }, }); - if (!response.ok) { - throw Error(error?.error); + if (response.ok) { + return { success: true, data: undefined }; + } else { + return { success: false, error: error?.error || "Failed to delete insurance policy" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; diff --git a/frontend/api/invoice-line-item.ts b/frontend/api/invoice-line-item.ts index f7e66cd5..320eac63 100644 --- a/frontend/api/invoice-line-item.ts +++ b/frontend/api/invoice-line-item.ts @@ -1,11 +1,12 @@ "use server"; import { CreateInvoiceLineItemsRequest, CreateInvoiceLineItemsResponse } from "@/types/invoice-line-items"; import { authHeader, authWrapper, getClient } from "./client"; +import { ServerActionResult } from "./types"; export const createBulkInvoiceLineItems = async ( newLineItems: CreateInvoiceLineItemsRequest -): Promise => { - const req = async (token: string): Promise => { +): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.POST("/invoice/line/bulk", { body: newLineItems, @@ -13,11 +14,11 @@ export const createBulkInvoiceLineItems = async ( }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to create invoice line items" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; diff --git a/frontend/api/invoice.ts b/frontend/api/invoice.ts index f1418360..7395f42b 100644 --- a/frontend/api/invoice.ts +++ b/frontend/api/invoice.ts @@ -1,9 +1,13 @@ "use server"; import { authHeader, authWrapper, getClient } from "./client"; import { CreateInvoiceRequest, Invoice, TotalInvoiceSum } from "../types/invoice"; +import { ServerActionResult } from "./types"; -export const getAllInvoicesForCompany = async (pageNumber: number, resultsPerPage: number): Promise => { - const req = async (token: string) => { +export const getAllInvoicesForCompany = async ( + pageNumber: number, + resultsPerPage: number +): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.GET("/invoice", { params: { @@ -15,16 +19,19 @@ export const getAllInvoicesForCompany = async (pageNumber: number, resultsPerPag headers: authHeader(token), }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to get invoices" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; -export const sumInvoicesByCompanyAndDateRange = async (startDate: Date, endDate: Date): Promise => { - const req = async (token: string): Promise<{ total: number }> => { +export const sumInvoicesByCompanyAndDateRange = async ( + startDate: Date, + endDate: Date +): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.GET("/invoice/bulk/totalIncome", { headers: authHeader(token), @@ -37,17 +44,17 @@ export const sumInvoicesByCompanyAndDateRange = async (startDate: Date, endDate: }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to sum invoices" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; -export const createInvoice = async (newLineItems: CreateInvoiceRequest): Promise => { - const req = async (token: string): Promise => { +export const createInvoice = async (newLineItems: CreateInvoiceRequest): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.POST("/invoice/bulk", { body: newLineItems, @@ -55,11 +62,11 @@ export const createInvoice = async (newLineItems: CreateInvoiceRequest): Promise }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to create invoice" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; diff --git a/frontend/api/location.ts b/frontend/api/location.ts index cb7e5801..7baa6781 100644 --- a/frontend/api/location.ts +++ b/frontend/api/location.ts @@ -9,87 +9,80 @@ import { UpdateLocationResponse, } from "@/types/location"; import { authHeader, authWrapper, getClient } from "./client"; +import { ServerActionResult } from "./types"; -export const createLocation = async (payload: CreateLocationRequest): Promise => { - const req = async (token: string): Promise => { +export const createLocation = async (payload: CreateLocationRequest): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.POST("/location-address", { headers: authHeader(token), body: payload, }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - const apiError = new Error(error?.error || "Failed to create locations - Unkown Error") as Error & { - status: number; - statusText: string; - }; - apiError.status = response.status; - apiError.statusText = response.statusText; - throw apiError; + return { success: false, error: error?.error || "Failed to create location" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; -export const createLocationBulk = async (payload: CreateLocationBulkRequest): Promise => { - const req = async (token: string): Promise => { +export const createLocationBulk = async ( + payload: CreateLocationBulkRequest +): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.POST("/location-address/bulk", { headers: authHeader(token), body: payload, }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - const apiError = new Error(error?.error || "Failed to create locations - Unkown Error") as Error & { - status: number; - statusText: string; - }; - apiError.status = response.status; - apiError.statusText = response.statusText; - throw apiError; + return { success: false, error: error?.error || "Failed to create locations" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; -export const updateLocationAddress = async (payload: UpdateLocationRequest): Promise => { - const req = async (token: string): Promise => { +export const updateLocationAddress = async ( + payload: UpdateLocationRequest +): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.PATCH("/location-address", { headers: authHeader(token), body: payload, }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to update location" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; export const updateLocationAddressBulk = async ( payload: UpdateLocationBulkRequest -): Promise => { - const req = async (token: string): Promise => { +): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.PATCH("/location-address/bulk", { headers: authHeader(token), body: payload, }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to update locations" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; -export const deleteLocation = async (locationId: string): Promise => { - const req = async (token: string): Promise => { +export const deleteLocation = async (locationId: string): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { error, response } = await client.DELETE("/location-address/{id}", { headers: authHeader(token), @@ -99,9 +92,11 @@ export const deleteLocation = async (locationId: string): Promise => { }, }, }); - if (!response.ok) { - throw Error(error?.error); + if (response.ok) { + return { success: true, data: undefined }; + } else { + return { success: false, error: error?.error || "Failed to delete location" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; diff --git a/frontend/api/notifications.ts b/frontend/api/notifications.ts index 44bf9622..249d7432 100644 --- a/frontend/api/notifications.ts +++ b/frontend/api/notifications.ts @@ -7,9 +7,12 @@ import { UnreadNotificationsResponse, } from "@/types/notifications"; import { authHeader, authWrapper, getClient } from "./client"; +import { ServerActionResult } from "./types"; -export const getNotifications = async (filters?: NotificationFilters): Promise => { - const req = async (token: string): Promise => { +export const getNotifications = async ( + filters?: NotificationFilters +): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.GET("/notifications", { params: { @@ -24,20 +27,20 @@ export const getNotifications = async (filters?: NotificationFilters): Promise()(req); + return authWrapper>()(req); }; export const updateNotificationStatus = async ( notificationId: string, status: string -): Promise => { - const req = async (token: string): Promise => { +): Promise> => { + const req = async (token: string): Promise> => { const path = status === "read" ? "/notifications/{id}/markAsRead" : "/notifications/{id}/markUnread"; const client = getClient(); const { data, error, response } = await client.PATCH(path, { @@ -48,45 +51,45 @@ export const updateNotificationStatus = async ( }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error || "Failed to update notification status"); + return { success: false, error: error?.error || "Failed to update notification status" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; -export const markAllNotificationsAsRead = async (): Promise => { - const req = async (token: string): Promise => { +export const markAllNotificationsAsRead = async (): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.PATCH("/notifications/user/markAllAsRead", { headers: authHeader(token), }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error || "Failed to mark all as read"); + return { success: false, error: error?.error || "Failed to mark all as read" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; -export const getUserUnreadNotifications = async (): Promise => { - const req = async (token: string) => { +export const getUserUnreadNotifications = async (): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); - const { data, response } = await client.GET("/notifications/unread", { + const { data, error, response } = await client.GET("/notifications/unread", { headers: authHeader(token), }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error("Failed to get user unread notifications"); + return { success: false, error: error || "Failed to get user unread notifications" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; diff --git a/frontend/api/preferences.ts b/frontend/api/preferences.ts index 34ee1adc..510443a6 100644 --- a/frontend/api/preferences.ts +++ b/frontend/api/preferences.ts @@ -2,34 +2,37 @@ import { UpdateUserNotificationPreferencesDTO, UserPreferences } from "@/types/preferences"; import { authHeader, authWrapper, getClient } from "./client"; +import { ServerActionResult } from "./types"; -export const getUserPreferences = async () => { - const req = async (token: string) => { +export const getUserPreferences = async (): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.GET("/preferences", { headers: authHeader(token), }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to get user preferences" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; -export const updateUserPreferences = async (preferences: UpdateUserNotificationPreferencesDTO) => { - const req = async (token: string) => { +export const updateUserPreferences = async ( + preferences: UpdateUserNotificationPreferencesDTO +): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.PUT("/preferences", { headers: authHeader(token), body: preferences, }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to update user preferences" }; } }; - return authWrapper>>()(req); + return authWrapper>()(req); }; diff --git a/frontend/api/purchase-line-item.ts b/frontend/api/purchase-line-item.ts index 307ff0b4..ae0da57b 100644 --- a/frontend/api/purchase-line-item.ts +++ b/frontend/api/purchase-line-item.ts @@ -1,11 +1,12 @@ "use server"; import { CreatePurchaseLineItemsRequest, CreatePurchaseLineItemsResponse } from "../types/purchase-line-items"; import { authHeader, authWrapper, getClient } from "./client"; +import { ServerActionResult } from "./types"; export const createBulkPurchaseLineItems = async ( newLineItems: CreatePurchaseLineItemsRequest -): Promise => { - const req = async (token: string): Promise => { +): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.POST("/purchase/line/bulk", { body: newLineItems, @@ -13,11 +14,11 @@ export const createBulkPurchaseLineItems = async ( }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to create purchase line items" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; diff --git a/frontend/api/purchase.ts b/frontend/api/purchase.ts index f5f24fb8..deff498f 100644 --- a/frontend/api/purchase.ts +++ b/frontend/api/purchase.ts @@ -8,9 +8,13 @@ import { PurchaseWithLineItems, } from "../types/purchase"; import { authHeader, authWrapper, getClient } from "./client"; +import { ServerActionResult } from "./types"; -export const sumPurchasesByCompanyAndDateRange = async (startDate: Date, endDate: Date): Promise<{ total: number }> => { - const req = async (token: string): Promise<{ total: number }> => { +export const sumPurchasesByCompanyAndDateRange = async ( + startDate: Date, + endDate: Date +): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.GET("/purchase/bulk/totalExpenses", { headers: authHeader(token), @@ -23,17 +27,21 @@ export const sumPurchasesByCompanyAndDateRange = async (startDate: Date, endDate }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to sum purchases" }; } }; - return authWrapper<{ total: number }>()(req); + return authWrapper>()(req); }; -export const updateCategory = async (category: string, purchaseLineIds: string[], removeCategory: boolean) => { - const req = async (token: string) => { +export const updateCategory = async ( + category: string, + purchaseLineIds: string[], + removeCategory: boolean +): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { error, response } = await client.PATCH("/purchase/line/category", { headers: authHeader(token), @@ -44,12 +52,14 @@ export const updateCategory = async (category: string, purchaseLineIds: string[] }, }); - if (!response.ok) { - throw Error(error?.error); + if (response.ok) { + return { success: true, data: undefined }; + } else { + return { success: false, error: error?.error || "Failed to update category" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; const typeMap: Record = { @@ -59,8 +69,8 @@ const typeMap: Record = { type typeString = "extraneous" | "typical" | "pending" | "suggested extraneous" | "suggested typical"; -export const updateType = async (type: typeString, purchaseLineIds: string[]) => { - const req = async (token: string) => { +export const updateType = async (type: typeString, purchaseLineIds: string[]): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { error, response } = await client.PATCH("/purchase/line/type", { headers: authHeader(token), @@ -69,16 +79,20 @@ export const updateType = async (type: typeString, purchaseLineIds: string[]) => type: typeMap[type], }, }); - if (!response.ok) { - throw Error(error?.error); + if (response.ok) { + return { success: true, data: undefined }; + } else { + return { success: false, error: error?.error || "Failed to update type" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; -export const createPurchaseForCompany = async (newPurchase: CreatePurchaseInput): Promise => { - const req = async (token: string): Promise => { +export const createPurchaseForCompany = async ( + newPurchase: CreatePurchaseInput +): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.POST("/purchase/bulk", { body: newPurchase, @@ -86,17 +100,17 @@ export const createPurchaseForCompany = async (newPurchase: CreatePurchaseInput) }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to create purchase" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; -export const fetchPurchases = async (filters: FilteredPurchases): Promise => { - const req = async (token: string): Promise => { +export const fetchPurchases = async (filters: FilteredPurchases): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.GET("/purchase", { params: { @@ -115,34 +129,34 @@ export const fetchPurchases = async (filters: FilteredPurchases): Promise()(req); + return authWrapper>()(req); }; -export const fetchAllCategories = async (): Promise => { - const req = async (token: string): Promise => { +export const fetchAllCategories = async (): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.GET("/purchase/categories", { headers: authHeader(token), }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to fetch categories" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; export const getAllPurchasesForExport = async ( filters: FilteredPurchases, total: number -): Promise => { - const req = async (token: string): Promise => { +): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.GET("/purchase", { headers: authHeader(token), @@ -161,11 +175,11 @@ export const getAllPurchasesForExport = async ( }); if (response.ok) { - return data?.purchases || []; + return { success: true, data: data?.purchases || [] }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to get purchases for export" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; diff --git a/frontend/api/quickbooks.ts b/frontend/api/quickbooks.ts index 142579ca..9b3bd02f 100644 --- a/frontend/api/quickbooks.ts +++ b/frontend/api/quickbooks.ts @@ -1,37 +1,36 @@ "use server"; import { authHeader, authWrapper, getClient } from "./client"; +import { ServerActionResult } from "./types"; -export const importQuickbooksData = async (): Promise<{ success: true } | undefined> => { - const req = async (token: string): Promise<{ success: true } | undefined> => { +export const importQuickbooksData = async (): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); - const { data, response } = await client.POST("/quickbooks/importQuickbooksData", { + const { data, error, response } = await client.POST("/quickbooks/importQuickbooksData", { headers: authHeader(token), }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - // TODO: error message? - return undefined; + return { success: false, error: error?.error || "Failed to import QuickBooks data" }; } }; - return authWrapper<{ success: true } | undefined>()(req); + return authWrapper>()(req); }; -export const redirectToQuickbooks = async (): Promise => { - const req = async (token: string): Promise => { +export const redirectToQuickbooks = async (): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); - const { data, response } = await client.GET("/quickbooks", { + const { data, response, error } = await client.GET("/quickbooks", { headers: authHeader(token), }); if (response.ok && data?.url) { - return data.url; + return { success: true, data: data.url }; } else { - // TODO: error message - return undefined; + return { success: false, error: error || "Failed to get QuickBooks redirect URL" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; diff --git a/frontend/api/requestHandlers.ts b/frontend/api/requestHandlers.ts new file mode 100644 index 00000000..383c1063 --- /dev/null +++ b/frontend/api/requestHandlers.ts @@ -0,0 +1,169 @@ +import { + useMutation, + useQuery, + useQueries, + useInfiniteQuery, + UseQueryOptions, + UseQueryResult, + UseInfiniteQueryResult, + InfiniteData, + QueryFunctionContext, + type UseMutationOptions, + type UseMutationResult, + type QueryKey, + type QueryFunction, +} from "@tanstack/react-query"; +import { ServerActionResult } from "./types"; + +// Helper to check if result is an error +function isServerActionError(result: ServerActionResult): result is { success: false; error: string } { + return !result.success; +} + +// Custom hook that mimics useMutation's interface +export function useServerActionMutation( + options: Omit, TError, TVariables, TContext>, "onSuccess"> & { + onSuccess?: (data: TData, variables: TVariables, context: TContext) => void; + } +): UseMutationResult, TError, TVariables, TContext> { + const { onSuccess, ...restOptions } = options; + + const mutation = useMutation, TError, TVariables, TContext>({ + ...restOptions, + mutationFn: async (variables, context) => { + if (!options.mutationFn) { + throw new Error("mutationFn is required"); + } + + const result = await options.mutationFn(variables, context); + + // Throw on error to trigger onError handler + if (isServerActionError(result)) { + throw result.error as TError; + } + + return result; + }, + onSuccess: (data, variables, context) => { + // Extract the actual data before passing to onSuccess + if (!isServerActionError(data)) { + onSuccess?.(data.data, variables, context); + } + }, + }); + + return mutation; +} + +// Custom hook that mimics useQuery's interface +export function useServerActionQuery( + options: Omit, TError, TData, TQueryKey>, "select"> & { + select?: (data: TData) => TData; + } +): UseQueryResult { + const { select, ...restOptions } = options; + + const query = useQuery, TError, TData, TQueryKey>({ + ...restOptions, + queryFn: options.queryFn + ? async (context) => { + // Type guard to ensure it's a function + const queryFn = options.queryFn as QueryFunction, TQueryKey>; + const result = await queryFn(context); + + // Throw on error to trigger error state + if (isServerActionError(result)) { + throw result.error as TError; + } + + return result; + } + : undefined, + select: (data) => { + // Unwrap the data + if (!isServerActionError(data)) { + const unwrapped = data.data; + // Apply user's select if provided + return select ? select(unwrapped) : unwrapped; + } + // This shouldn't happen due to queryFn throw, but TypeScript needs it + throw new Error("Unexpected error state"); + }, + }); + + return query; +} + +// Custom hook for useQueries that unwraps ServerActionResult +export function useServerActionQueries< + TData = unknown, + TError = unknown, + TQueryKey extends QueryKey = QueryKey, +>(options: { + queries: Array< + Omit, TError, TData, TQueryKey>, "select"> & { + select?: (data: TData) => TData; + } + >; +}) { + return useQueries({ + queries: options.queries.map((queryOptions) => ({ + ...queryOptions, + queryFn: queryOptions.queryFn + ? async (context: Parameters, TQueryKey>>[0]) => { + const queryFn = queryOptions.queryFn as QueryFunction, TQueryKey>; + const result = await queryFn(context); + + if (isServerActionError(result)) { + throw result.error as TError; + } + + return result; + } + : undefined, + select: (data: ServerActionResult) => { + if (!isServerActionError(data)) { + const unwrapped = data.data; + return queryOptions.select ? queryOptions.select(unwrapped) : unwrapped; + } + throw new Error("Unexpected error state"); + }, + })), + }); +} + +// Custom hook for useInfiniteQuery that unwraps ServerActionResult +// Note: This hook unwraps ServerActionResult in queryFn, so getNextPageParam receives the unwrapped TData +export function useServerActionInfiniteQuery< + TData = unknown, + TError = unknown, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>(options: { + queryKey: TQueryKey; + queryFn: (context: { pageParam: TPageParam }) => Promise>; + getNextPageParam: (lastPage: TData, allPages: TData[]) => TPageParam | undefined; + initialPageParam: TPageParam; + enabled?: boolean; + staleTime?: number; + gcTime?: number; + refetchOnWindowFocus?: boolean; + refetchOnMount?: boolean; + refetchOnReconnect?: boolean; + retry?: boolean | number; +}): UseInfiniteQueryResult, TError> { + const { queryFn, ...restOptions } = options; + + return useInfiniteQuery({ + ...restOptions, + queryFn: async (context: QueryFunctionContext) => { + const result = await queryFn({ pageParam: context.pageParam as TPageParam }); + + if (isServerActionError(result)) { + throw result.error as TError; + } + + return result.data; + }, + }) as UseInfiniteQueryResult, TError>; +} diff --git a/frontend/api/self-disaster.ts b/frontend/api/self-disaster.ts index 5c8fada2..c8f06de2 100644 --- a/frontend/api/self-disaster.ts +++ b/frontend/api/self-disaster.ts @@ -7,28 +7,31 @@ import { UpdateSelfDisasterResponse, } from "@/types/self-disaster"; import { authHeader, authWrapper, getClient } from "./client"; +import { ServerActionResult } from "./types"; -export const createSelfDisaster = async (payload: CreateSelfDisasterRequest): Promise => { - const req = async (token: string): Promise => { +export const createSelfDisaster = async ( + payload: CreateSelfDisasterRequest +): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.POST("/disaster/self", { headers: authHeader(token), body: payload, }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to create self disaster" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; export const updateSelfDisaster = async ( id: string, payload: UpdateSelfDisasterRequest -): Promise => { - const req = async (token: string): Promise => { +): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.PATCH("/disaster/self/{id}", { headers: authHeader(token), @@ -38,10 +41,10 @@ export const updateSelfDisaster = async ( body: payload, }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to update self disaster" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; diff --git a/frontend/api/types.ts b/frontend/api/types.ts new file mode 100644 index 00000000..3c708b21 --- /dev/null +++ b/frontend/api/types.ts @@ -0,0 +1,15 @@ +export type ServerActionResult = { success: true; data: TData } | { success: false; error: string }; + +// Type guard to check if result is successful +export function isServerActionSuccess( + result: ServerActionResult +): result is { success: true; data: TData } { + return result.success === true; +} + +// Type guard to check if result is an error +export function isServerActionError( + result: ServerActionResult +): result is { success: false; error: string } { + return result.success === false; +} diff --git a/frontend/api/user.ts b/frontend/api/user.ts index d778e7f5..4ac48d94 100644 --- a/frontend/api/user.ts +++ b/frontend/api/user.ts @@ -8,14 +8,15 @@ import { } from "@/types/user"; import { createSupabaseClient } from "@/utils/supabase/server"; import { authHeader, authWrapper, getClient } from "./client"; +import { ServerActionResult } from "./types"; -export const createUser = async (payload: CreateUserRequest): Promise => { - const req = async (token: string): Promise => { +export const createUser = async (payload: CreateUserRequest): Promise> => { + const req = async (token: string): Promise> => { const supabase = await createSupabaseClient(); if (!payload.email) { const { data, error } = await supabase.auth.getUser(); if (error) { - throw Error("Error with Retrieving Email. Please Try Again"); + return { success: false, error: "Error with Retrieving Email. Please Try Again" }; } payload.email = data.user?.email; } @@ -30,42 +31,44 @@ export const createUser = async (payload: CreateUserRequest): Promise => { onboarding_step: requiredOnboardingProgress.COMPANY, }, }); - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to create user" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; -export const getUser = async (): Promise => { - const req = async (token: string): Promise => { +export const getUser = async (): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.GET("/users", { headers: authHeader(token), }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to get user" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; -export const updateUserInfo = async (payload: UpdateUserRequest & { id: string }) => { - const req = async (token: string) => { +export const updateUserInfo = async ( + payload: UpdateUserRequest & { id: string } +): Promise> => { + const req = async (token: string): Promise> => { const client = getClient(); const { data, error, response } = await client.PATCH("/users", { headers: authHeader(token), body: { ...payload, id: payload.id }, }); if (response.ok) { - return data!; + return { success: true, data: data! }; } else { - throw Error(error?.error); + return { success: false, error: error?.error || "Failed to update user info" }; } }; - return authWrapper()(req); + return authWrapper>()(req); }; diff --git a/frontend/app/NavBar.tsx b/frontend/app/NavBar.tsx index 7fd86006..ab8f0cbd 100644 --- a/frontend/app/NavBar.tsx +++ b/frontend/app/NavBar.tsx @@ -1,6 +1,7 @@ import { logoutUser } from "@/actions/auth"; import { getUserUnreadNotifications } from "@/api/notifications"; -import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { useServerActionQuery } from "@/api/requestHandlers"; +import { useQueryClient } from "@tanstack/react-query"; import { UserIcon } from "lucide-react"; import Image from "next/image"; import Link from "next/link"; @@ -15,7 +16,7 @@ export default function NavBar() { const pathname = usePathname(); const queryClient = useQueryClient(); - const { data: unreadNotifications } = useQuery({ + const { data: unreadNotifications } = useServerActionQuery({ queryKey: ["unreadNotifications"], queryFn: getUserUnreadNotifications, }); diff --git a/frontend/app/business-profile/overview/BusinessCard.tsx b/frontend/app/business-profile/overview/BusinessCard.tsx index d54b0c02..114dd80a 100644 --- a/frontend/app/business-profile/overview/BusinessCard.tsx +++ b/frontend/app/business-profile/overview/BusinessCard.tsx @@ -2,12 +2,13 @@ import { getCompany, updateCompany } from "@/api/company"; import { UpdateCompanyRequest } from "@/types/company"; -import { useMutation, useQuery } from "@tanstack/react-query"; +import { useServerActionMutation } from "@/api/requestHandlers"; import { useEffect, useState } from "react"; import CompanyEditor from "./BusinessInfoEditor"; import { getUser } from "@/api/user"; import { Card } from "@/components/ui/card"; import Loading from "@/components/loading"; +import { useServerActionQuery } from "@/api/requestHandlers"; export default function BusinessCard() { const [businessInfo, setBusinessInfo] = useState({ @@ -25,10 +26,10 @@ export default function BusinessCard() { const [error, setError] = useState(null); const [editing, setEditing] = useState(false); - const { mutate: updateBusinessMutate } = useMutation({ + const { mutate: updateBusinessMutate } = useServerActionMutation({ mutationFn: (businessInfo: UpdateCompanyRequest) => updateCompany(businessInfo), onError: (error: Error) => { - setError(error.message); + setError(String(error)); }, }); @@ -39,12 +40,12 @@ export default function BusinessCard() { } }; - const { data: businessQuery, isPending: businessPending } = useQuery({ + const { data: businessQuery, isPending: businessPending } = useServerActionQuery({ queryKey: ["businessInfo"], queryFn: getCompany, }); - const { data: userQuery } = useQuery({ + const { data: userQuery } = useServerActionQuery({ queryKey: ["userInfo"], queryFn: getUser, }); diff --git a/frontend/app/business-profile/overview/InsuranceCard.tsx b/frontend/app/business-profile/overview/InsuranceCard.tsx index 0914d444..6924cb1d 100644 --- a/frontend/app/business-profile/overview/InsuranceCard.tsx +++ b/frontend/app/business-profile/overview/InsuranceCard.tsx @@ -6,12 +6,13 @@ import { getInsurancePolicies, updateInsurancePolicy, } from "@/api/insurance"; +import { useServerActionQuery } from "@/api/requestHandlers"; import InsuranceEditor from "@/components/InsuranceEditor"; import Loading from "@/components/loading"; import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; import { CreateInsurancePolicyRequest, InsurancePolicy, UpdateInsurancePolicyRequest } from "@/types/insurance-policy"; -import { useMutation, useQuery } from "@tanstack/react-query"; +import { useServerActionMutation } from "@/api/requestHandlers"; import { useEffect, useState } from "react"; import { IoAddCircleOutline } from "react-icons/io5"; @@ -28,42 +29,42 @@ export default function InsuranceCard({ const [editingInsuranceIndex, setEditingInsuranceIndex] = useState(null); const [saveError, setSaveError] = useState(null); - const { data: insuranceQuery, isPending: insurancePending } = useQuery({ + const { data: insuranceQuery, isPending: insurancePending } = useServerActionQuery({ queryKey: ["insuranceInfo"], queryFn: getInsurancePolicies, }); - const { mutate: updateInsuranceMutate } = useMutation({ + const { mutate: updateInsuranceMutate } = useServerActionMutation({ mutationFn: (insurance: UpdateInsurancePolicyRequest) => updateInsurancePolicy(insurance), onSuccess: () => { setSaveError(null); setEditingInsuranceIndex(null); }, onError: (error: Error) => { - const errorMessage = error.message || "Error updating policy. Check required fields and try again"; + const errorMessage = String(error) || "Error updating policy. Check required fields and try again"; setSaveError(errorMessage); }, }); - const { mutate: createInsuranceMutate } = useMutation({ + const { mutate: createInsuranceMutate } = useServerActionMutation({ mutationFn: (insurance: CreateInsurancePolicyRequest) => createInsurancePolicy(insurance), onSuccess: () => { setSaveError(null); setEditingInsuranceIndex(null); }, onError: (error: Error) => { - const errorMessage = error.message || "Error creating policy. Check required fields and try again"; + const errorMessage = String(error) || "Error creating policy. Check required fields and try again"; setSaveError(errorMessage); }, }); - const { mutate: deleteInsuranceMutate } = useMutation({ + const { mutate: deleteInsuranceMutate } = useServerActionMutation({ mutationFn: (insurancePolicyId: string) => deleteInsurancePolicy(insurancePolicyId), onSuccess: () => { setSaveError(null); setEditingInsuranceIndex(null); }, - onError: (_error: Error) => { + onError: () => { setSaveError("An error occurred while deleting the insurance policy."); }, }); diff --git a/frontend/app/business-profile/overview/LocationsCard.tsx b/frontend/app/business-profile/overview/LocationsCard.tsx index b60a9491..b0ca5e46 100644 --- a/frontend/app/business-profile/overview/LocationsCard.tsx +++ b/frontend/app/business-profile/overview/LocationsCard.tsx @@ -2,12 +2,13 @@ import { getCompanyLocations } from "@/api/company"; import { createLocation, deleteLocation, updateLocationAddress } from "@/api/location"; +import { useServerActionQuery } from "@/api/requestHandlers"; import LocationEditor from "@/components/LocationEditor"; import Loading from "@/components/loading"; import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; import { CreateLocationRequest, Location, UpdateLocationRequest } from "@/types/location"; -import { useMutation, useQuery } from "@tanstack/react-query"; +import { useServerActionMutation } from "@/api/requestHandlers"; import { useEffect, useState } from "react"; import { IoAddCircleOutline } from "react-icons/io5"; @@ -22,51 +23,51 @@ export default function LocationsCard({ const [editingLocationIndex, setEditingLocationIndex] = useState(null); const [saveError, setSaveError] = useState(null); - const { data: locationsQuery, isPending: locationPending } = useQuery({ + const { data: locationsQuery, isPending: locationPending } = useServerActionQuery({ queryKey: ["locations"], queryFn: getCompanyLocations, }); - const { mutate: updateLocationsMutate } = useMutation({ + const { mutate: updateLocationsMutate } = useServerActionMutation({ mutationFn: (location: UpdateLocationRequest) => updateLocationAddress(location), onSuccess: () => { setSaveError(null); setEditingLocationIndex(null); }, onError: (error: Error) => { - if (error.message.includes("postalCode")) { + const errorMessage = String(error); + if (errorMessage.includes("postalCode")) { setSaveError("Error updating location. Please check postal code details and try again."); } else { - const errorMessage = error.message || "Error updating location. Check required fields and try again"; - setSaveError(errorMessage); + setSaveError(errorMessage || "Error updating location. Check required fields and try again"); } }, }); - const { mutate: createLocationMutate } = useMutation({ + const { mutate: createLocationMutate } = useServerActionMutation({ mutationFn: (location: CreateLocationRequest) => createLocation(location), onSuccess: () => { setSaveError(null); setEditingLocationIndex(null); }, onError: (error: Error) => { - if (error.message.includes("postalCode")) { + const errorMessage = String(error); + if (errorMessage.includes("postalCode")) { setSaveError("Error creating location. Please check postal code details and try again."); } else { - const errorMessage = error.message || "Error creating location. Check required fields and try again"; - setSaveError(errorMessage); + setSaveError(errorMessage || "Error creating location. Check required fields and try again"); } }, }); - const { mutate: deleteLocationMutate } = useMutation({ + const { mutate: deleteLocationMutate } = useServerActionMutation({ mutationFn: (locationId: string) => deleteLocation(locationId), onSuccess: () => { setSaveError(null); setEditingLocationIndex(null); }, - onError: (_error: Error) => { - const errorMessage = _error.message || "Error removing location. Check required fields and try again"; + onError: (error: Error) => { + const errorMessage = String(error) || "Error removing location. Check required fields and try again"; setSaveError(errorMessage); }, }); diff --git a/frontend/app/business-profile/view-documents/ViewDocuments.tsx b/frontend/app/business-profile/view-documents/ViewDocuments.tsx index 4ca5fdc0..b1d4019b 100644 --- a/frontend/app/business-profile/view-documents/ViewDocuments.tsx +++ b/frontend/app/business-profile/view-documents/ViewDocuments.tsx @@ -19,6 +19,7 @@ import { BusinessDocument, DocumentCategories } from "@/types/documents"; import Loading from "@/components/loading"; import ErrorDisplay from "@/components/ErrorDisplay"; import { ChevronLeft, ChevronRight } from "lucide-react"; +import { isServerActionError } from "@/api/types"; type SortOrder = "asc" | "desc"; @@ -51,9 +52,15 @@ export default function ViewDocuments() { setIsLoadingDocuments(true); const docs = await getAllDocuments(); + if (isServerActionError(docs)) { + console.error("Error getting documents:", docs.error); + setError(true); + return; + } + const docsData = docs.data; // Transform the response into the BusinessDocument type - const transformedDocs: BusinessDocument[] = docs!.map((doc) => { + const transformedDocs: BusinessDocument[] = docsData.map((doc) => { // Access the nested document object const { document, downloadUrl } = doc; @@ -169,10 +176,14 @@ export default function ViewDocuments() { try { //Get presigned upload URL from backend - const { uploadUrl, key, documentId } = await getBusinessDocumentUploadUrl( - selectedFile.name, - selectedFile.type - ); + const uploadUrlResponse = await getBusinessDocumentUploadUrl(selectedFile.name, selectedFile.type); + if (isServerActionError(uploadUrlResponse)) { + console.error("Error getting upload URL:", uploadUrlResponse.error); + alert(`Failed to get upload URL: ${uploadUrlResponse.error}`); + return; + } + const uploadUrlData = uploadUrlResponse.data; + const { uploadUrl, key, documentId } = uploadUrlData; // Upload directly to S3 await uploadToS3(uploadUrl, selectedFile); diff --git a/frontend/app/claims/claim-table/claim-table.tsx b/frontend/app/claims/claim-table/claim-table.tsx index 1334a8cb..029e81cf 100644 --- a/frontend/app/claims/claim-table/claim-table.tsx +++ b/frontend/app/claims/claim-table/claim-table.tsx @@ -5,13 +5,15 @@ import ResultsPerPageSelect from "@/app/expense-tracker/expense-table/ResultsPer import { Button } from "@/components/ui/button"; import { Card, CardAction, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; import { BannerAction } from "@/components/ui/shadcn-io/banner"; -import { useQuery } from "@tanstack/react-query"; import Link from "next/link"; import { useState } from "react"; import { DateRange } from "react-day-picker"; import { IoFilterOutline } from "react-icons/io5"; import { Filters } from "./filters"; import TableContent from "./table-content"; +import { useServerActionQuery } from "@/api/requestHandlers"; +import { UseQueryResult } from "@tanstack/react-query"; +import { GetCompanyClaimResponse } from "@/types/claim"; export default function ClaimTable({ claimInProgress }: { claimInProgress: boolean }) { const [showFilters, setShowFilters] = useState(true); @@ -56,7 +58,7 @@ export default function ClaimTable({ claimInProgress }: { claimInProgress: boole onSearchChange={setSearch} /> )} - + } />
@@ -83,7 +85,7 @@ export function useFetchClaims(filters: { page: number; resultsPerPage: number; }) { - return useQuery({ + return useServerActionQuery({ queryKey: ["company-claims", filters], queryFn: async () => { return getClaims({ diff --git a/frontend/app/claims/claim-table/table-content.tsx b/frontend/app/claims/claim-table/table-content.tsx index 1295c8bd..cbc1fe49 100644 --- a/frontend/app/claims/claim-table/table-content.tsx +++ b/frontend/app/claims/claim-table/table-content.tsx @@ -11,11 +11,13 @@ import { DialogTitle, } from "@/components/ui/dialog"; import { GetCompanyClaimResponse } from "@/types/claim"; -import { useMutation, useQueryClient, UseQueryResult } from "@tanstack/react-query"; +import { useServerActionMutation } from "@/api/requestHandlers"; +import { useQueryClient, UseQueryResult } from "@tanstack/react-query"; import { getCoreRowModel, getExpandedRowModel, useReactTable } from "@tanstack/react-table"; import { PropsWithChildren, useState } from "react"; import { PiDownloadSimpleLight } from "react-icons/pi"; import { TfiTrash } from "react-icons/tfi"; +import { isServerActionSuccess } from "@/api/types"; function IconButton({ onClick, children }: PropsWithChildren<{ onClick: () => void }>) { return ( @@ -32,7 +34,7 @@ export default function TableContent({ claims }: { claims: UseQueryResult(null); const queryClient = useQueryClient(); - const claimDelete = useMutation({ + const claimDelete = useServerActionMutation({ mutationFn: deleteClaim, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["company-claims"] }); @@ -63,8 +65,12 @@ export default function TableContent({ claims }: { claims: UseQueryResult { try { - const { url } = await createClaimPDF(row.original.id); - window.open(url, "_blank"); + const result = await createClaimPDF(row.original.id); + if (isServerActionSuccess(result)) { + window.open(result.data.url, "_blank"); + } else { + console.error(result.error); + } } catch (error) { console.error(error); } diff --git a/frontend/app/claims/declare/DisasterInfoStep.tsx b/frontend/app/claims/declare/DisasterInfoStep.tsx index f230d5ca..bdb6d447 100644 --- a/frontend/app/claims/declare/DisasterInfoStep.tsx +++ b/frontend/app/claims/declare/DisasterInfoStep.tsx @@ -9,7 +9,6 @@ import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; import { GetCompanyLocationsResponse } from "@/types/company"; -import { useQuery } from "@tanstack/react-query"; import React from "react"; import { validateDisasterInfo } from "./utils/validationUtils"; import { CloudCheck, UploadIcon } from "lucide-react"; @@ -17,6 +16,7 @@ import { useModal } from "@/components/ui/modal/useModal"; import { Modal } from "@/components/ui/modal/Modal"; import { UploadDocument } from "./UploadDocument"; import { DisasterInfo } from "@/types/claim"; +import { useServerActionQuery } from "@/api/requestHandlers"; type DisasterInfoStepProps = { disasterInfo: DisasterInfo; @@ -35,7 +35,7 @@ export default function DisasterInfoStep({ }: DisasterInfoStepProps) { const { openModal: openUploadModal, isOpen: isUploadModalOpen, closeModal: closeUploadModal } = useModal({}); - const { data: hasData } = useQuery({ + const { data: hasData } = useServerActionQuery({ queryKey: ["company-has-data"], queryFn: companyHasData, }); diff --git a/frontend/app/claims/declare/ExportStep.tsx b/frontend/app/claims/declare/ExportStep.tsx index 825757ba..088a633d 100644 --- a/frontend/app/claims/declare/ExportStep.tsx +++ b/frontend/app/claims/declare/ExportStep.tsx @@ -3,11 +3,11 @@ import { createClaimPDF } from "@/api/claim"; import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; -import { useMutation } from "@tanstack/react-query"; +import { useServerActionMutation } from "@/api/requestHandlers"; +import { useQueryClient } from "@tanstack/react-query"; import { useRouter } from "next/navigation"; import React, { useState } from "react"; import { Spinner } from "@/components/ui/spinner"; -import { useQueryClient } from "@tanstack/react-query"; type ExportStepProps = { claimId: string | null; handleStepForward: () => void; @@ -20,18 +20,17 @@ export default function ExportStep({ claimId, handleStepForward }: ExportStepPro const [isLoadingPDFDownload, setIsLoadingPDFDownload] = useState(false); const router = useRouter(); - const { mutate: updateBusinessMutate } = useMutation({ + const { mutate: updateBusinessMutate } = useServerActionMutation({ mutationFn: async () => { setIsLoadingPDFDownload(true); - const result = await createClaimPDF(claimId!); - return result.url; + return createClaimPDF(claimId!); }, onError: (error: Error) => { setIsLoadingPDFDownload(false); - setError(error.message); + setError(String(error)); }, - onSuccess: (url: string) => { - window.open(url, "_blank"); + onSuccess: (data) => { + window.open(data.url, "_blank"); queryClient.invalidateQueries({ queryKey: ["banner-data"] }); queryClient.invalidateQueries({ queryKey: ["claim-in-progress"] }); setIsLoadingPDFDownload(false); diff --git a/frontend/app/claims/declare/hooks/useClaimProgress.ts b/frontend/app/claims/declare/hooks/useClaimProgress.ts index 744ae0ac..682903d7 100644 --- a/frontend/app/claims/declare/hooks/useClaimProgress.ts +++ b/frontend/app/claims/declare/hooks/useClaimProgress.ts @@ -28,10 +28,11 @@ import { SaveStatus, UploadClaimRelatedDocumentsRequest, } from "@/types/claim"; -import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { useQueryClient } from "@tanstack/react-query"; import { parseAsNumberLiteral, useQueryState } from "nuqs"; import { useCallback, useEffect, useRef, useState } from "react"; import { cleanExpiredTempData, clearTempData, loadTempData, saveTempData } from "../utils/tempStorage"; +import { useServerActionQuery } from "@/api/requestHandlers"; interface UseClaimProgressReturn { claimId: string | null; @@ -78,8 +79,14 @@ async function rehydratePurchaseSelections( resultsPerPage: 1000, }); + if (!purchasesResponse.success) { + throw new Error(purchasesResponse.error); + } + + const purchases = purchasesResponse.data; + const purchaseToAllLineItems = new Map(); - for (const purchase of purchasesResponse.purchases) { + for (const purchase of purchases.purchases) { purchaseToAllLineItems.set( purchase.id, purchase.lineItems.map((li) => li.id) @@ -122,7 +129,7 @@ export function useClaimProgress( ): UseClaimProgressReturn { const queryClient = useQueryClient(); - const { data: userInfoData } = useQuery({ + const { data: userInfoData } = useServerActionQuery({ queryKey: ["userInfo"], queryFn: getUser, }); @@ -161,48 +168,59 @@ export function useClaimProgress( const loadExistingClaim = async (id: string) => { try { const claim = await getClaimById(id); - setClaimId(claim.id); - setStatus(claim.status); + if ("error" in claim) { + console.error("Error loading claim:", claim.error); + return; + } + const claimData = claim.data; + setClaimId(claimData.id); + setStatus(claimData.status); - if (claim.selfDisaster) { - selfDisasterRef.current = claim.selfDisaster.id; + if (claimData.selfDisaster) { + selfDisasterRef.current = claimData.selfDisaster.id; } // Fetch linked purchase line items to rehydrate selections let purchaseSelections: PurchaseSelections = { fullPurchaseIds: [], partialLineItemIds: [] }; try { const linkedLineItems = await getPurchaseLineItemsFromClaim({ claimId: id }); - if (linkedLineItems && linkedLineItems.length > 0) { - purchaseSelections = await rehydratePurchaseSelections(linkedLineItems); + if (linkedLineItems.success && linkedLineItems && linkedLineItems.data.length > 0) { + purchaseSelections = await rehydratePurchaseSelections(linkedLineItems.data); + } else { + if (!linkedLineItems.success) { + throw new Error(linkedLineItems.error); + } } } catch (error) { console.error("Error rehydrating purchase selections:", error); // Fallback to using purchaseLineItemIds if rehydration fails purchaseSelections = { fullPurchaseIds: [], - partialLineItemIds: claim.purchaseLineItemIds ?? [], + partialLineItemIds: claimData.purchaseLineItemIds ?? [], }; } setDisasterInfoState((prev) => { - const startDate = claim.femaDisaster + const startDate = claimData.femaDisaster ? // TODO: [future] move these to the claim entity rather than disaster (self or fema) - claim.femaDisaster.incidentBeginDate - : claim.selfDisaster?.startDate; + claimData.femaDisaster.incidentBeginDate + : claimData.selfDisaster?.startDate; - const endDate = claim.femaDisaster ? claim.femaDisaster.incidentEndDate : claim.selfDisaster?.endDate; + const endDate = claimData.femaDisaster + ? claimData.femaDisaster.incidentEndDate + : claimData.selfDisaster?.endDate; return { - name: claim.name || "", - description: claim.selfDisaster?.description || "", + name: claimData.name || "", + description: claimData.selfDisaster?.description || "", startDate: startDate ? new Date(startDate) : null, endDate: endDate ? new Date(endDate) : null, location: - claim.claimLocations && claim.claimLocations.length > 0 - ? claim.claimLocations[0].id + claimData.claimLocations && claimData.claimLocations.length > 0 + ? claimData.claimLocations[0].id : prev.location, - ...(claim.femaDisaster - ? { isFema: true, femaDisasterId: claim.femaDisaster.id } + ...(claimData.femaDisaster + ? { isFema: true, femaDisasterId: claimData.femaDisaster.id } : { isFema: false, femaDisasterId: undefined }), additionalDocuments: [], purchaseSelections, @@ -438,14 +456,19 @@ export function useClaimProgress( status: "IN_PROGRESS_DISASTER", name: dataToUse.name, }); + if ("error" in newClaim) { + console.error("Error creating claim:", newClaim.error); + return; + } + const newClaimData = newClaim.data; - setClaimId(newClaim.id); - currentClaimId = newClaim.id; + setClaimId(newClaimData.id); + currentClaimId = newClaimData.id; selfDisasterRef.current = null; - setStatus(newClaim.status); + setStatus(newClaimData.status); await createClaimLocationLink({ - claimId: newClaim.id, + claimId: newClaimData.id, locationAddressId: dataToUse.location, }); } else { @@ -456,27 +479,37 @@ export function useClaimProgress( dataToUse.startDate?.toISOString().split("T")[0] || new Date().toISOString().split("T")[0], endDate: dataToUse.endDate?.toISOString().split("T")[0], }); - selfDisasterRef.current = selfDisaster.id; + if ("error" in selfDisaster) { + console.error("Error creating self disaster:", selfDisaster.error); + return; + } + const selfDisasterData = selfDisaster.data; + selfDisasterRef.current = selfDisasterData.id; const newClaim = await createClaim({ - selfDisasterId: selfDisaster.id, + selfDisasterId: selfDisasterData.id, status: "IN_PROGRESS_DISASTER", name: dataToUse.name, }); + if ("error" in newClaim) { + console.error("Error creating claim:", newClaim.error); + return; + } + const newClaimData = newClaim.data; - setClaimId(newClaim.id); - currentClaimId = newClaim.id; - setStatus(newClaim.status); + setClaimId(newClaimData.id); + currentClaimId = newClaimData.id; + setStatus(newClaimData.status); // Upload additional documents if any if (dataToUse.additionalDocuments?.length > 0) { - await saveAdditionalDocumentsToS3(dataToUse.additionalDocuments, selfDisaster.id); + await saveAdditionalDocumentsToS3(dataToUse.additionalDocuments, selfDisasterData.id); } // Link location if provided if (dataToUse.location) { await createClaimLocationLink({ - claimId: newClaim.id, + claimId: newClaimData.id, locationAddressId: dataToUse.location, }); } diff --git a/frontend/app/claims/declare/hooks/useDownloadClaimPDF.ts b/frontend/app/claims/declare/hooks/useDownloadClaimPDF.ts index 12c8bd5f..2d9fb949 100644 --- a/frontend/app/claims/declare/hooks/useDownloadClaimPDF.ts +++ b/frontend/app/claims/declare/hooks/useDownloadClaimPDF.ts @@ -1,5 +1,6 @@ import { createClaimPDF } from "@/api/claim"; import { useState } from "react"; +import { isServerActionSuccess } from "@/api/types"; export const useDownloadClaimPDF = () => { const [isLoading, setIsLoading] = useState(false); @@ -7,8 +8,12 @@ export const useDownloadClaimPDF = () => { const download = async (claimId: string) => { setIsLoading(true); try { - const url = await createClaimPDF(claimId); - downloadFileSimple(url.url); + const result = await createClaimPDF(claimId); + if (isServerActionSuccess(result)) { + downloadFileSimple(result.data.url); + } else { + throw new Error(result.error); + } } catch (err) { setIsLoading(false); throw err; diff --git a/frontend/app/claims/declare/page.tsx b/frontend/app/claims/declare/page.tsx index 82ba3eff..8ebd9b1f 100644 --- a/frontend/app/claims/declare/page.tsx +++ b/frontend/app/claims/declare/page.tsx @@ -16,7 +16,7 @@ import { isStep, PersonalInfo, } from "@/types/claim"; -import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { useQueryClient } from "@tanstack/react-query"; import { CheckIcon } from "lucide-react"; import { useRouter } from "next/navigation"; import React, { Suspense } from "react"; @@ -29,6 +29,7 @@ import IncidentDateStep from "./IncidentDateStep"; import InsuranceInfoStep from "./InsuranceInfoStep"; import PersonalInfoStep from "./PersonalInfoStep"; import StartStep from "./StartStep"; +import { useServerActionQuery } from "@/api/requestHandlers"; /** * The steps that are displayed in the progress bar @@ -43,17 +44,17 @@ const progressSteps = [ function DeclareDisasterContent() { const router = useRouter(); - const { data: businessInfoData, isSuccess: businessInfoSuccess } = useQuery({ + const { data: businessInfoData, isSuccess: businessInfoSuccess } = useServerActionQuery({ queryKey: ["businessInfo"], queryFn: getCompany, }); - const { data: userInfoData, isSuccess: userInfoSuccess } = useQuery({ + const { data: userInfoData, isSuccess: userInfoSuccess } = useServerActionQuery({ queryKey: ["userInfo"], queryFn: getUser, }); - const { data: companyLocations } = useQuery({ + const { data: companyLocations } = useServerActionQuery({ queryKey: ["companyLocations"], queryFn: getCompanyLocations, }); diff --git a/frontend/app/claims/page.tsx b/frontend/app/claims/page.tsx index 03d6cb4a..401601be 100644 --- a/frontend/app/claims/page.tsx +++ b/frontend/app/claims/page.tsx @@ -3,11 +3,11 @@ import { getClaimInProgress } from "@/api/company"; import { ClaimInProgress } from "@/components/claims/ClaimInProgress"; import { ClaimInProgressIndexMapping } from "@/types/claim"; -import { useQuery } from "@tanstack/react-query"; import ClaimTable from "./claim-table/claim-table"; +import { useServerActionQuery } from "@/api/requestHandlers"; export default function Claims() { - const { data: claimInProgress } = useQuery({ + const { data: claimInProgress } = useServerActionQuery({ queryKey: ["claim-in-progress"], queryFn: getClaimInProgress, }); diff --git a/frontend/app/expense-tracker/expense-table/expense-table.tsx b/frontend/app/expense-tracker/expense-table/expense-table.tsx index 0627afdb..cd66f7aa 100644 --- a/frontend/app/expense-tracker/expense-table/expense-table.tsx +++ b/frontend/app/expense-tracker/expense-table/expense-table.tsx @@ -4,8 +4,7 @@ import { LargeLoading } from "@/components/loading"; import { Button } from "@/components/ui/button"; import { Card, CardAction, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; import { PurchaseSelections } from "@/types/claim"; -import { FilteredPurchases, PurchaseLineItemType, PurchaseWithLineItems } from "@/types/purchase"; -import { useQuery } from "@tanstack/react-query"; +import { FilteredPurchases, PurchaseLineItemType, PurchasesWithCount, PurchaseWithLineItems } from "@/types/purchase"; import { FileUp, Loader2 } from "lucide-react"; import { useCallback, useEffect, useState } from "react"; import { FaExclamation } from "react-icons/fa6"; @@ -18,6 +17,9 @@ import PaginationControls from "./PaginationControls"; import ResultsPerPageSelect from "./ResultsPerPageSelect"; import ExpenseSideView from "./side-view"; import TableContent from "./table-content"; +import { useServerActionQuery } from "@/api/requestHandlers"; +import { ServerActionResult } from "@/api/types"; +import { UseQueryResult } from "@tanstack/react-query"; interface ExpenseTableConfig { title: string; @@ -86,7 +88,7 @@ export default function ExpenseTable({ updateFilter("categories")([]); }; - const purchases = useFetchPurchases(filters); + const purchases = useFetchPurchases(filters) as UseQueryResult; const categories = useFetchAllCategories(); @@ -204,7 +206,7 @@ export default function ExpenseTable({ } export function useFetchPurchases(filters: FilteredPurchases) { - return useQuery({ + return useServerActionQuery({ queryKey: ["purchases-for-company", filters], queryFn: async () => { return fetchPurchases(filters); @@ -214,9 +216,9 @@ export function useFetchPurchases(filters: FilteredPurchases) { } export function useFetchAllCategories() { - return useQuery({ + return useServerActionQuery({ queryKey: ["categories-for-purchases"], - queryFn: async (): Promise => { + queryFn: async (): Promise> => { return fetchAllCategories(); }, }); diff --git a/frontend/app/expense-tracker/expense-table/export.ts b/frontend/app/expense-tracker/expense-table/export.ts index 9b5b7e57..30bfa857 100644 --- a/frontend/app/expense-tracker/expense-table/export.ts +++ b/frontend/app/expense-tracker/expense-table/export.ts @@ -2,6 +2,7 @@ import { FilteredPurchases, PurchaseWithLineItems } from "@/types/purchase"; import Papa from "papaparse"; import { DISASTER_TYPE_LABELS } from "@/types/disaster"; import { getAllPurchasesForExport } from "@/api/purchase"; +import { isServerActionSuccess } from "@/api/types"; const handleCSVCreation = (purchases: PurchaseWithLineItems[]) => { const data = purchases.flatMap((p) => { @@ -47,8 +48,12 @@ export const handleExportClick = async ( ) => { if (total) { setIsExporting(true); - const allPurchases = await getAllPurchasesForExport(filters, total); - handleCSVCreation(allPurchases); + const result = await getAllPurchasesForExport(filters, total); + if (isServerActionSuccess(result)) { + handleCSVCreation(result.data); + } else { + console.error(result.error); + } setIsExporting(false); } }; diff --git a/frontend/app/expense-tracker/expense-table/table-content.tsx b/frontend/app/expense-tracker/expense-table/table-content.tsx index 2ea2ccbd..bcf7dada 100644 --- a/frontend/app/expense-tracker/expense-table/table-content.tsx +++ b/frontend/app/expense-tracker/expense-table/table-content.tsx @@ -5,7 +5,8 @@ import { TABLE_HEADER_HEIGHT, TABLE_ROW_HEIGHT } from "@/components/ui/table"; import { cn } from "@/lib/utils"; import { PurchaseSelections } from "@/types/claim"; import { DisasterType, FilteredPurchases, PurchasesWithCount, PurchaseWithLineItems } from "@/types/purchase"; -import { useMutation, useQueryClient, UseQueryResult } from "@tanstack/react-query"; +import { useServerActionMutation } from "@/api/requestHandlers"; +import { useQueryClient, UseQueryResult } from "@tanstack/react-query"; import { getCoreRowModel, getExpandedRowModel, Row, useReactTable } from "@tanstack/react-table"; import { useMemo } from "react"; import { updateCategory, updateType } from "../../../api/purchase"; @@ -147,7 +148,7 @@ export default function TableContent({ } }; const queryClient = useQueryClient(); - const categoryMutation = useMutation({ + const categoryMutation = useServerActionMutation({ mutationFn: ({ lineItemIds, category, @@ -164,7 +165,7 @@ export default function TableContent({ queryClient.invalidateQueries({ queryKey: ["purchases-for-company"] }); }, }); - const typeMutation = useMutation({ + const typeMutation = useServerActionMutation({ mutationFn: ({ lineItemIds, type }: { type: DisasterType; lineItemIds: string[] }) => { return updateType(type, lineItemIds); }, diff --git a/frontend/app/expense-tracker/page.tsx b/frontend/app/expense-tracker/page.tsx index 795054a3..de6af449 100644 --- a/frontend/app/expense-tracker/page.tsx +++ b/frontend/app/expense-tracker/page.tsx @@ -6,7 +6,6 @@ import { Button } from "@/components/ui/button"; import RevenueAndExpenses, { RevenueAndExpensesNoData } from "@/components/dashboard/RevenueAndExpenses"; import NetDisasterExpense, { NetDisasterExpenseNoData } from "@/components/dashboard/NetDisasterExpenses"; import { getDashboardBannerData } from "@/api/dashboard"; -import { useQuery } from "@tanstack/react-query"; import ReviewExpenses from "./ReviewExpenses"; import { PurchaseLineItemType } from "@/types/purchase"; import { companyHasData, getCompany } from "@/api/company"; @@ -14,6 +13,7 @@ import { GoSync } from "react-icons/go"; import { FiUpload } from "react-icons/fi"; import NoDataPopupWrapper from "@/components/dashboard/NoDataPopupWrapper"; import { Spinner } from "@/components/ui/spinner"; +import { useServerActionQuery } from "@/api/requestHandlers"; export default function ExpenseTracker() { const [importModalOpen, setImportModalOpen] = useState(false); @@ -21,17 +21,17 @@ export default function ExpenseTracker() { const onOpenImportModal = () => setImportModalOpen(true); const onCloseImportModal = () => setImportModalOpen(false); - const { data: hasData, isPending: hasDataLoading } = useQuery({ + const { data: hasData, isPending: hasDataLoading } = useServerActionQuery({ queryKey: ["company-has-data"], queryFn: companyHasData, }); - const { data: companyLastUpdate } = useQuery({ + const { data: companyLastUpdate } = useServerActionQuery({ queryKey: ["company-last-update"], queryFn: getCompany, }); - const { data: bannerData } = useQuery({ + const { data: bannerData } = useServerActionQuery({ queryKey: ["banner-data"], queryFn: getDashboardBannerData, }); diff --git a/frontend/app/expense-tracker/transaction-import-csv/hooks/useParseCSVForTransaction.ts b/frontend/app/expense-tracker/transaction-import-csv/hooks/useParseCSVForTransaction.ts index bb3949cc..4d32b5fc 100644 --- a/frontend/app/expense-tracker/transaction-import-csv/hooks/useParseCSVForTransaction.ts +++ b/frontend/app/expense-tracker/transaction-import-csv/hooks/useParseCSVForTransaction.ts @@ -8,6 +8,7 @@ import { createPurchaseForCompany } from "@/api/purchase"; import { parseAndSaveTransaction } from "./helpers/parseAndSaveTransaction"; import { collectValidationErrors } from "./helpers/collectValidationErrors"; import { useColumnOrderSelection } from "./useColumnOrderSelection"; +import { isServerActionSuccess } from "@/api/types"; export const useParseCSVForTransaction = () => { const [selectedFile, setSelectedFile] = useState(); @@ -110,16 +111,24 @@ export const useParseCSVForTransaction = () => { }, ], }); - parentTransactionId = purchaseResult[0]?.id || null; + if (isServerActionSuccess(purchaseResult)) { + parentTransactionId = purchaseResult.data[0]?.id || null; + } else { + throw new Error(purchaseResult.error); + } } else if (transactionType === "invoice") { - const purchaseResult = await createInvoice({ + const invoiceResult = await createInvoice({ items: [ { totalAmountCents: 0, }, ], }); - parentTransactionId = purchaseResult[0]?.id || null; + if (isServerActionSuccess(invoiceResult)) { + parentTransactionId = invoiceResult.data[0]?.id || null; + } else { + throw new Error(invoiceResult.error); + } } if (!parentTransactionId) { diff --git a/frontend/app/location-based-risk/hooks/useFEMARiskScore.ts b/frontend/app/location-based-risk/hooks/useFEMARiskScore.ts index 1eefdd95..45d958ee 100644 --- a/frontend/app/location-based-risk/hooks/useFEMARiskScore.ts +++ b/frontend/app/location-based-risk/hooks/useFEMARiskScore.ts @@ -1,6 +1,7 @@ import { useState, useEffect, useMemo } from "react"; import { getFemaRiskIndexData, refreshFemaRiskIndexData } from "@/api/fema-risk-index"; import { FemaRisKIndexCountiesFemaDisaster } from "@/types/fema-risk-index"; +import { isServerActionSuccess } from "@/api/types"; export function useFEMARiskScore() { const [data, setData] = useState([]); @@ -10,12 +11,16 @@ export function useFEMARiskScore() { let retries = 0; const fetchRiskData = async () => { setLoading(true); - const res = await getFemaRiskIndexData(); - setData(res); - if (res.length === 0 && retries < 5) { - await refreshFemaRiskIndexData(); - retries++; - fetchRiskData(); + const result = await getFemaRiskIndexData(); + if (isServerActionSuccess(result)) { + setData(result.data); + if (result.data.length === 0 && retries < 5) { + await refreshFemaRiskIndexData(); + retries++; + fetchRiskData(); + } + } else { + console.error(result.error); } setLoading(false); }; diff --git a/frontend/app/location-based-risk/hooks/useSelectedLocation.ts b/frontend/app/location-based-risk/hooks/useSelectedLocation.ts index 80216e90..2601a8a4 100644 --- a/frontend/app/location-based-risk/hooks/useSelectedLocation.ts +++ b/frontend/app/location-based-risk/hooks/useSelectedLocation.ts @@ -1,6 +1,7 @@ import { getCompanyLocations } from "@/api/company"; import { GetCompanyLocationsResponse } from "@/types/company"; import { useEffect, useState } from "react"; +import { isServerActionSuccess } from "@/api/types"; export const useSelectedLocation = () => { const [selectedLocation, setSelectedLocation] = useState(); @@ -9,8 +10,12 @@ export const useSelectedLocation = () => { useEffect(() => { const fetchAndSaveLocations = async () => { const result = await getCompanyLocations(); - setAvailableLocations(result); - setSelectedLocation(result[0]); + if (isServerActionSuccess(result)) { + setAvailableLocations(result.data); + setSelectedLocation(result.data[0]); + } else { + console.error(result.error); + } }; fetchAndSaveLocations(); }, []); diff --git a/frontend/app/notifications/noification-page.tsx b/frontend/app/notifications/noification-page.tsx index 51e30155..46169eb0 100644 --- a/frontend/app/notifications/noification-page.tsx +++ b/frontend/app/notifications/noification-page.tsx @@ -3,16 +3,15 @@ import { getNotifications } from "@/api/notifications"; import { LargeLoading } from "@/components/loading"; import { NOTIFICATION_LIMIT } from "@/types/constants"; import { GetNotificationsResponse } from "@/types/notifications"; -import { InfiniteData, useInfiniteQuery } from "@tanstack/react-query"; +import { useServerActionInfiniteQuery } from "@/api/requestHandlers"; import { useEffect, useRef } from "react"; import Notification, { LoadingNotification } from "./notification"; export default function NotificationPage() { const observerTarget = useRef(null); - const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } = useInfiniteQuery< + const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } = useServerActionInfiniteQuery< GetNotificationsResponse, Error, - InfiniteData, string[], number >({ diff --git a/frontend/app/notifications/notification.tsx b/frontend/app/notifications/notification.tsx index f56dd2c5..26a6ac31 100644 --- a/frontend/app/notifications/notification.tsx +++ b/frontend/app/notifications/notification.tsx @@ -3,7 +3,7 @@ import Loading from "@/components/loading"; import { Button } from "@/components/ui/button"; import { Notification as NotificationType } from "@/types/notifications"; import { dateFormatter, getDeclarationTypeMeanings } from "@/utils/formatting"; -import { useMutation } from "@tanstack/react-query"; +import { useServerActionMutation } from "@/api/requestHandlers"; import { useState } from "react"; import { RiMore2Fill } from "react-icons/ri"; import formatDescription from "./utils"; @@ -14,7 +14,7 @@ interface NotificationProps { export default function Notification({ notification }: NotificationProps) { const [error, setError] = useState(false); const [title, setTitle] = useState(notification.notificationStatus); - const { mutate } = useMutation({ + const { mutate } = useServerActionMutation({ mutationFn: () => updateNotificationStatus(notification.id, title == "read" ? "unread" : "read"), onError: () => { setError(false); diff --git a/frontend/app/profile/linked-accounts.tsx b/frontend/app/profile/linked-accounts.tsx index d127286e..ec54cb0b 100644 --- a/frontend/app/profile/linked-accounts.tsx +++ b/frontend/app/profile/linked-accounts.tsx @@ -4,14 +4,15 @@ import { QuickBooksIcon } from "@/icons/quickbooks"; import { CirclePlusIcon } from "lucide-react"; import { ProfileSettingsCard } from "./common"; import { redirectToQuickbooks } from "@/api/quickbooks"; +import { isServerActionSuccess } from "@/api/types"; export function LinkedAccountsSettings() { const quickbooksAuth = async () => { - const url = await redirectToQuickbooks(); - if (url) { - window.location.href = url; + const result = await redirectToQuickbooks(); + if (isServerActionSuccess(result)) { + window.location.href = result.data; } else { - console.error("Failed to retrieve QuickBooks URL"); + console.error(result.error); } }; diff --git a/frontend/app/profile/notifications.tsx b/frontend/app/profile/notifications.tsx index e3a11d7e..0d17c7de 100644 --- a/frontend/app/profile/notifications.tsx +++ b/frontend/app/profile/notifications.tsx @@ -1,8 +1,9 @@ "use client"; import { getUserPreferences, updateUserPreferences } from "@/api/preferences"; +import { useServerActionMutation, useServerActionQuery } from "@/api/requestHandlers"; import { Switch } from "@/components/ui/switch"; -import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { useQueryClient } from "@tanstack/react-query"; import { BellDotIcon } from "lucide-react"; import { useEffect, useState } from "react"; import { ProfileSettingsCard } from "./common"; @@ -10,7 +11,7 @@ import { ProfileSettingsCard } from "./common"; export function NotificationSettings() { const [emailNotifsEnabled, setEmailNotifsEnabled] = useState(false); - const { data: userPreferencesData } = useQuery({ + const { data: userPreferencesData } = useServerActionQuery({ queryKey: ["userPreferences"], queryFn: getUserPreferences, }); @@ -23,7 +24,7 @@ export function NotificationSettings() { const queryClient = useQueryClient(); - const { mutate: updateUserPreferencesMutation } = useMutation({ + const { mutate: updateUserPreferencesMutation } = useServerActionMutation({ mutationFn: updateUserPreferences, onSuccess: (data) => { setEmailNotifsEnabled(data.emailEnabled); diff --git a/frontend/app/profile/personal-info.tsx b/frontend/app/profile/personal-info.tsx index fadf0439..a4ecc29d 100644 --- a/frontend/app/profile/personal-info.tsx +++ b/frontend/app/profile/personal-info.tsx @@ -4,11 +4,13 @@ import { getUser, updateUserInfo } from "@/api/user"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { cn } from "@/lib/utils"; -import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { useQueryClient } from "@tanstack/react-query"; +import { useServerActionMutation } from "@/api/requestHandlers"; import { CheckIcon, SquarePenIcon } from "lucide-react"; import { useEffect, useState } from "react"; import { ProfileSettingsCard } from "./common"; import { HiOutlineX } from "react-icons/hi"; +import { useServerActionQuery } from "@/api/requestHandlers"; export function PersonalInfoSettings() { const queryClient = useQueryClient(); @@ -22,7 +24,7 @@ export function PersonalInfoSettings() { phoneNumber: "", }); - const { data: userInfoData } = useQuery({ + const { data: userInfoData } = useServerActionQuery({ queryKey: ["userInfo"], queryFn: getUser, }); @@ -38,7 +40,7 @@ export function PersonalInfoSettings() { } }, [userInfoData]); - const { mutate: updateInfoMutation } = useMutation({ + const { mutate: updateInfoMutation } = useServerActionMutation({ mutationFn: updateUserInfo, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["userInfo"] }); diff --git a/frontend/app/signup/company.tsx b/frontend/app/signup/company.tsx index da23facb..a938038f 100644 --- a/frontend/app/signup/company.tsx +++ b/frontend/app/signup/company.tsx @@ -8,7 +8,7 @@ import { Label } from "@/components/ui/label"; import { type Company, CompanyTypesEnum, CreateCompanyRequest, businessTypes } from "@/types/company"; import { CreateLocationBulkRequest, CreateLocationRequest } from "@/types/location"; import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@/components/ui/select"; -import { useMutation } from "@tanstack/react-query"; +import { useServerActionMutation } from "@/api/requestHandlers"; import React, { useState } from "react"; import { Spinner } from "@/components/ui/spinner"; import { Card } from "@/components/ui/card"; @@ -67,13 +67,13 @@ export default function Company({ handleNext: incrementNext }: CompanyInfoProps) setEditingLocationIndex(null); }; - const { isPending: isLocationPending, mutate: mutateLocation } = useMutation({ + const { isPending: isLocationPending, mutate: mutateLocation } = useServerActionMutation({ mutationFn: (payload: CreateLocationBulkRequest) => createLocationBulk(payload), onSuccess: () => { incrementNext(); }, - onError: (_error: Error) => { - const errorMessage = _error.message || "Error creating locations. Check required fields and try again"; + onError: (error: Error) => { + const errorMessage = String(error) || "Error creating locations. Check required fields and try again"; setLocError(errorMessage); }, }); @@ -82,7 +82,7 @@ export default function Company({ handleNext: incrementNext }: CompanyInfoProps) isPending, error: companyMutateError, mutate, - } = useMutation({ + } = useServerActionMutation({ mutationFn: (payload: CreateCompanyRequest) => createCompany(payload), onError: (error: Error) => { console.error("Error creating company:", error); diff --git a/frontend/app/signup/insurance.tsx b/frontend/app/signup/insurance.tsx index 87f71a12..3877964d 100644 --- a/frontend/app/signup/insurance.tsx +++ b/frontend/app/signup/insurance.tsx @@ -4,7 +4,7 @@ import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; import { Spinner } from "@/components/ui/spinner"; import { CreateInsurancePolicyBulkRequest, CreateInsurancePolicyRequest } from "@/types/insurance-policy"; -import { useMutation } from "@tanstack/react-query"; +import { useServerActionMutation } from "@/api/requestHandlers"; import React from "react"; import { IoAddCircleOutline } from "react-icons/io5"; @@ -24,13 +24,13 @@ export default function Insurance({ handleNext: incrementProgress }: InsuranceIn }, ]); - const { mutate: bulkCreateInsurance, isPending: createInsurancePending } = useMutation({ + const { mutate: bulkCreateInsurance, isPending: createInsurancePending } = useServerActionMutation({ mutationFn: () => createInsurancePolicyBulk(insurancePayload), onSuccess: () => { incrementProgress(); }, onError: (error: Error) => { - setError(error.message || "An error occurred while creating insurance policies."); + setError(String(error) || "An error occurred while creating insurance policies."); }, }); diff --git a/frontend/app/signup/quickbooks.tsx b/frontend/app/signup/quickbooks.tsx index 7493fa58..4c06b32e 100644 --- a/frontend/app/signup/quickbooks.tsx +++ b/frontend/app/signup/quickbooks.tsx @@ -2,6 +2,7 @@ import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; import { GoSync } from "react-icons/go"; import { redirectToQuickbooks } from "@/api/quickbooks"; +import { isServerActionSuccess } from "@/api/types"; interface QuickbooksInfoProps { handleNext: () => void; @@ -9,11 +10,11 @@ interface QuickbooksInfoProps { export default function Quickbooks({ handleNext }: QuickbooksInfoProps) { const quickbooksAuth = async () => { - const url = await redirectToQuickbooks(); - if (url) { - window.location.href = url; + const result = await redirectToQuickbooks(); + if (isServerActionSuccess(result)) { + window.location.href = result.data; } else { - console.error("Failed to retrieve QuickBooks URL"); + console.error(result.error); } }; diff --git a/frontend/app/signup/user.tsx b/frontend/app/signup/user.tsx index 835958a6..bb7de13f 100644 --- a/frontend/app/signup/user.tsx +++ b/frontend/app/signup/user.tsx @@ -6,7 +6,7 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Spinner } from "@/components/ui/spinner"; import { CreateUserRequest } from "@/types/user"; -import { useMutation } from "@tanstack/react-query"; +import { useServerActionMutation } from "@/api/requestHandlers"; import { useState } from "react"; interface UserInfoProps { @@ -20,13 +20,13 @@ export default function UserInfoPage({ email, handleNext }: UserInfoProps) { email: email, phoneNumber: "", }); - const { isPending, mutate } = useMutation({ + const { isPending, mutate } = useServerActionMutation({ mutationFn: (payload: CreateUserRequest) => createUser(payload), onSuccess: () => { handleNext(); }, onError: (error: Error) => { - setFieldError(error.message || "An error occurred while creating the user."); + setFieldError(String(error) || "An error occurred while creating the user."); }, }); diff --git a/frontend/components/dashboard/DashboardBody.tsx b/frontend/components/dashboard/DashboardBody.tsx index e8a2b50b..a4c2d810 100644 --- a/frontend/components/dashboard/DashboardBody.tsx +++ b/frontend/components/dashboard/DashboardBody.tsx @@ -7,16 +7,16 @@ import NetDisasterExpense, { NetDisasterExpenseNoData } from "./NetDisasterExpen import LocationRisk from "./LocationRisk"; import { companyHasData } from "@/api/company"; import NoDataPopupWrapper from "./NoDataPopupWrapper"; -import { useQuery } from "@tanstack/react-query"; import { Spinner } from "../ui/spinner"; +import { useServerActionQuery } from "@/api/requestHandlers"; export default function DashboardBody() { - const { data: bannerData } = useQuery({ + const { data: bannerData } = useServerActionQuery({ queryKey: ["banner-data"], queryFn: getDashboardBannerData, }); - const { data: hasData, isLoading: hasDataLoading } = useQuery({ + const { data: hasData, isLoading: hasDataLoading } = useServerActionQuery({ queryKey: ["company-has-data"], queryFn: companyHasData, }); diff --git a/frontend/components/dashboard/NetDisasterExpenses.tsx b/frontend/components/dashboard/NetDisasterExpenses.tsx index 6ad67bc9..ea37e3e3 100644 --- a/frontend/components/dashboard/NetDisasterExpenses.tsx +++ b/frontend/components/dashboard/NetDisasterExpenses.tsx @@ -3,12 +3,12 @@ import { getPurchaseLineItemsFromClaim } from "@/api/claim"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader } from "@/components/ui/card"; import { BannerData } from "@/types/user"; -import { useQuery } from "@tanstack/react-query"; import Link from "next/link"; import { BiMessageEdit } from "react-icons/bi"; import { LargeLoading } from "../loading"; import { FaExclamation } from "react-icons/fa6"; import ErrorDisplay from "../ErrorDisplay"; +import { useServerActionQuery } from "@/api/requestHandlers"; type Props = { bannerData: BannerData; @@ -62,7 +62,7 @@ export default function NetDisasterExpense({ bannerData, onDashboard = true, han claimId = bannerData.claim.id; } - const purchaseLineItems = useQuery({ + const purchaseLineItems = useServerActionQuery({ queryKey: ["purchaseLineItems-for-company", claimId], queryFn: () => getPurchaseLineItemsFromClaim({ claimId }), }); diff --git a/frontend/components/dashboard/NoDataPopup.tsx b/frontend/components/dashboard/NoDataPopup.tsx index 5152e60b..5a8d7605 100644 --- a/frontend/components/dashboard/NoDataPopup.tsx +++ b/frontend/components/dashboard/NoDataPopup.tsx @@ -4,6 +4,7 @@ import { redirectToQuickbooks } from "@/api/quickbooks"; import { Button } from "@/components/ui/button"; import Link from "next/link"; import { GoSync } from "react-icons/go"; +import { isServerActionSuccess } from "@/api/types"; type Props = { isOpen: boolean; @@ -14,11 +15,11 @@ export default function NoDataPopup({ isOpen, onClose }: Props) { if (!isOpen) return <>; const quickbooksAuth = async () => { - const url = await redirectToQuickbooks(); - if (url) { - window.open(url, "_blank"); + const result = await redirectToQuickbooks(); + if (isServerActionSuccess(result)) { + window.open(result.data, "_blank"); } else { - console.error("Failed to retrieve QuickBooks URL"); + console.error(result.error); } }; diff --git a/frontend/components/dashboard/RevenueAndExpenses.tsx b/frontend/components/dashboard/RevenueAndExpenses.tsx index 3ee48dc9..d521ac0a 100644 --- a/frontend/components/dashboard/RevenueAndExpenses.tsx +++ b/frontend/components/dashboard/RevenueAndExpenses.tsx @@ -3,7 +3,7 @@ import { sumInvoicesByCompanyAndDateRange } from "@/api/invoice"; import { sumPurchasesByCompanyAndDateRange } from "@/api/purchase"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardTitle } from "@/components/ui/card"; -import { useQueries } from "@tanstack/react-query"; +import { useServerActionQueries } from "@/api/requestHandlers"; import { Bar, BarChart, CartesianGrid, XAxis } from "recharts"; import { ChartContainer, ChartTooltipContent, ChartTooltip, ChartConfig } from "@/components/ui/chart"; import Link from "next/link"; @@ -50,7 +50,7 @@ export default function RevenueAndExpenses({ onDashboard = true }: { onDashboard const [months, setMonths] = useState(6); const monthDates = Array.from({ length: months }, (_, i) => getMonth(i)); - const revenueQueries = useQueries({ + const revenueQueries = useServerActionQueries({ queries: monthDates.map((date) => { const year = date.getFullYear(); const month = date.getMonth(); @@ -64,7 +64,7 @@ export default function RevenueAndExpenses({ onDashboard = true }: { onDashboard }), }); - const expensesQueries = useQueries({ + const expensesQueries = useServerActionQueries({ queries: monthDates.map((date) => { const year = date.getFullYear(); const month = date.getMonth(); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c13bce29..2bbf40a3 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,31 +10,48 @@ "dependencies": { "@radix-ui/react-alert-dialog": "^1.1.15", "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-select": "^2.2.6", - "@radix-ui/react-separator": "^1.1.7", - "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-switch": "^1.2.6", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-toggle": "^1.1.10", + "@radix-ui/react-toggle-group": "^1.1.11", + "@radix-ui/react-use-controllable-state": "^1.2.2", "@supabase/ssr": "^0.7.0", "@supabase/supabase-js": "^2.57.4", "@tanstack/react-query": "^5.90.2", "@tanstack/react-table": "^8.21.3", + "@types/leaflet": "^1.9.21", + "@types/papaparse": "^5.5.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cmdk": "^1.1.1", "date-fns": "^4.1.0", + "dayjs": "^1.11.19", "dotenv": "^17.2.3", "hono": "^4.9.10", + "leaflet": "^1.9.4", "lucide-react": "^0.552.0", "next": "15.5.3", + "nuqs": "^2.8.2", "openapi-fetch": "^0.14.1", "openapi-typescript": "^7.9.1", + "pako": "^2.1.0", + "papaparse": "^5.5.3", "prettier": "^3.6.2", "react": "19.1.0", "react-day-picker": "^9.11.1", "react-dom": "19.1.0", "react-dropzone": "^14.3.8", "react-icons": "^5.5.0", + "react-switch": "^7.1.0", + "recharts": "2.15.4", "tailwind-merge": "^3.3.1", "vaul": "^1.1.2", "zod": "^4.1.12" @@ -44,6 +61,7 @@ "@tailwindcss/postcss": "^4", "@tanstack/eslint-plugin-query": "^5.91.0", "@types/node": "^20", + "@types/pako": "^2.0.4", "@types/react": "^19", "@types/react-dom": "^19", "eslint": "^9", @@ -83,6 +101,15 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@date-fns/tz": { "version": "1.4.1", "license": "MIT" @@ -390,6 +417,21 @@ "fast-glob": "3.3.1" } }, + "node_modules/@next/swc-darwin-arm64": { + "version": "15.5.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.3.tgz", + "integrity": "sha512-nzbHQo69+au9wJkGKTU9lP7PXv0d1J5ljFpvb+LnEomLtSbJkbZyEs6sbF3plQmiOB2l9OBtN2tNSvCH1nQ9Jg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@next/swc-darwin-x64": { "version": "15.5.3", "cpu": [ @@ -404,6 +446,96 @@ "node": ">= 10" } }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "15.5.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.3.tgz", + "integrity": "sha512-+m7pfIs0/yvgVu26ieaKrifV8C8yiLe7jVp9SpcIzg7XmyyNE7toC1fy5IOQozmr6kWl/JONC51osih2RyoXRw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "15.5.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.3.tgz", + "integrity": "sha512-u3PEIzuguSenoZviZJahNLgCexGFhso5mxWCrrIMdvpZn6lkME5vc/ADZG8UUk5K1uWRy4hqSFECrON6UKQBbQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "15.5.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.3.tgz", + "integrity": "sha512-lDtOOScYDZxI2BENN9m0pfVPJDSuUkAD1YXSvlJF0DKwZt0WlA7T7o3wrcEr4Q+iHYGzEaVuZcsIbCps4K27sA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "15.5.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.3.tgz", + "integrity": "sha512-9vWVUnsx9PrY2NwdVRJ4dUURAQ8Su0sLRPqcCCxtX5zIQUBES12eRVHq6b70bbfaVaxIDGJN2afHui0eDm+cLg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "15.5.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.3.tgz", + "integrity": "sha512-1CU20FZzY9LFQigRi6jM45oJMU3KziA5/sSG+dXeVaTm661snQP6xu3ykGxxwU5sLG3sh14teO/IOEPVsQMRfA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "15.5.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.3.tgz", + "integrity": "sha512-JMoLAq3n3y5tKXPQwCK5c+6tmwkuFDa2XAxz8Wm4+IVthdBZdZGh+lmiLUHg9f9IDwIQpUjp+ysd6OkYTyZRZw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "dev": true, @@ -478,6 +610,24 @@ } } }, + "node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-arrow": { "version": "1.1.7", "license": "MIT", @@ -527,6 +677,36 @@ } } }, + "node_modules/@radix-ui/react-collapsible": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz", + "integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-collection": { "version": "1.1.7", "license": "MIT", @@ -551,6 +731,24 @@ } } }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.1.2", "license": "MIT", @@ -611,6 +809,24 @@ } } }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-direction": { "version": "1.1.1", "license": "MIT", @@ -649,6 +865,35 @@ } } }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", + "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-focus-guards": { "version": "1.1.3", "license": "MIT", @@ -701,11 +946,266 @@ } } }, - "node_modules/@radix-ui/react-label": { - "version": "2.1.7", + "node_modules/@radix-ui/react-label": { + "version": "2.1.7", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", + "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.15", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.8", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.3" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", @@ -722,23 +1222,29 @@ } } }, - "node_modules/@radix-ui/react-popover": { - "version": "1.1.15", + "node_modules/@radix-ui/react-select": { + "version": "2.2.6", "license": "MIT", "dependencies": { + "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, @@ -757,42 +1263,31 @@ } } }, - "node_modules/@radix-ui/react-popper": { - "version": "1.2.8", + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", "license": "MIT", "dependencies": { - "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-layout-effect": "1.1.1", - "@radix-ui/react-use-rect": "1.1.1", - "@radix-ui/react-use-size": "1.1.1", - "@radix-ui/rect": "1.1.1" + "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { "optional": true - }, - "@types/react-dom": { - "optional": true } } }, - "node_modules/@radix-ui/react-portal": { - "version": "1.1.9", + "node_modules/@radix-ui/react-separator": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.8.tgz", + "integrity": "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-layout-effect": "1.1.1" + "@radix-ui/react-primitive": "2.1.4" }, "peerDependencies": { "@types/react": "*", @@ -809,12 +1304,13 @@ } } }, - "node_modules/@radix-ui/react-presence": { - "version": "1.1.5", + "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-use-layout-effect": "1.1.1" + "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", @@ -831,11 +1327,37 @@ } } }, - "node_modules/@radix-ui/react-primitive": { - "version": "2.1.3", + "node_modules/@radix-ui/react-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", "license": "MIT", "dependencies": { - "@radix-ui/react-slot": "1.2.3" + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", + "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -852,31 +1374,20 @@ } } }, - "node_modules/@radix-ui/react-select": { - "version": "2.2.6", + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", + "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", "license": "MIT", "dependencies": { - "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-layout-effect": "1.1.1", - "@radix-ui/react-use-previous": "1.1.1", - "@radix-ui/react-visually-hidden": "1.2.3", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", @@ -893,11 +1404,15 @@ } } }, - "node_modules/@radix-ui/react-separator": { - "version": "1.1.7", + "node_modules/@radix-ui/react-toggle": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", + "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.3" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", @@ -914,19 +1429,32 @@ } } }, - "node_modules/@radix-ui/react-slot": { - "version": "1.2.3", + "node_modules/@radix-ui/react-toggle-group": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.11.tgz", + "integrity": "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-toggle": "1.1.10", + "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { "optional": true + }, + "@types/react-dom": { + "optional": true } } }, @@ -1143,6 +1671,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "license": "MIT" + }, "node_modules/@supabase/auth-js": { "version": "2.75.0", "license": "MIT", @@ -1354,11 +1888,80 @@ "url": "https://github.com/sponsors/tannerlinsley" } }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "dev": true, "license": "MIT" }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "dev": true, @@ -1369,6 +1972,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/leaflet": { + "version": "1.9.21", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.21.tgz", + "integrity": "sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/node": { "version": "20.19.21", "license": "MIT", @@ -1376,6 +1988,22 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/pako": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz", + "integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/papaparse": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.5.1.tgz", + "integrity": "sha512-esEO+VISsLIyE+JZBmb89NzsYYbpwV8lmv2rPo6oX5y9KhBaIP7hhHgjuTut54qjdKVMufTEcrh5fUl9+58huw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/phoenix": { "version": "1.6.6", "license": "MIT" @@ -2112,58 +2740,194 @@ "version": "2.1.1", "license": "MIT", "engines": { - "node": ">=6" + "node": ">=6" + } + }, + "node_modules/cmdk": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz", + "integrity": "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-id": "^1.1.0", + "@radix-ui/react-primitive": "^2.0.2" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "1.4.0", + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.0.2", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", "dependencies": { - "color-name": "~1.1.4" + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" }, "engines": { - "node": ">=7.0.0" + "node": ">=12" } }, - "node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/colorette": { - "version": "1.4.0", - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "dev": true, - "license": "MIT" + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } }, - "node_modules/cookie": { - "version": "1.0.2", - "license": "MIT", + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "dev": true, - "license": "MIT", + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "d3-time": "1 - 3" }, "engines": { - "node": ">= 8" + "node": ">=12" } }, - "node_modules/csstype": { - "version": "3.1.3", - "devOptional": true, - "license": "MIT" + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } }, "node_modules/damerau-levenshtein": { "version": "1.0.8", @@ -2230,6 +2994,12 @@ "version": "4.1.0-0", "license": "MIT" }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.3", "license": "MIT", @@ -2245,6 +3015,12 @@ } } }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, "node_modules/deep-is": { "version": "0.1.4", "dev": true, @@ -2305,6 +3081,16 @@ "node": ">=0.10.0" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/dotenv": { "version": "17.2.3", "license": "BSD-2-Clause", @@ -2883,10 +3669,25 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "license": "MIT" }, + "node_modules/fast-equals": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.3.3.tgz", + "integrity": "sha512-/boTcHZeIAQ2r/tL11voclBHDeP9WPxLt+tyAbVSyyXuUFyh0Tne7gJZTqGbxnvj79TjLdCXLOY7UIPhyG5MTw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/fast-glob": { "version": "3.3.1", "dev": true, @@ -3347,6 +4148,15 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "dev": true, @@ -3827,6 +4637,12 @@ "node": ">=0.10" } }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", + "license": "BSD-2-Clause" + }, "node_modules/levn": { "version": "0.4.1", "dev": true, @@ -3899,6 +4715,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "dev": true, @@ -4121,6 +4943,43 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/nuqs": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/nuqs/-/nuqs-2.8.3.tgz", + "integrity": "sha512-ZSLiAw0uDYE3JpmV3Yot7aGP4mfyj7vYCPrM7r7qUo9Bx00Vf/7eHOp4iC9LtgV1PVVdqLkvMf/8qN79BP1Jkg==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/franky47" + }, + "peerDependencies": { + "@remix-run/react": ">=2", + "@tanstack/react-router": "^1", + "next": ">=14.2.0", + "react": ">=18.2.0 || ^19.0.0-0", + "react-router": "^5 || ^6 || ^7", + "react-router-dom": "^5 || ^6 || ^7" + }, + "peerDependenciesMeta": { + "@remix-run/react": { + "optional": true + }, + "@tanstack/react-router": { + "optional": true + }, + "next": { + "optional": true + }, + "react-router": { + "optional": true + }, + "react-router-dom": { + "optional": true + } + } + }, "node_modules/object-assign": { "version": "4.1.1", "license": "MIT", @@ -4316,6 +5175,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "license": "(MIT AND Zlib)" + }, + "node_modules/papaparse": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.3.tgz", + "integrity": "sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A==", + "license": "MIT" + }, "node_modules/parent-module": { "version": "1.0.1", "dev": true, @@ -4584,6 +5455,21 @@ } } }, + "node_modules/react-smooth": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", + "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", + "license": "MIT", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-style-singleton": { "version": "2.2.3", "license": "MIT", @@ -4604,6 +5490,73 @@ } } }, + "node_modules/react-switch": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/react-switch/-/react-switch-7.1.0.tgz", + "integrity": "sha512-4xVeyImZE8QOTDw2FmhWz0iqo2psoRiS7XzdjaZBCIP8Dzo3rT0esHUjLee5WsAPSFXWWl1eVA5arp9n2C6yQA==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/recharts": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz", + "integrity": "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==", + "license": "MIT", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^18.3.1", + "react-smooth": "^4.0.4", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "license": "MIT", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "node_modules/recharts/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "dev": true, @@ -5186,6 +6139,12 @@ "node": ">=18" } }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.15", "dev": true, @@ -5468,6 +6427,28 @@ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" } }, + "node_modules/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "license": "BSD-2-Clause" @@ -5638,111 +6619,6 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } - }, - "node_modules/@next/swc-darwin-arm64": { - "version": "15.5.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.3.tgz", - "integrity": "sha512-nzbHQo69+au9wJkGKTU9lP7PXv0d1J5ljFpvb+LnEomLtSbJkbZyEs6sbF3plQmiOB2l9OBtN2tNSvCH1nQ9Jg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.5.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.3.tgz", - "integrity": "sha512-+m7pfIs0/yvgVu26ieaKrifV8C8yiLe7jVp9SpcIzg7XmyyNE7toC1fy5IOQozmr6kWl/JONC51osih2RyoXRw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.5.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.3.tgz", - "integrity": "sha512-u3PEIzuguSenoZviZJahNLgCexGFhso5mxWCrrIMdvpZn6lkME5vc/ADZG8UUk5K1uWRy4hqSFECrON6UKQBbQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.5.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.3.tgz", - "integrity": "sha512-lDtOOScYDZxI2BENN9m0pfVPJDSuUkAD1YXSvlJF0DKwZt0WlA7T7o3wrcEr4Q+iHYGzEaVuZcsIbCps4K27sA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "15.5.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.3.tgz", - "integrity": "sha512-9vWVUnsx9PrY2NwdVRJ4dUURAQ8Su0sLRPqcCCxtX5zIQUBES12eRVHq6b70bbfaVaxIDGJN2afHui0eDm+cLg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.5.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.3.tgz", - "integrity": "sha512-1CU20FZzY9LFQigRi6jM45oJMU3KziA5/sSG+dXeVaTm661snQP6xu3ykGxxwU5sLG3sh14teO/IOEPVsQMRfA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.5.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.3.tgz", - "integrity": "sha512-JMoLAq3n3y5tKXPQwCK5c+6tmwkuFDa2XAxz8Wm4+IVthdBZdZGh+lmiLUHg9f9IDwIQpUjp+ysd6OkYTyZRZw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } } } } diff --git a/package-lock.json b/package-lock.json index 35a60877..c1537c51 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,12 +7,14 @@ "name": "prisere", "dependencies": { "@aws-sdk/client-sqs": "^3.925.0", + "@react-pdf/renderer": "^4.3.1", "openapi-typescript": "^7.10.1", "react-day-picker": "^9.11.1", "react-icons": "^5.5.0" }, "devDependencies": { - "@types/bun": "latest" + "@types/bun": "latest", + "@types/jest": "^30.0.0" }, "peerDependencies": { "typescript": "^5" @@ -688,12 +690,268 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@date-fns/tz": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.4.1.tgz", "integrity": "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==", "license": "MIT" }, + "node_modules/@jest/diff-sequences": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", + "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz", + "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/get-type": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/types": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", + "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@react-pdf/fns": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@react-pdf/fns/-/fns-3.1.2.tgz", + "integrity": "sha512-qTKGUf0iAMGg2+OsUcp9ffKnKi41RukM/zYIWMDJ4hRVYSr89Q7e3wSDW/Koqx3ea3Uy/z3h2y3wPX6Bdfxk6g==", + "license": "MIT" + }, + "node_modules/@react-pdf/font": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@react-pdf/font/-/font-4.0.3.tgz", + "integrity": "sha512-N1qQDZr6phXYQOp033Hvm2nkUkx2LkszjGPbmRavs9VOYzi4sp31MaccMKptL24ii6UhBh/z9yPUhnuNe/qHwA==", + "license": "MIT", + "dependencies": { + "@react-pdf/pdfkit": "^4.0.4", + "@react-pdf/types": "^2.9.1", + "fontkit": "^2.0.2", + "is-url": "^1.2.4" + } + }, + "node_modules/@react-pdf/image": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@react-pdf/image/-/image-3.0.3.tgz", + "integrity": "sha512-lvP5ryzYM3wpbO9bvqLZYwEr5XBDX9jcaRICvtnoRqdJOo7PRrMnmB4MMScyb+Xw10mGeIubZAAomNAG5ONQZQ==", + "license": "MIT", + "dependencies": { + "@react-pdf/png-js": "^3.0.0", + "jay-peg": "^1.1.1" + } + }, + "node_modules/@react-pdf/layout": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@react-pdf/layout/-/layout-4.4.1.tgz", + "integrity": "sha512-GVzdlWoZWldRDzlWj3SttRXmVDxg7YfraAohwy+o9gb9hrbDJaaAV6jV3pc630Evd3K46OAzk8EFu8EgPDuVuA==", + "license": "MIT", + "dependencies": { + "@react-pdf/fns": "3.1.2", + "@react-pdf/image": "^3.0.3", + "@react-pdf/primitives": "^4.1.1", + "@react-pdf/stylesheet": "^6.1.1", + "@react-pdf/textkit": "^6.0.0", + "@react-pdf/types": "^2.9.1", + "emoji-regex-xs": "^1.0.0", + "queue": "^6.0.1", + "yoga-layout": "^3.2.1" + } + }, + "node_modules/@react-pdf/pdfkit": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@react-pdf/pdfkit/-/pdfkit-4.0.4.tgz", + "integrity": "sha512-/nITLggsPlB66bVLnm0X7MNdKQxXelLGZG6zB5acF5cCgkFwmXHnLNyxYOUD4GMOMg1HOPShXDKWrwk2ZeHsvw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@react-pdf/png-js": "^3.0.0", + "browserify-zlib": "^0.2.0", + "crypto-js": "^4.2.0", + "fontkit": "^2.0.2", + "jay-peg": "^1.1.1", + "linebreak": "^1.1.0", + "vite-compatible-readable-stream": "^3.6.1" + } + }, + "node_modules/@react-pdf/png-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@react-pdf/png-js/-/png-js-3.0.0.tgz", + "integrity": "sha512-eSJnEItZ37WPt6Qv5pncQDxLJRK15eaRwPT+gZoujP548CodenOVp49GST8XJvKMFt9YqIBzGBV/j9AgrOQzVA==", + "license": "MIT", + "dependencies": { + "browserify-zlib": "^0.2.0" + } + }, + "node_modules/@react-pdf/primitives": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@react-pdf/primitives/-/primitives-4.1.1.tgz", + "integrity": "sha512-IuhxYls1luJb7NUWy6q5avb1XrNaVj9bTNI40U9qGRuS6n7Hje/8H8Qi99Z9UKFV74bBP3DOf3L1wV2qZVgVrQ==", + "license": "MIT" + }, + "node_modules/@react-pdf/reconciler": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@react-pdf/reconciler/-/reconciler-1.1.4.tgz", + "integrity": "sha512-oTQDiR/t4Z/Guxac88IavpU2UgN7eR0RMI9DRKvKnvPz2DUasGjXfChAdMqDNmJJxxV26mMy9xQOUV2UU5/okg==", + "license": "MIT", + "dependencies": { + "object-assign": "^4.1.1", + "scheduler": "0.25.0-rc-603e6108-20241029" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@react-pdf/render": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@react-pdf/render/-/render-4.3.1.tgz", + "integrity": "sha512-v1WAaAhQShQZGcBxfjkEThGCHVH9CSuitrZ1bIOLvB5iBKM14abYK5D6djKhWCwF6FTzYeT2WRjRMVgze/ND2A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@react-pdf/fns": "3.1.2", + "@react-pdf/primitives": "^4.1.1", + "@react-pdf/textkit": "^6.0.0", + "@react-pdf/types": "^2.9.1", + "abs-svg-path": "^0.1.1", + "color-string": "^1.9.1", + "normalize-svg-path": "^1.1.0", + "parse-svg-path": "^0.1.2", + "svg-arc-to-cubic-bezier": "^3.2.0" + } + }, + "node_modules/@react-pdf/renderer": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@react-pdf/renderer/-/renderer-4.3.1.tgz", + "integrity": "sha512-dPKHiwGTaOsKqNWCHPYYrx8CDfAGsUnV4tvRsEu0VPGxuot1AOq/M+YgfN/Pb+MeXCTe2/lv6NvA8haUtj3tsA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@react-pdf/fns": "3.1.2", + "@react-pdf/font": "^4.0.3", + "@react-pdf/layout": "^4.4.1", + "@react-pdf/pdfkit": "^4.0.4", + "@react-pdf/primitives": "^4.1.1", + "@react-pdf/reconciler": "^1.1.4", + "@react-pdf/render": "^4.3.1", + "@react-pdf/types": "^2.9.1", + "events": "^3.3.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "queue": "^6.0.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@react-pdf/stylesheet": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@react-pdf/stylesheet/-/stylesheet-6.1.1.tgz", + "integrity": "sha512-Iyw0A3wRIeQLN4EkaKf8yF9MvdMxiZ8JjoyzLzDHSxnKYoOA4UGu84veCb8dT9N8MxY5x7a0BUv/avTe586Plg==", + "license": "MIT", + "dependencies": { + "@react-pdf/fns": "3.1.2", + "@react-pdf/types": "^2.9.1", + "color-string": "^1.9.1", + "hsl-to-hex": "^1.0.0", + "media-engine": "^1.0.3", + "postcss-value-parser": "^4.1.0" + } + }, + "node_modules/@react-pdf/textkit": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@react-pdf/textkit/-/textkit-6.0.0.tgz", + "integrity": "sha512-fDt19KWaJRK/n2AaFoVm31hgGmpygmTV7LsHGJNGZkgzXcFyLsx+XUl63DTDPH3iqxj3xUX128t104GtOz8tTw==", + "license": "MIT", + "dependencies": { + "@react-pdf/fns": "3.1.2", + "bidi-js": "^1.0.2", + "hyphen": "^1.6.4", + "unicode-properties": "^1.4.1" + } + }, + "node_modules/@react-pdf/types": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@react-pdf/types/-/types-2.9.1.tgz", + "integrity": "sha512-5GoCgG0G5NMgpPuHbKG2xcVRQt7+E5pg3IyzVIIozKG3nLcnsXW4zy25vG1ZBQA0jmo39q34au/sOnL/0d1A4w==", + "license": "MIT", + "dependencies": { + "@react-pdf/font": "^4.0.3", + "@react-pdf/primitives": "^4.1.1", + "@react-pdf/stylesheet": "^6.1.1" + } + }, "node_modules/@redocly/ajv": { "version": "8.11.3", "license": "MIT", @@ -731,6 +989,13 @@ "npm": ">=9.5.0" } }, + "node_modules/@sinclair/typebox": { + "version": "0.34.41", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", + "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", + "dev": true, + "license": "MIT" + }, "node_modules/@smithy/abort-controller": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.5.tgz", @@ -1325,6 +1590,15 @@ "node": ">=18.0.0" } }, + "node_modules/@swc/helpers": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, "node_modules/@types/bun": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.3.1.tgz", @@ -1335,6 +1609,44 @@ "bun-types": "1.3.1" } }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", + "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^30.0.0", + "pretty-format": "^30.0.0" + } + }, "node_modules/@types/node": { "version": "24.10.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz", @@ -1356,6 +1668,36 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/abs-svg-path": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/abs-svg-path/-/abs-svg-path-0.1.1.tgz", + "integrity": "sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA==", + "license": "MIT" + }, "node_modules/agent-base": { "version": "7.1.4", "license": "MIT", @@ -1370,6 +1712,22 @@ "node": ">=6" } }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/argparse": { "version": "2.0.1", "license": "Python-2.0" @@ -1378,6 +1736,35 @@ "version": "1.0.2", "license": "MIT" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/bowser": { "version": "2.12.1", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.12.1.tgz", @@ -1391,6 +1778,37 @@ "balanced-match": "^1.0.0" } }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brotli": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", + "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.1.2" + } + }, + "node_modules/browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "license": "MIT", + "dependencies": { + "pako": "~1.0.5" + } + }, "node_modules/bun-types": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.3.1.tgz", @@ -1404,14 +1822,104 @@ "@types/react": "^19" } }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/change-case": { "version": "5.4.4", "license": "MIT" }, + "node_modules/ci-info": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "node_modules/colorette": { "version": "1.4.0", "license": "MIT" }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -1451,6 +1959,55 @@ } } }, + "node_modules/dfa": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", + "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==", + "license": "MIT" + }, + "node_modules/emoji-regex-xs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", + "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/expect": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", + "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "30.2.0", + "@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" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "license": "MIT" @@ -1473,6 +2030,68 @@ "fxparser": "src/cli/cli.js" } }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fontkit": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz", + "integrity": "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==", + "license": "MIT", + "dependencies": { + "@swc/helpers": "^0.5.12", + "brotli": "^1.3.2", + "clone": "^2.1.2", + "dfa": "^1.2.0", + "fast-deep-equal": "^3.1.3", + "restructure": "^3.0.0", + "tiny-inflate": "^1.0.3", + "unicode-properties": "^1.4.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hsl-to-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsl-to-hex/-/hsl-to-hex-1.0.0.tgz", + "integrity": "sha512-K6GVpucS5wFf44X0h2bLVRDsycgJmf9FF2elg+CrqD8GcFU8c6vYhgXn8NjUkFCwj+xDFb70qgLbTUm6sxwPmA==", + "license": "MIT", + "dependencies": { + "hsl-to-rgb-for-reals": "^1.1.0" + } + }, + "node_modules/hsl-to-rgb-for-reals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/hsl-to-rgb-for-reals/-/hsl-to-rgb-for-reals-1.1.1.tgz", + "integrity": "sha512-LgOWAkrN0rFaQpfdWBQlv/VhkOxb5AsBjk6NQVx4yEzWS923T07X0M1Y0VNko2H52HeSpZrZNNMJ0aFqsdVzQg==", + "license": "ISC" + }, "node_modules/https-proxy-agent": { "version": "7.0.6", "license": "MIT", @@ -1484,6 +2103,12 @@ "node": ">= 14" } }, + "node_modules/hyphen": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/hyphen/-/hyphen-1.10.6.tgz", + "integrity": "sha512-fXHXcGFTXOvZTSkPJuGOQf5Lv5T/R2itiiCVPg9LxAje5D00O0pP83yJShFq5V89Ly//Gt6acj7z8pbBr34stw==", + "license": "ISC" + }, "node_modules/index-to-position": { "version": "1.2.0", "license": "MIT", @@ -1494,6 +2119,139 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/is-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "license": "MIT" + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "license": "MIT" + }, + "node_modules/jay-peg": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/jay-peg/-/jay-peg-1.1.1.tgz", + "integrity": "sha512-D62KEuBxz/ip2gQKOEhk/mx14o7eiFRaU+VNNSP4MOiIkwb/D6B3G1Mfas7C/Fit8EsSV2/IWjZElx/Gs6A4ww==", + "license": "MIT", + "dependencies": { + "restructure": "^3.0.0" + } + }, + "node_modules/jest-diff": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", + "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/diff-sequences": "30.0.1", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", + "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.2.0", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", + "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.2.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "micromatch": "^4.0.8", + "pretty-format": "30.2.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-mock": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", + "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@types/node": "*", + "jest-util": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", + "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/js-levenshtein": { "version": "1.1.6", "license": "MIT", @@ -1519,6 +2277,70 @@ "version": "1.0.0", "license": "MIT" }, + "node_modules/linebreak": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz", + "integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==", + "license": "MIT", + "dependencies": { + "base64-js": "0.0.8", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/linebreak/node_modules/base64-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", + "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/media-engine": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/media-engine/-/media-engine-1.0.3.tgz", + "integrity": "sha512-aa5tG6sDoK+k70B9iEX1NeyfT8ObCKhNDs6lJVpwF6r8vhUfuKMslIcirq6HIUYuuUYLefcEQOn9bSBOvawtwg==", + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/minimatch": { "version": "5.1.6", "license": "ISC", @@ -1533,6 +2355,24 @@ "version": "2.1.3", "license": "MIT" }, + "node_modules/normalize-svg-path": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/normalize-svg-path/-/normalize-svg-path-1.1.0.tgz", + "integrity": "sha512-r9KHKG2UUeB5LoTouwDzBy2VxXlHsiM6fyLQvnJa0S5hrhzqElH/CH7TUGhT1fVvIYBIKf3OpY4YJ4CK+iaqHg==", + "license": "MIT", + "dependencies": { + "svg-arc-to-cubic-bezier": "^3.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/openapi-typescript": { "version": "7.10.1", "resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-7.10.1.tgz", @@ -1553,6 +2393,12 @@ "typescript": "^5.x" } }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/parse-json": { "version": "8.3.0", "license": "MIT", @@ -1568,10 +2414,29 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse-svg-path": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/parse-svg-path/-/parse-svg-path-0.1.2.tgz", + "integrity": "sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==", + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "license": "ISC" }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/pluralize": { "version": "8.0.0", "license": "MIT", @@ -1579,6 +2444,66 @@ "node": ">=4" } }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/pretty-format": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", + "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/queue": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "license": "MIT", + "dependencies": { + "inherits": "~2.0.3" + } + }, "node_modules/react": { "version": "19.2.0", "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", @@ -1619,6 +2544,13 @@ "react": "*" } }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, "node_modules/require-from-string": { "version": "2.0.2", "license": "MIT", @@ -1626,6 +2558,79 @@ "node": ">=0.10.0" } }, + "node_modules/restructure": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz", + "integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==", + "license": "MIT" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.25.0-rc-603e6108-20241029", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0-rc-603e6108-20241029.tgz", + "integrity": "sha512-pFwF6H1XrSdYYNLfOcGlM28/j8CGLu8IvdrxqhjWULe2bPcKiKW4CV+OWqR/9fT52mywx65l7ysNkjLKBda7eA==", + "license": "MIT" + }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/strnum": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", @@ -1648,6 +2653,31 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/svg-arc-to-cubic-bezier": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.0.tgz", + "integrity": "sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==", + "license": "ISC" + }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -1683,10 +2713,56 @@ "dev": true, "license": "MIT" }, + "node_modules/unicode-properties": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", + "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "license": "MIT", + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, + "node_modules/unicode-trie/node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "license": "MIT" + }, "node_modules/uri-js-replace": { "version": "1.0.1", "license": "MIT" }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/vite-compatible-readable-stream": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/vite-compatible-readable-stream/-/vite-compatible-readable-stream-3.6.1.tgz", + "integrity": "sha512-t20zYkrSf868+j/p31cRIGN28Phrjm3nRSLR2fyc2tiWi4cZGVdv68yNlwnIINTkMTmPoMiSlc0OadaO7DXZaQ==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/yaml-ast-parser": { "version": "0.0.43", "license": "Apache-2.0" @@ -1697,6 +2773,12 @@ "engines": { "node": ">=12" } + }, + "node_modules/yoga-layout": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", + "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", + "license": "MIT" } } }