diff --git a/package.json b/package.json index 2fc807d0..8b8cd2da 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openstack-uicore-foundation", - "version": "5.0.43", + "version": "5.0.46-beta.2", "description": "ui reactjs components for openstack marketing site", "main": "lib/openstack-uicore-foundation.js", "scripts": { @@ -31,7 +31,7 @@ "@mui/icons-material": "^6.4.3", "@mui/material": "^6.4.3", "@mui/x-date-pickers": "^7.26.0", - "@react-pdf/renderer": "^3.1.11", + "@react-pdf/renderer": "^4.4.1", "@sentry/react": "^8.54.0", "@sentry/webpack-plugin": "^3.1.2", "@stripe/react-stripe-js": "^5.4.1", @@ -126,7 +126,7 @@ "@mui/icons-material": "^6.4.3", "@mui/material": "^6.4.3", "@mui/x-date-pickers": "^7.26.0", - "@react-pdf/renderer": "^3.1.11", + "@react-pdf/renderer": "^4.4.1", "@sentry/react": "^8.54.0", "@stripe/react-stripe-js": "^5.4.1", "@stripe/stripe-js": "^8.5.3", diff --git a/src/components/index.js b/src/components/index.js index f074b1c0..b77daffd 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -141,3 +141,4 @@ export {MuiBaseCustomTheme} from './mui/MuiBaseCustomTheme' // export {default as MuiAdditionalInput} from './mui/formik-inputs/additional-input/additional-input' // react-beautiful-dnd (via dnd-list) // export {default as MuiAdditionalInputList} from './mui/formik-inputs/additional-input/additional-input-list' // react-beautiful-dnd (via dnd-list) // export {default as MuiDragNDropList} from './mui/DragNDropList' // @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities +// export {OrderPdf, buildRows as buildOrderPdfRows, generateInvoicePDF, previewPDF} from './order-invoice-pdf' // @react-pdf/renderer diff --git a/src/components/order-invoice-pdf/__tests__/order-invoice-pdf.test.js b/src/components/order-invoice-pdf/__tests__/order-invoice-pdf.test.js new file mode 100644 index 00000000..e2ff3203 --- /dev/null +++ b/src/components/order-invoice-pdf/__tests__/order-invoice-pdf.test.js @@ -0,0 +1,629 @@ +/** + * Copyright 2026 OpenStack Foundation + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * */ + +import React from "react"; +import { render } from "@testing-library/react"; +import { pdf } from "@react-pdf/renderer"; +import { buildRows, OrderPdf, generateInvoicePDF, previewPDF } from "../index"; +import { formatDate } from "../helpers"; + +jest.mock("@react-pdf/renderer", () => { + const React = require("react"); + const Span = ({ children }) => + React.createElement("span", null, children ?? null); + return { + Document: Span, + Page: Span, + Text: Span, + View: Span, + Image: () => null, + Svg: () => null, + Path: () => null, + StyleSheet: { create: (s) => s }, + Font: { register: () => {} }, + pdf: jest.fn(() => ({ toBlob: async () => ({}) })) + }; +}); + +// ─── Helpers ───────────────────────────────────────────────────────────────── + +const MOCK_SUMMIT = { time_zone_id: "UTC" }; + +const makeForm = (overrides = {}) => ({ + id: 1, + code: "FORM-1", + name: "Gold Package", + discount_in_cents: 0, + discount_amount: null, + discount_type: null, + add_on: null, + items: [], + ...overrides +}); + +const makeItem = (overrides = {}) => ({ + line_id: 10, + code: "ITEM-A", + title: "Logo Placement", + type: null, + quantity: 2, + amount: 5000, + ...overrides +}); + +// ─── Empty / missing collections ───────────────────────────────────────────── + +describe("buildRows — empty / missing collections", () => { + it("returns [] without throwing for any empty input", () => { + expect(buildRows({}, MOCK_SUMMIT)).toEqual([]); + expect( + buildRows({ forms: [], fees: [], payments: [], refunds: [] }, MOCK_SUMMIT) + ).toEqual([]); + }); +}); + +// ─── Item rows ──────────────────────────────────────────────────────────────── + +describe("buildRows — item rows", () => { + it("emits item rows directly with no group row", () => { + const rows = buildRows( + { forms: [makeForm({ items: [makeItem()] })] }, + MOCK_SUMMIT + ); + expect(rows[0].type).toBe("item"); + expect(rows.every((r) => r.type !== "group")).toBe(true); + }); + + it("formats price, stringifies qty, and uses form.code as the row code", () => { + const form = makeForm({ + code: "ABC-1", + items: [makeItem({ amount: 5000, quantity: 3 })] + }); + const itemRow = buildRows({ forms: [form] }, MOCK_SUMMIT).find( + (r) => r.type === "item" + ); + expect(itemRow.price).toBe("$50.00"); + expect(itemRow.qty).toBe("3"); + expect(itemRow.code).toBe("ABC-1"); + }); + + it("prefers item.type.name over item.title for description", () => { + const withType = makeItem({ + type: { name: "Platinum Sponsor" }, + title: "fallback" + }); + const withoutType = makeItem({ type: null, title: "Logo Placement" }); + const rows = buildRows( + { + forms: [ + makeForm({ id: 1, items: [withType] }), + makeForm({ id: 2, items: [withoutType] }) + ] + }, + MOCK_SUMMIT + ); + const itemRows = rows.filter((r) => r.type === "item"); + expect(itemRows[0].description).toBe("Platinum Sponsor"); + expect(itemRows[1].description).toBe("Logo Placement"); + }); + + it("excludes items with quantity 0", () => { + const rows = buildRows( + { forms: [makeForm({ items: [makeItem({ quantity: 0 })] })] }, + MOCK_SUMMIT + ); + expect(rows).toHaveLength(0); + }); +}); + +// ─── Cancelled items (per-item, not per-form) ───────────────────────────────── + +describe("buildRows — cancelled items", () => { + const cancelledItem = makeItem({ + line_id: 20, + amount: 10000, + canceled_by_id: 99, + canceled_by_full_name: "Admin User", + canceled_at: 1700000000 + }); + + it("sets cancelled: true and populates cancelledBy when item.canceled_by_id is set", () => { + const rows = buildRows( + { forms: [makeForm({ items: [cancelledItem] })] }, + MOCK_SUMMIT + ); + expect(rows[0].cancelled).toBe(true); + expect(rows[0].cancelledBy).toMatch(/Admin User/); + }); + + it("sets cancelled: false and empty cancelledBy when canceled_by_id is absent or null", () => { + const withNull = makeForm({ + id: 1, + items: [makeItem({ canceled_by_id: null })] + }); + const withAbsent = makeForm({ id: 2, items: [makeItem()] }); + const rows = buildRows({ forms: [withNull, withAbsent] }, MOCK_SUMMIT); + rows.forEach((r) => { + expect(r.cancelled).toBe(false); + expect(r.cancelledBy).toBe(""); + }); + }); + + it("cancelled items still accumulate into the running balance", () => { + const normalItem = makeItem({ line_id: 10, amount: 8000 }); + const rows = buildRows( + { + forms: [ + makeForm({ id: 1, items: [normalItem] }), + makeForm({ id: 2, items: [cancelledItem] }) + ] + }, + MOCK_SUMMIT + ); + const normal = rows.find((r) => !r.cancelled); + const cancelled = rows.find((r) => r.cancelled); + expect(normal.balanceCents).toBe(8000); + expect(cancelled.balanceCents).toBe(18000); // 8000 + 10000 + }); + + it("a form-level canceled_by_id does not mark items as cancelled", () => { + const form = makeForm({ canceled_by_id: 99, items: [makeItem()] }); + const rows = buildRows({ forms: [form] }, MOCK_SUMMIT); + expect(rows[0].cancelled).toBe(false); + }); +}); + +// ─── Fee rows ───────────────────────────────────────────────────────────────── + +describe("buildRows — fee rows", () => { + it("emits code PAYFEE with formatted amount", () => { + const feeRow = buildRows( + { fees: [{ id: 1, title: "Processing Fee", amount: 200 }] }, + MOCK_SUMMIT + ).find((r) => r.type === "fee"); + expect(feeRow.code).toBe("PAYFEE"); + expect(feeRow.price).toBe("$2.00"); + }); +}); + +// ─── Discount rows ──────────────────────────────────────────────────────────── + +describe("buildRows — discount rows", () => { + it("emits no discount rows when discount_in_cents is 0", () => { + const rows = buildRows( + { forms: [makeForm({ discount_in_cents: 0 })] }, + MOCK_SUMMIT + ); + expect(rows.filter((r) => r.type === "discount")).toHaveLength(0); + }); + + it("emits one discount row with code DIS and formatted amount, describing a Rate discount from raw discount_amount/discount_type", () => { + const form = makeForm({ + discount_in_cents: 1500, + discount_amount: 1500, + discount_type: "Rate" + }); + const discountRows = buildRows({ forms: [form] }, MOCK_SUMMIT).filter( + (r) => r.type === "discount" + ); + expect(discountRows).toHaveLength(1); + expect(discountRows[0].rowKey).toBe(`discount-${form.id}`); + expect(discountRows[0].code).toBe("DIS"); + expect(discountRows[0].price).toBe("$15.00"); + expect(discountRows[0].description).toBe("15%"); + }); + + it("describes an Amount discount from raw discount_amount/discount_type", () => { + const form = makeForm({ + discount_in_cents: 500, + discount_amount: 500, + discount_type: "Amount" + }); + const discountRows = buildRows({ forms: [form] }, MOCK_SUMMIT).filter( + (r) => r.type === "discount" + ); + expect(discountRows[0].description).toBe("$5.00"); + }); +}); + +// ─── Payment rows ───────────────────────────────────────────────────────────── + +describe("buildRows — payment rows", () => { + it("sets description to 'Paid via ' and defaults method to card", () => { + const withMethod = buildRows( + { payments: [{ id: 1, amount: 2500, method: "wire" }] }, + MOCK_SUMMIT + ).find((r) => r.type === "payment"); + expect(withMethod.price).toBe("$25.00"); + expect(withMethod.description).toBe("Paid via wire"); + + const withoutMethod = buildRows( + { payments: [{ id: 2, amount: 10000 }] }, + MOCK_SUMMIT + ).find((r) => r.type === "payment"); + expect(withoutMethod.description).toBe("Paid via card"); + }); +}); + +// ─── Refund rows ────────────────────────────────────────────────────────────── + +describe("buildRows — refund rows", () => { + it("maps reason to description and status to subDescription, with defaults when absent", () => { + const withFields = buildRows( + { + refunds: [ + { + id: 1, + reason: "duplicate charge", + status: "approved", + amount: 3000 + } + ] + }, + MOCK_SUMMIT + ).find((r) => r.type === "refund"); + expect(withFields.price).toBe("$30.00"); + expect(withFields.description).toBe("duplicate charge"); + expect(withFields.subDescription).toBe("approved"); + + const withDefaults = buildRows( + { refunds: [{ id: 2, amount: 1000 }] }, + MOCK_SUMMIT + ).find((r) => r.type === "refund"); + expect(withDefaults.description).toBe("Refund"); + expect(withDefaults.subDescription).toBe(""); + }); +}); + +// ─── Note rows ──────────────────────────────────────────────────────────────── + +describe("buildRows — note rows", () => { + it("emits type 'note' with content, defaulting to empty string when absent", () => { + const withContent = buildRows( + { notes: [{ id: 1, content: "Call client to confirm" }] }, + MOCK_SUMMIT + ); + expect(withContent[0].type).toBe("note"); + expect(withContent[0].content).toBe("Call client to confirm"); + + const withoutContent = buildRows({ notes: [{ id: 2 }] }, MOCK_SUMMIT); + expect(withoutContent[0].content).toBe(""); + }); +}); + +// ─── Balance accumulation ───────────────────────────────────────────────────── + +describe("buildRows — balance accumulation", () => { + it("interleaves payments and refunds by created date and updates balance correctly", () => { + const rows = buildRows( + { + payments: [{ id: 1, amount: 10000, created: 2 }], + refunds: [{ id: 2, amount: 3000, created: 1 }] + }, + MOCK_SUMMIT + ); + // refund first (created: 1), then payment (created: 2) + expect(rows[0].type).toBe("refund"); + expect(rows[0].balanceCents).toBe(3000); + expect(rows[1].type).toBe("payment"); + expect(rows[1].balanceCents).toBe(-7000); // 3000 - 10000 + }); +}); + +// ─── OrderPdf render-level ──────────────────────────────────────────────────── + +const makeRenderOrder = (overrides = {}) => ({ + number: "ORD-2026-001", + total: 0, + purchased_date: 1700000000, + purchased_by_full_name: "Jane Doe", + client: { contact_name: "Jane Doe", company_name: "Acme Corp" }, + address: null, + forms: [], + fees: [], + payments: [], + refunds: [], + cancelled_total: 0, + refunds_total: 0, + retained: 0, + credited_to_payment_method: 0, + ...overrides +}); + +const makeRenderSummit = (overrides = {}) => ({ + name: "OpenStack Summit 2026", + time_zone_id: "America/Los_Angeles", + locations: [ + { + is_main: true, + short_name: "Main Hall", + name: "Convention Center", + address_1: "123 Expo Blvd", + city: "Vancouver", + state: "BC", + postal_code: "V6B 1A1", + country: "Canada" + } + ], + ...overrides +}); + +describe("OrderPdf — render", () => { + it("renders header fields from order and summit, venue from locations marked is_main", () => { + const { container } = render( + + ); + const text = container.textContent; + expect(text).toContain("ORD-2026-001"); + expect(text).toContain("Acme Corp"); + expect(text).toContain("Jane Doe"); + expect(text).toContain("OpenStack Summit 2026"); + expect(text).toContain("Main Hall"); + expect(text).toContain("123 Expo Blvd"); + expect(text).toContain("V6B 1A1"); + }); + + it("prefers main_locations over locations.is_main when both are present", () => { + const { container } = render( + + ); + const text = container.textContent; + expect(text).toContain("Legacy Hall"); + expect(text).toContain("1 Legacy Way"); + expect(text).not.toContain("Main Hall"); + }); + + it("throws a clear error when order or summit are omitted", () => { + expect(() => render()).toThrow( + "OrderPdf: order is required" + ); + expect(() => render()).toThrow( + "OrderPdf: summit is required" + ); + }); + + it("renders 'Pending' for the Date field when purchased_date is null", () => { + const { container } = render( + + ); + expect(container.textContent).toContain("Pending"); + }); + + it("renders the formatted date when purchased_date is present", () => { + const { container } = render( + + ); + expect(container.textContent).not.toContain("Pending"); + expect(container.textContent).toContain( + formatDate( + makeRenderOrder().purchased_date, + makeRenderSummit().time_zone_id, + "YYYY/MM/DD hh:mm a" + ) + ); + }); +}); + +// ─── Reconciliation block ───────────────────────────────────────────────────── + +describe("OrderPdf — reconciliation block", () => { + it("renders cancelled and refunded totals from order-level fields", () => { + const order = makeRenderOrder({ + cancelled_total: 75000, + refunds_total: 20000, + retained: 75000, + credited_to_payment_method: 0 + }); + const { container } = render( + + ); + const text = container.textContent; + expect(text).toContain("Reconciliation"); + expect(text).toContain("Cancelled"); + expect(text).toContain("Refunded"); + expect(text).toContain("$750.00"); + expect(text).toContain("$200.00"); + }); + + it("switches label between 'Retained as cancellation fee' and 'Credited to Payment Method'", () => { + const { container: retainedContainer } = render( + + ); + expect(retainedContainer.textContent).toContain( + "Retained as cancellation fee" + ); + + const { container: creditedContainer } = render( + + ); + expect(creditedContainer.textContent).toContain( + "Credited to Payment Method" + ); + }); +}); + +// ─── generateInvoicePDF ──────────────────────────────────────────────────── + +describe("generateInvoicePDF", () => { + let callOrder; + let clickSpy; + let appendChildSpy; + let removeChildSpy; + + beforeEach(() => { + jest.useFakeTimers(); + callOrder = []; + URL.createObjectURL = jest.fn(() => { + callOrder.push("createObjectURL"); + return "blob:mock-url"; + }); + URL.revokeObjectURL = jest.fn(() => callOrder.push("revokeObjectURL")); + clickSpy = jest + .spyOn(HTMLAnchorElement.prototype, "click") + .mockImplementation(() => callOrder.push("click")); + appendChildSpy = jest.spyOn(document.body, "appendChild"); + removeChildSpy = jest.spyOn(document.body, "removeChild"); + }); + + afterEach(() => { + jest.useRealTimers(); + delete URL.createObjectURL; + delete URL.revokeObjectURL; + jest.restoreAllMocks(); + }); + + it("downloads the blob through a temporary link, then revokes the URL only after a delay", async () => { + await generateInvoicePDF( + makeRenderOrder({ number: "ORD 2026 001" }), + makeRenderSummit() + ); + + const link = appendChildSpy.mock.calls[0][0]; + expect(link.tagName).toBe("A"); + expect(link.href).toContain("blob:mock-url"); + expect(link.download).toBe("invoice-ord-2026-001.pdf"); + + expect(clickSpy).toHaveBeenCalledTimes(1); + expect(removeChildSpy).toHaveBeenCalledWith(link); + // revoking right after click() has been reported to produce empty/failed + // downloads on Safari and some Firefox versions, so it must be deferred + expect(callOrder).toEqual(["createObjectURL", "click"]); + expect(URL.revokeObjectURL).not.toHaveBeenCalled(); + + jest.advanceTimersByTime(60_000); + expect(callOrder).toEqual(["createObjectURL", "click", "revokeObjectURL"]); + }); + + it("logs and rethrows when PDF generation fails, without touching the DOM", async () => { + const consoleErrorSpy = jest + .spyOn(console, "error") + .mockImplementation(() => {}); + pdf.mockReturnValueOnce({ + toBlob: () => Promise.reject(new Error("boom")) + }); + + await expect( + generateInvoicePDF(makeRenderOrder(), makeRenderSummit()) + ).rejects.toThrow("boom"); + + expect(consoleErrorSpy).toHaveBeenCalledWith( + "Error generating invoice PDF:", + expect.any(Error) + ); + expect(appendChildSpy).not.toHaveBeenCalled(); + consoleErrorSpy.mockRestore(); + }); +}); + +// ─── previewPDF ───────────────────────────────────────────────────────────── + +describe("previewPDF", () => { + let originalOpen; + let fakeTab; + + beforeEach(() => { + jest.useFakeTimers(); + originalOpen = window.open; + fakeTab = { location: { href: "" } }; + URL.createObjectURL = jest.fn(() => "blob:mock-preview-url"); + URL.revokeObjectURL = jest.fn(); + window.open = jest.fn(() => fakeTab); + }); + + afterEach(() => { + jest.useRealTimers(); + delete URL.createObjectURL; + delete URL.revokeObjectURL; + window.open = originalOpen; + }); + + it("opens a blank tab synchronously (before the blob is ready) so Safari's popup blocker doesn't kill it", async () => { + const promise = previewPDF(makeRenderOrder(), makeRenderSummit()); + + // asserted before awaiting, i.e. before the pending PDF promise resolves + expect(window.open).toHaveBeenCalledWith("", "_blank"); + + await promise; + }); + + it("navigates the pre-opened tab to the blob URL once it's ready", async () => { + await previewPDF(makeRenderOrder(), makeRenderSummit()); + + expect(fakeTab.location.href).toBe("blob:mock-preview-url"); + }); + + it("does not throw when the popup blocker returns null for the pre-opened tab", async () => { + window.open = jest.fn(() => null); + + await expect( + previewPDF(makeRenderOrder(), makeRenderSummit()) + ).resolves.toBeUndefined(); + }); + + it("logs, closes the pre-opened tab, and rethrows when PDF generation fails", async () => { + const consoleErrorSpy = jest + .spyOn(console, "error") + .mockImplementation(() => {}); + fakeTab.close = jest.fn(); + pdf.mockReturnValueOnce({ + toBlob: () => Promise.reject(new Error("boom")) + }); + + await expect( + previewPDF(makeRenderOrder(), makeRenderSummit()) + ).rejects.toThrow("boom"); + + expect(consoleErrorSpy).toHaveBeenCalledWith( + "Error generating invoice PDF preview:", + expect.any(Error) + ); + expect(fakeTab.close).toHaveBeenCalledTimes(1); + consoleErrorSpy.mockRestore(); + }); + + it("keeps the URL alive until the new tab has had time to load it", async () => { + await previewPDF(makeRenderOrder(), makeRenderSummit()); + + expect(URL.revokeObjectURL).not.toHaveBeenCalled(); + + jest.advanceTimersByTime(59_999); + expect(URL.revokeObjectURL).not.toHaveBeenCalled(); + + jest.advanceTimersByTime(1); + expect(URL.revokeObjectURL).toHaveBeenCalledWith("blob:mock-preview-url"); + }); +}); diff --git a/src/components/order-invoice-pdf/components/field-row.js b/src/components/order-invoice-pdf/components/field-row.js new file mode 100644 index 00000000..6f2271ff --- /dev/null +++ b/src/components/order-invoice-pdf/components/field-row.js @@ -0,0 +1,22 @@ +/** + * Copyright 2026 OpenStack Foundation + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * */ + +import React from "react"; +import { View, Text } from "@react-pdf/renderer"; + +export const FieldRow = ({ styles, label, value, noBorder = false }) => ( + + {label ?? ""} + {value ?? ""} + +); \ No newline at end of file diff --git a/src/components/order-invoice-pdf/components/pdf-icon.js b/src/components/order-invoice-pdf/components/pdf-icon.js new file mode 100644 index 00000000..23033842 --- /dev/null +++ b/src/components/order-invoice-pdf/components/pdf-icon.js @@ -0,0 +1,38 @@ +/** + * Copyright 2026 OpenStack Foundation + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * */ + +import React from "react"; +import { Svg, Path } from "@react-pdf/renderer"; + +const MUI_ICON_PATHS = { + ArrowUpward: "M4 12l1.41 1.41L11 7.83V20h2V7.83l5.58 5.59L20 12l-8-8-8 8z", + ArrowDownward: + "M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z", + Refresh: + "M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z", + DoNotDisturb: + "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8 0-1.85.63-3.55 1.69-4.9L16.9 18.31C15.55 19.37 13.85 20 12 20zm6.31-3.1L7.1 5.69C8.45 4.63 10.15 4 12 4c4.42 0 8 3.58 8 8 0 1.85-.63 3.55-1.69 4.9z" +}; + +const PDF_ICON_SIZE = 8; + +export const PdfIcon = ({ name, color, size = PDF_ICON_SIZE }) => ( + + + +); \ No newline at end of file diff --git a/src/components/order-invoice-pdf/components/pdf-table-row.js b/src/components/order-invoice-pdf/components/pdf-table-row.js new file mode 100644 index 00000000..833dddb2 --- /dev/null +++ b/src/components/order-invoice-pdf/components/pdf-table-row.js @@ -0,0 +1,74 @@ +/** + * Copyright 2026 OpenStack Foundation + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * */ + +import React from "react"; +import { View, Text } from "@react-pdf/renderer"; +import { fmtBalance } from "../helpers"; +import { PdfIcon } from "./pdf-icon"; + +export const PdfTableRow = ({ row, styles, rowStyles }) => { + // Notes span all columns, matching NotesRow in the table + if (row.type === "note") { + return ( + + NOTE + {row.content} + + ); + } + + const s = rowStyles[row.cancelled ? "cancelled" : row.type]; + return ( + + {row.code} + {s.typeBadge ? ( + + + + {s.typeBadge.label} + + + ) : ( + + )} + + + {row.type === "item" + ? `${row.description} - Total: ${row.qty}` + : row.description} + + {row.cancelledBy && ( + {row.cancelledBy} + )} + {row.subDescription && ( + {row.subDescription} + )} + + {row.price} + {row.balanceCents != null ? ( + + {fmtBalance(row.balanceCents)} + + ) : ( + + )} + + ); +}; \ No newline at end of file diff --git a/src/components/order-invoice-pdf/components/reconciliation-block.js b/src/components/order-invoice-pdf/components/reconciliation-block.js new file mode 100644 index 00000000..cf3ee451 --- /dev/null +++ b/src/components/order-invoice-pdf/components/reconciliation-block.js @@ -0,0 +1,66 @@ +/** + * Copyright 2026 OpenStack Foundation + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * */ + +import React from "react"; +import { View, Text } from "@react-pdf/renderer"; +import { currencyAmountFromCents } from "../../../utils/money"; + +// Uses order-level totals matching the table's ReconciliationBox props: +// cancelledTotal ← order.cancelled_total +// refundsTotal ← order.refunds_total +// retained ← order.retained +// credited ← order.credited_to_payment_method +export const ReconciliationBlock = ({ + styles, + cancelledTotal, + refundsTotal, + retained, + credited +}) => { + const totalColor = retained > 0 ? "#c62828" : "#1b5e20"; + const totalLabel = + retained > 0 + ? "Retained as cancellation fee" + : credited > 0 + ? "Credited to Payment Method" + : "Balance"; + const totalValue = + retained > 0 ? retained : credited > 0 ? credited : retained; + + return ( + + + Reconciliation + + Cancelled + + {currencyAmountFromCents(cancelledTotal ?? 0)} + + + + Refunded + + {currencyAmountFromCents(refundsTotal ?? 0)} + + + + + {totalLabel} + + {currencyAmountFromCents(totalValue ?? 0)} + + + + + ); +}; diff --git a/src/components/order-invoice-pdf/helpers.js b/src/components/order-invoice-pdf/helpers.js new file mode 100644 index 00000000..7c78fee2 --- /dev/null +++ b/src/components/order-invoice-pdf/helpers.js @@ -0,0 +1,196 @@ +/** + * Copyright 2026 OpenStack Foundation + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * */ + +import moment from "moment-timezone"; +import { currencyAmountFromCents, formatDiscount } from "../../utils/money"; +import { MILLISECONDS_IN_SECOND } from "../../utils/constants"; + +export const DEFAULT_FONT_FAMILY = "Helvetica"; + +export const formatDate = ( + date, + timeZone = "LOC", + format = "dddd Do h:mm a" +) => { + if (!date) return ""; + + if (timeZone === "LOC") { + return moment(date * MILLISECONDS_IN_SECOND).format(format); + } + + return moment(date * MILLISECONDS_IN_SECOND) + .tz(timeZone) + .format(format); +}; + +/** + * Derives an order's amount due directly from its raw net_amount/amount_due + * fields (both in cents). This is the single source for that fallback rule, + * shared by consumers that normalize a raw order (e.g. into order.total) and + * by consumers that render straight from the raw order (e.g. the invoice PDF). + * @param {object} order - Raw order object with net_amount/amount_due in cents. + * @returns {number} - The amount due, in cents. + */ +export const getOrderTotal = (order) => { + if (!order) return 0; + return order.net_amount || order.amount_due || 0; +}; + +export const formatAddress = (address) => { + if (!address) return ""; + return [ + address.address_1, + address.address_2, + address.city, + address.state, + address.zip_code ?? address.postal_code, + address.country + ] + .filter(Boolean) + .join(", "); +}; + +export const formatVenueName = (location) => { + if (!location) return ""; + return `${location?.short_name ?? ""}${location?.name ? ` (${location?.name})` : ""}`; +}; + +// Reads fontFamily off the consumer's MUI theme so it stays in sync with the +// rest of their app instead of being duplicated as a separate literal. Only +// the first name in the CSS font stack is usable here: react-pdf renders +// with fonts registered via Font.register, so this name must match a family +// already registered that way in the consuming app. +export const getThemeFontFamily = (theme) => { + const fontFamily = theme?.typography?.fontFamily; + if (!fontFamily) return DEFAULT_FONT_FAMILY; + return fontFamily.split(",")[0].trim().replace(/^['"]|['"]$/g, ""); +}; + +export const fmtBalance = (cents) => { + if (cents == null) return ""; + const abs = currencyAmountFromCents(Math.abs(cents)); + return cents < 0 ? `-${abs}` : abs; +}; + +export const buildRows = (order, summit) => { + const rows = []; + let balanceCents = 0; + + (order.forms || []).forEach((form) => { + (form.items || []) + .filter((item) => (item.quantity ?? 1) > 0) + .forEach((item) => { + // Cancelled is per-item + const cancelled = !!item.canceled_by_id; + const cancelledBy = cancelled + ? `Cancelled by ${item.canceled_by_full_name} on ${formatDate(item.canceled_at, summit.time_zone_id, "YYYY/MM/DD HH:mm")}` + : ""; + + // Cancelled items still accumulate + balanceCents += item.amount; + + rows.push({ + rowKey: `item-${item.line_id ?? item.id}`, + type: "item", + // Table shows form.code per item row (columnKey: "formCode", value: form.code) + code: String(form.code || ""), + description: String(item.type?.name || item.title || ""), + addon: String(form.add_on?.name || ""), + qty: String(item.quantity ?? 1), + price: currencyAmountFromCents(item.amount), + balanceCents, + cancelled, + cancelledBy + }); + }); + + const discountCents = form.discount_in_cents ?? 0; + if (discountCents) { + balanceCents -= discountCents; + rows.push({ + rowKey: `discount-${form.id}`, + type: "discount", + code: "DIS", + description: formatDiscount(form.discount_amount, form.discount_type), + addon: "", + qty: "", + price: currencyAmountFromCents(discountCents), + balanceCents + }); + } + }); + + (order.fees || []).forEach((fee) => { + balanceCents += fee.amount; + rows.push({ + rowKey: `fee-${fee.id}`, + type: "fee", + code: "PAYFEE", + description: String(fee.title || ""), + addon: "", + qty: "1", + price: currencyAmountFromCents(fee.amount), + balanceCents + }); + }); + + // Payments and refunds interleaved and sorted by created: + const paymentsAndRefundsOrdered = [ + ...(order.payments || []).map((p) => ({ ...p, _rowType: "payment" })), + ...(order.refunds || []).map((r) => ({ ...r, _rowType: "refund" })) + ].sort((a, b) => a.created - b.created); + + paymentsAndRefundsOrdered.forEach((item) => { + if (item._rowType === "payment") { + balanceCents -= item.amount; + rows.push({ + rowKey: `payment-${item.id}`, + type: "payment", + code: "PAY", + description: `Paid via ${item.method || "card"}`, + subDescription: formatDate( + item.created, + summit.time_zone_id, + "YYYY/MM/DD HH:mm" + ), + addon: "", + qty: "1", + price: currencyAmountFromCents(item.amount), + balanceCents + }); + } else { + balanceCents += item.amount; + rows.push({ + rowKey: `refund-${item.id}`, + type: "refund", + code: "REF", + description: String(item.reason || "Refund"), + subDescription: String(item.status || ""), + addon: "", + qty: "1", + price: currencyAmountFromCents(item.amount), + balanceCents + }); + } + }); + + (order.notes || []).forEach((note) => { + rows.push({ + rowKey: `note-${note.id}`, + type: "note", + content: String(note.content || "") + }); + }); + + return rows; +}; diff --git a/src/components/order-invoice-pdf/index.js b/src/components/order-invoice-pdf/index.js new file mode 100644 index 00000000..e5ea084f --- /dev/null +++ b/src/components/order-invoice-pdf/index.js @@ -0,0 +1,236 @@ +/** + * Copyright 2026 OpenStack Foundation + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * */ + +import React from "react"; +import PropTypes from "prop-types"; +import { Document, Page, Text, View, Image, pdf } from "@react-pdf/renderer"; +import { createStyles, createRowStyles } from "./styles"; +import { + buildRows, + formatDate, + formatAddress, + formatVenueName, + getThemeFontFamily, + fmtBalance, + getOrderTotal +} from "./helpers"; +import { FieldRow } from "./components/field-row"; +import { PdfTableRow } from "./components/pdf-table-row"; +import { ReconciliationBlock } from "./components/reconciliation-block"; + +export { buildRows }; + +// logoSrc/theme are left to the consumer on purpose: this component is +// shared across apps with different branding/typefaces, so no logo image or +// font file ships with uicore. fontFamily is read off theme.typography and +// falls back to a react-pdf built-in (no Font.register needed) when absent; +// the theme's fontFamily must already be registered via Font.register in +// the consuming app to render as a custom typeface instead of falling back. +export const OrderPdf = ({ order, summit, logoSrc, theme }) => { + if (!order) throw new Error("OrderPdf: order is required"); + if (!summit) throw new Error("OrderPdf: summit is required"); + + const fontFamily = getThemeFontFamily(theme); + const styles = createStyles(fontFamily); + const rowStyles = createRowStyles(styles); + const rows = buildRows(order, summit); + const mainLocation = + summit.main_locations?.[0] ?? + summit.locations?.find((location) => location.is_main); + + const { + cancelled_total: cancelledTotal = 0, + refunds_total: refundsTotal = 0, + retained = 0, + credited_to_payment_method: credited = 0 + } = order; + + const total = getOrderTotal(order); + + const clientName = + order.client?.contact_name || order.purchased_by_full_name || ""; + const clientCompany = order.client?.company_name || ""; + const clientAddress = formatAddress(order.address) || "N/A"; + + return ( + + + {/* Row 1: Logo | Invoice title + Order / Date */} + + + {logoSrc ? : null} + + + Invoice + + + + + + {/* Row 2: Client + Address | Event + Venue + Address */} + + + + + + + + + + + + + {/* Table */} + + + + Code + Type + Details + Amount + Balance + + + {rows.map((row) => ( + + ))} + + + + + AMOUNT DUE + + 0 ? "#e65100" : "#1b5e20" + } + ]} + > + {fmtBalance(total ?? 0)} + + + + + + + ); +}; + +OrderPdf.propTypes = { + order: PropTypes.object.isRequired, + summit: PropTypes.object.isRequired, + logoSrc: PropTypes.string, + theme: PropTypes.object +}; + +// Revoking a blob URL immediately after use has been reported to produce +// empty/failed downloads on Safari and some Firefox versions, so both +// generateInvoicePDF and previewPDF defer the revoke by this long instead. +const REVOKE_DELAY_MS = 60_000; + +export const generateInvoicePDF = async ( + order, + summit, + { logoSrc, theme } = {} +) => { + try { + const blob = await pdf( + + ).toBlob(); + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = url; + link.download = `invoice-${order.number}.pdf` + .toLowerCase() + .replace(/\s+/g, "-"); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + setTimeout(() => URL.revokeObjectURL(url), REVOKE_DELAY_MS); + } catch (error) { + console.error("Error generating invoice PDF:", error); + throw error; + } +}; + +export const previewPDF = async (order, summit, { logoSrc, theme } = {}) => { + // Opened synchronously, before the await below, so the tab is still tied + // to the originating click — otherwise Safari's popup blocker kills it. + // Can't pass "noopener" (or "noreferrer", which implies it) here: that + // makes window.open return null, and we need the reference to set + // .location.href once the blob URL is ready. + const newTab = window.open("", "_blank"); + + try { + const blob = await pdf( + + ).toBlob(); + const url = URL.createObjectURL(blob); + + if (newTab) { + newTab.location.href = url; + } + + // Revoke after the new tab has had a chance to load the PDF. + setTimeout(() => URL.revokeObjectURL(url), REVOKE_DELAY_MS); + } catch (error) { + console.error("Error generating invoice PDF preview:", error); + if (newTab) newTab.close(); + throw error; + } +}; diff --git a/src/components/order-invoice-pdf/styles.js b/src/components/order-invoice-pdf/styles.js new file mode 100644 index 00000000..6a6d110a --- /dev/null +++ b/src/components/order-invoice-pdf/styles.js @@ -0,0 +1,330 @@ +/** + * Copyright 2026 OpenStack Foundation + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * */ + +import { StyleSheet } from "@react-pdf/renderer"; + +// Parameterized by fontFamily so a consumer's custom typeface (registered via +// Font.register in their own app) applies to every text style explicitly, +// not just the ones a consumer happens to remember to override. +export const createStyles = (fontFamily) => StyleSheet.create({ + page: { + backgroundColor: "#F7F7F8", + padding: 64, + fontFamily, + fontSize: 8, + color: "#212529" + }, + headerRow: { + flexDirection: "row", + marginBottom: 20, + gap: 20 + }, + cellLeft: { + flex: 1, + backgroundColor: "#ffffff", + borderRadius: 10, + borderWidth: 1, + borderColor: "#DEE2E6", + paddingHorizontal: 18, + paddingVertical: 10, + justifyContent: "center" + }, + logo: { + width: "100%", + height: 84, + objectFit: "contain", + marginBottom: 0, + paddingBottom: 0 + }, + cellRight: { + flex: 1, + backgroundColor: "#ffffff", + borderRadius: 10, + borderWidth: 1, + borderColor: "#DEE2E6", + paddingHorizontal: 18, + paddingVertical: 10 + }, + invoiceTitle: { + fontFamily, + fontWeight: "bold", + fontSize: 14, + lineHeight: 1.5, + letterSpacing: 0, + color: "rgba(17, 19, 21, 1)", + marginBottom: 8 + }, + fieldRow: { + fontSize: 8, + lineHeight: 1.8, + flexDirection: "row", + color: "rgba(0, 0, 0, 0.87)", + paddingBottom: 3, + paddingTop: 6, + borderBottom: "1px solid rgba(224, 224, 224, 1)", + alignContent: "flex-start" + }, + fieldRowNoBorder: { + borderBottomWidth: 0 + }, + fieldLabel: { + flex: 4, + fontWeight: "bold", + color: "rgba(0, 0, 0, 0.87)" + }, + fieldValue: { + flex: 9 + }, + tableWrapper: { + marginTop: 8, + borderRadius: 8, + borderWidth: 1, + borderStyle: "solid", + borderColor: "#eee", + backgroundColor: "#ffffff" + }, + table: { + fontFamily, + borderRadius: 8, + overflow: "hidden" + }, + thRow: { + flexDirection: "row", + backgroundColor: "#f0f0f0", + paddingVertical: 12, + paddingHorizontal: 12, + alignItems: "center", + borderBottomWidth: 1, + borderBottomStyle: "solid", + borderBottomColor: "#eee" + }, + thText: { + fontFamily, + fontSize: 7, + fontWeight: 600, + color: "#333333" + }, + tdRow: { + backgroundColor: "#FFFFFF", + flexDirection: "row", + paddingHorizontal: 12, + paddingVertical: 14, + alignItems: "center", + borderBottomWidth: 1, + borderBottomStyle: "solid", + borderBottomColor: "#eee" + }, + tdPayment: { + flexDirection: "row", + paddingHorizontal: 12, + paddingVertical: 14, + alignItems: "flex-start", + backgroundColor: "#f0fdf4", + borderBottomWidth: 1, + borderBottomStyle: "solid", + borderBottomColor: "#eee" + }, + tdRefund: { + flexDirection: "row", + paddingHorizontal: 12, + paddingVertical: 14, + alignItems: "flex-start", + backgroundColor: "#fff7ed", + borderBottomWidth: 1, + borderBottomStyle: "solid", + borderBottomColor: "#eee" + }, + tdNote: { + flexDirection: "row", + paddingHorizontal: 12, + paddingVertical: 10, + alignItems: "flex-start", + backgroundColor: "#FFFFFF", + borderBottomWidth: 1, + borderBottomStyle: "solid", + borderBottomColor: "#eee" + }, + tdAmountDue: { + flexDirection: "row", + paddingHorizontal: 12, + paddingVertical: 14, + alignItems: "center", + backgroundColor: "#fafafa", + borderTopWidth: 1, + borderTopColor: "#000000" + }, + colCode: { width: "12%", fontSize: 8 }, + colCodePayment: { width: "12%", fontSize: 8, fontWeight: "bold" }, + colType: { width: "15%", fontSize: 8 }, + colDesc: { width: "39%", fontSize: 8 }, + colDescMultiLine: { + width: "39%", + flexDirection: "column", + justifyContent: "flex-start" + }, + colPrice: { width: "18%", textAlign: "right", fontSize: 8, color: "#333333" }, + colPriceRefund: { + fontFamily, + width: "18%", + textAlign: "right", + fontSize: 8, + fontWeight: "bold", + color: "#e65100" + }, + colPricePayment: { + fontFamily, + width: "18%", + textAlign: "right", + fontSize: 8, + fontWeight: "bold", + color: "#1b5e20" + }, + colBalance: { + width: "16%", + textAlign: "right", + fontSize: 8, + color: "#666666" + }, + colBalanceNegative: { color: "#dc2626" }, + tdAmountDueLabel: { + fontFamily, + width: "66%", + fontSize: 8, + fontWeight: 700 + }, + tdAmountDueValue: { + fontFamily, + width: "16%", + textAlign: "right", + fontSize: 9, + fontWeight: 700 + }, + paymentDesc: { fontFamily, fontSize: 8, fontWeight: "bold" }, + muted: { color: "#6C757D", fontSize: 8 }, + typeCell: { width: "15%", flexDirection: "row", alignItems: "center" }, + typeBadgeLabel: { fontSize: 8 }, + cancelledText: { color: "#9ca3af", textDecoration: "line-through" }, + reconciliationWrapper: { + flexDirection: "row", + paddingHorizontal: 12, + paddingVertical: 12, + borderBottomWidth: 1, + borderBottomStyle: "solid", + borderBottomColor: "#eee" + }, + reconciliationBox: { + width: "33%" + }, + reconciliationTitle: { + fontFamily, + fontSize: 8, + fontWeight: 600, + color: "#333333", + marginBottom: 6 + }, + reconciliationRow: { + flexDirection: "row", + justifyContent: "space-between", + paddingVertical: 2 + }, + reconciliationLabel: { + fontSize: 7, + color: "#6C757D" + }, + reconciliationValue: { + fontSize: 7, + color: "#333333", + textAlign: "right" + }, + reconciliationDivider: { + borderBottomWidth: 1, + borderBottomColor: "#dee2e6", + marginVertical: 4 + } +}); + +export const createRowStyles = (styles) => ({ + fee: { + row: styles.tdRow, + code: styles.colCode, + descText: null, + price: styles.colPrice, + typeBadge: { + icon: "ArrowUpward", + iconColor: "#ff9800", + label: "Charge", + labelColor: "#333333" + } + }, + item: { + row: styles.tdRow, + code: styles.colCode, + descText: null, + price: styles.colPrice, + typeBadge: { + icon: "ArrowUpward", + iconColor: "#ff9800", + label: "Charge", + labelColor: "#333333" + } + }, + discount: { + row: styles.tdPayment, + code: styles.colCodePayment, + descText: styles.paymentDesc, + price: styles.colPricePayment, + typeBadge: { + icon: "ArrowDownward", + iconColor: "#4caf50", + label: "Discount", + labelColor: "#1b5e20" + } + }, + payment: { + row: styles.tdPayment, + code: styles.colCodePayment, + descText: styles.paymentDesc, + price: styles.colPricePayment, + typeBadge: { + icon: "ArrowDownward", + iconColor: "#4caf50", + label: "Payment", + labelColor: "#1b5e20" + } + }, + refund: { + row: styles.tdRefund, + code: styles.colCodePayment, + descText: styles.paymentDesc, + price: styles.colPriceRefund, + typeBadge: { + icon: "Refresh", + iconColor: "#ea580c", + label: "Refund", + labelColor: "#e65100" + } + }, + cancelled: { + row: styles.tdRow, + code: [styles.colCode, styles.cancelledText], + descText: styles.cancelledText, + price: [styles.colPrice, styles.cancelledText], + balance: [styles.colBalance, styles.cancelledText], + typeBadge: { + icon: "DoNotDisturb", + iconColor: "#9ca3af", + label: "Cancelled", + labelColor: "#9ca3af" + } + } +}); diff --git a/webpack.common.js b/webpack.common.js index 162da07a..4dd4ae58 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -25,6 +25,7 @@ module.exports = { 'components/sections/panel': './src/components/sections/panel.js', 'components/simple-link-list': './src/components/simple-link-list/index.js', 'components/summit-dropdown': './src/components/summit-dropdown/index.js', + 'components/order-invoice-pdf': './src/components/order-invoice-pdf/index.js', 'components/table': './src/components/table/Table.js', 'components/table-editable': './src/components/table-editable/EditableTable.js', 'components/table-selectable': './src/components/table-selectable/SelectableTable.js', diff --git a/yarn.lock b/yarn.lock index a61a59e6..314afe32 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1722,6 +1722,16 @@ "@babel/runtime" "^7.25.7" "@mui/utils" "^5.16.6 || ^6.0.0 || ^7.0.0" +"@noble/ciphers@^1.0.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-1.3.0.tgz#f64b8ff886c240e644e5573c097f86e5b43676dc" + integrity sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw== + +"@noble/hashes@^1.6.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.8.0.tgz#cee43d801fcef9644b11b8194857695acd5f815a" + integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== + "@npmcli/fs@^1.0.0": version "1.1.1" resolved "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz" @@ -1758,143 +1768,146 @@ resolved "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz" integrity sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA== -"@react-pdf/fns@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@react-pdf/fns/-/fns-2.0.1.tgz" - integrity sha512-/vgecczzFYBQFkgUupH+sxXhLWQtBwdwCgweyh25XOlR4NZuaMD/UVUDl4loFHhRQqDMQq37lkTcchh7zzW6ug== - dependencies: - "@babel/runtime" "^7.20.13" +"@react-pdf/fns@3.1.3": + version "3.1.3" + resolved "https://registry.yarnpkg.com/@react-pdf/fns/-/fns-3.1.3.tgz#e0437d60ac10746bfbdf080e6809ba6f20d01556" + integrity sha512-0I7pApDr1/RLAKbizuLy/IHTEa93LSPy/bEwYniboC3Xqnp6Od8xFJKbKEzGw2wh/5zKFFwl00g4t9RwgIMc3w== -"@react-pdf/font@^2.3.6": - version "2.3.6" - resolved "https://registry.npmjs.org/@react-pdf/font/-/font-2.3.6.tgz" - integrity sha512-JYV+KmVyG2tPdpCK0/iFiBy1V7VHz2fETttKCgTRsLAo+w8RpM0pUGSAYROSuRl7yqbhiKGw/A24PYWhBReiOQ== +"@react-pdf/font@^4.0.8": + version "4.0.8" + resolved "https://registry.yarnpkg.com/@react-pdf/font/-/font-4.0.8.tgz#2279fb487f8a532e8b82e11732703a904ad05bb3" + integrity sha512-deNd+emtZAJho1IlzKL9bRoLAGv/6oXOIKO2oZfs4RuXUrK1onLHbJO7e2YoVLPFP/sQxisRTnzdJFtd35iKwA== dependencies: - "@babel/runtime" "^7.20.13" - "@react-pdf/types" "^2.3.3" - cross-fetch "^3.1.5" + "@react-pdf/pdfkit" "^5.1.1" + "@react-pdf/types" "^2.11.1" fontkit "^2.0.2" is-url "^1.2.4" -"@react-pdf/image@^2.2.1": - version "2.2.1" - resolved "https://registry.npmjs.org/@react-pdf/image/-/image-2.2.1.tgz" - integrity sha512-f0+cEP6pSBmk8eS/wP2tMsJcv2c7xjzca6cr1kwcapr1nzkPrh6fMdEeFl6kR2/HlJK/JoHo+xxlzRiQ8V2lrw== - dependencies: - "@babel/runtime" "^7.20.13" - "@react-pdf/png-js" "^2.2.0" - cross-fetch "^3.1.5" - -"@react-pdf/layout@^3.6.2": - version "3.6.2" - resolved "https://registry.npmjs.org/@react-pdf/layout/-/layout-3.6.2.tgz" - integrity sha512-YD3/tDC6p5XPCXI04zH79bgX8LytjxEYfeCtsIzEFk0A2VvIHoRnRRDZ2OhZmO5g112ykyjY8vn9//ubTt+Ktg== - dependencies: - "@babel/runtime" "^7.20.13" - "@react-pdf/fns" "2.0.1" - "@react-pdf/image" "^2.2.1" - "@react-pdf/pdfkit" "^3.0.2" - "@react-pdf/primitives" "^3.0.0" - "@react-pdf/stylesheet" "^4.1.7" - "@react-pdf/textkit" "^4.2.0" - "@react-pdf/types" "^2.3.3" - "@react-pdf/yoga" "^4.1.2" - cross-fetch "^3.1.5" - emoji-regex "^10.2.1" +"@react-pdf/image@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@react-pdf/image/-/image-3.1.0.tgz#1ab51db9a6341ffde5049be3ef55d44c3dc777d8" + integrity sha512-ks7Ry8v711r8NvKWSELehj0BXBNPRihSnWsM09nDD8Ur175zbWBCK217LLwQMKDNYDVpkZaipdoJPom1LGaE9g== + dependencies: + "@react-pdf/svg" "^1.1.0" + jay-peg "^1.1.1" + png-js "^2.0.0" + +"@react-pdf/layout@^4.6.1": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@react-pdf/layout/-/layout-4.6.1.tgz#6777fafa2a47996d4b42de37ddd324ea1aff4557" + integrity sha512-gN6PmWoEffvlIkifLfEhMsVucRywVMyH3rnxdyOVOhGy0nWJKKGpHyPc4plbDdpP6EfZ0r8prHXujDSkIG2nSA== + dependencies: + "@react-pdf/fns" "3.1.3" + "@react-pdf/image" "^3.1.0" + "@react-pdf/primitives" "^4.3.0" + "@react-pdf/stylesheet" "^6.2.1" + "@react-pdf/textkit" "^6.3.0" + "@react-pdf/types" "^2.11.1" + emoji-regex-xs "^1.0.0" queue "^6.0.1" + yoga-layout "^3.2.1" -"@react-pdf/pdfkit@^3.0.2": - version "3.0.2" - resolved "https://registry.npmjs.org/@react-pdf/pdfkit/-/pdfkit-3.0.2.tgz" - integrity sha512-+m5rwNCwyEH6lmnZWpsQJvdqb6YaCCR0nMWrc/KKDwznuPMrGmGWyNxqCja+bQPORcHZyl6Cd/iFL0glyB3QGw== +"@react-pdf/pdfkit@^5.1.1": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@react-pdf/pdfkit/-/pdfkit-5.1.1.tgz#b3af968f94555a3d7c4cc1d5b81493bdbfbd76e0" + integrity sha512-wNcdSsNlNYyGHGAgIdt453egBF7fiF9UxpRlklUfVvu8OWCrUppG9xiUrPLVoKiqWet5tMi0w6LmuFUJuYqjEg== dependencies: "@babel/runtime" "^7.20.13" - "@react-pdf/png-js" "^2.2.0" + "@noble/ciphers" "^1.0.0" + "@noble/hashes" "^1.6.0" browserify-zlib "^0.2.0" - crypto-js "^4.0.0" fontkit "^2.0.2" + jay-peg "^1.1.1" + js-md5 "^0.8.3" + linebreak "^1.1.0" + png-js "^2.0.0" vite-compatible-readable-stream "^3.6.1" -"@react-pdf/png-js@^2.2.0": - version "2.2.0" - resolved "https://registry.npmjs.org/@react-pdf/png-js/-/png-js-2.2.0.tgz" - integrity sha512-csZU5lfNW73tq7s7zB/1rWXGro+Z9cQhxtsXwxS418TSszHUiM6PwddouiKJxdGhbVLjRIcuuFVa0aR5cDOC6w== - dependencies: - browserify-zlib "^0.2.0" +"@react-pdf/primitives@^4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@react-pdf/primitives/-/primitives-4.3.0.tgz#3bb5f74294bea923392499dd46bc5196d47b918c" + integrity sha512-nYXoZ36pvwNzbc54+DbL8RCn15jU7woJ9D/svnh5tpUXekJ+CbI4mZLo6boSv24CvJgychOu6h7gxX03B4ps0A== -"@react-pdf/primitives@^3.0.0": - version "3.0.1" - resolved "https://registry.npmjs.org/@react-pdf/primitives/-/primitives-3.0.1.tgz" - integrity sha512-0HGcknrLNwyhxe+SZCBL29JY4M85mXKdvTZE9uhjNbADGgTc8wVnkc5+e4S/lDvugbVISXyuIhZnYwtK9eDnyQ== +"@react-pdf/reconciler@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@react-pdf/reconciler/-/reconciler-2.0.0.tgz#d53ba53d5418c275c1fe1b150f0e9822243b799a" + integrity sha512-7zaPRujpbHSmCpIrZ+b9HSTJHthcVZzX0Wx7RzvQGsGBUbHP4p6s5itXrAIOuQuPvDepoHGNOvf6xUuMVvdoyw== + dependencies: + object-assign "^4.1.1" + scheduler "0.25.0-rc-603e6108-20241029" -"@react-pdf/render@^3.2.6": - version "3.2.6" - resolved "https://registry.npmjs.org/@react-pdf/render/-/render-3.2.6.tgz" - integrity sha512-nsd1sleWMzBdrYGv5BwChPgVwoTZilfdiadE5wQiblFqG1C7EYINadalnEl1tjldKAzofSPBLKJbnSGR5r2lIQ== +"@react-pdf/render@^4.5.1": + version "4.5.1" + resolved "https://registry.yarnpkg.com/@react-pdf/render/-/render-4.5.1.tgz#7d94bfb96f4abe0cac28f769e296db909c8c456f" + integrity sha512-IW/N4HWJWtioBXCf7n02IR24VJJ8gbdS3jGypf+vW/rSErEx3/URRzh9UK6Ma8Fpog9+T/W6GE2NHJ5AAKHhVA== dependencies: "@babel/runtime" "^7.20.13" - "@react-pdf/fns" "2.0.1" - "@react-pdf/primitives" "^3.0.0" - "@react-pdf/textkit" "^4.2.0" - "@react-pdf/types" "^2.3.3" + "@react-pdf/fns" "3.1.3" + "@react-pdf/primitives" "^4.3.0" + "@react-pdf/textkit" "^6.3.0" + "@react-pdf/types" "^2.11.1" abs-svg-path "^0.1.1" - color-string "^1.5.3" + color-string "^2.1.4" normalize-svg-path "^1.1.0" parse-svg-path "^0.1.2" svg-arc-to-cubic-bezier "^3.2.0" -"@react-pdf/renderer@^3.1.11": - version "3.1.12" - resolved "https://registry.npmjs.org/@react-pdf/renderer/-/renderer-3.1.12.tgz" - integrity sha512-y4H2ELH0okJP7+ig+uNjFAfuAanWiF3mxsKsE7ZBuhF4tabbMt9fX+pQ7qn4xrzEWX0vlso6s6ebkkeGdSGWzA== +"@react-pdf/renderer@^4.4.1": + version "4.5.1" + resolved "https://registry.yarnpkg.com/@react-pdf/renderer/-/renderer-4.5.1.tgz#3daa9caa572ea8c42beece01b6faa348b460d304" + integrity sha512-5r1VQrE6FRLXX5wWUxwZzM24E2BJMo6g8AQWuS8WyPs9ugu5yMnb2g8/RpPYka/Z6J+RUEWc32wty2NoUJF42Q== dependencies: "@babel/runtime" "^7.20.13" - "@react-pdf/font" "^2.3.6" - "@react-pdf/layout" "^3.6.2" - "@react-pdf/pdfkit" "^3.0.2" - "@react-pdf/primitives" "^3.0.0" - "@react-pdf/render" "^3.2.6" - "@react-pdf/types" "^2.3.3" + "@react-pdf/fns" "3.1.3" + "@react-pdf/font" "^4.0.8" + "@react-pdf/layout" "^4.6.1" + "@react-pdf/pdfkit" "^5.1.1" + "@react-pdf/primitives" "^4.3.0" + "@react-pdf/reconciler" "^2.0.0" + "@react-pdf/render" "^4.5.1" + "@react-pdf/types" "^2.11.1" events "^3.3.0" object-assign "^4.1.1" prop-types "^15.6.2" queue "^6.0.1" - scheduler "^0.17.0" -"@react-pdf/stylesheet@^4.1.7": - version "4.1.7" - resolved "https://registry.npmjs.org/@react-pdf/stylesheet/-/stylesheet-4.1.7.tgz" - integrity sha512-3n0Vg0XFszPyo0MpH75DkLRvsS4JOE0HzBH6XqFHDiquZDrC4mNgmMhZEbsOED+8xDGoCeVh8fLU3L6Tu0HWqg== +"@react-pdf/stylesheet@^6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@react-pdf/stylesheet/-/stylesheet-6.2.1.tgz#baa87869c0cc953fb334d20c63359672b872475c" + integrity sha512-2+UEk+7e+z8baaWi2l5kPLWmwtJeOI+T5wW9GGeN3iDH7vd3kbTqOpN1yt9mmfNVZFxQsnDHpznFb5v5UF983A== dependencies: - "@babel/runtime" "^7.20.13" - "@react-pdf/fns" "2.0.1" - "@react-pdf/types" "^2.3.3" - color-string "^1.5.3" + "@react-pdf/fns" "3.1.3" + "@react-pdf/types" "^2.11.1" + color-string "^2.1.4" hsl-to-hex "^1.0.0" media-engine "^1.0.3" postcss-value-parser "^4.1.0" -"@react-pdf/textkit@^4.2.0": - version "4.2.0" - resolved "https://registry.npmjs.org/@react-pdf/textkit/-/textkit-4.2.0.tgz" - integrity sha512-R90pEOW6NdhUx4p99iROvKmwB06IRYdXMhh0QcmUeoPOLe64ZdMfs3LZliNUWgI5fCmq71J+nv868i/EakFPDg== +"@react-pdf/svg@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@react-pdf/svg/-/svg-1.1.0.tgz#c3ba275d312f7e2862f2a7044944b46c44c35f41" + integrity sha512-cTIHXiz9x1HrbfqzfxfZP3FRdDwUXG77QWF6Fb5MP/lV3ONxR+g0Z3hwtBatCS9HeGBQCpxX/Lzb8wHE+co1PA== dependencies: - "@babel/runtime" "^7.20.13" - "@react-pdf/fns" "2.0.1" + "@react-pdf/primitives" "^4.3.0" + +"@react-pdf/textkit@^6.3.0": + version "6.3.0" + resolved "https://registry.yarnpkg.com/@react-pdf/textkit/-/textkit-6.3.0.tgz#fe685654f557ff861008e09308db4a2a57f6bc42" + integrity sha512-v6+V8nAcVwm7s2s1jIG2MD3Iw//x/k+XrH1foWOELBE4b32pyDgKyPXN/6KJE0dnX7+fVy27uctLNCLNMvzKzQ== + dependencies: + "@react-pdf/fns" "3.1.3" + bidi-js "^1.0.2" hyphen "^1.6.4" unicode-properties "^1.4.1" -"@react-pdf/types@^2.3.3": - version "2.3.3" - resolved "https://registry.npmjs.org/@react-pdf/types/-/types-2.3.3.tgz" - integrity sha512-I3BVu5vF0xxX6rvqZHt4gCjFAt6X+mak5bwYQyf6bm21IIMDXXBtgXqWEl1wosWizArox7fcN/XbEnysrf/8Dw== - -"@react-pdf/yoga@^4.1.2": - version "4.1.2" - resolved "https://registry.npmjs.org/@react-pdf/yoga/-/yoga-4.1.2.tgz" - integrity sha512-OlMZkFrJDj4GyKZ70thiObwwPVZ52B7mlPyfzwa+sgwsioqHXg9nMWOO+7SQFNUbbOGagMUu0bCuTv+iXYZuaQ== +"@react-pdf/types@^2.11.1": + version "2.11.1" + resolved "https://registry.yarnpkg.com/@react-pdf/types/-/types-2.11.1.tgz#ae37a12a883ae2d54a2b98b9b9ba32fbf4241478" + integrity sha512-i9xQgfaDU9QoeNnbp6rltXCWg1huEh195rpOuN8cE4BZ2FuLdQrsIcb2dhFF9aOxXf+XBA6LOSpIW051MDD/bw== dependencies: - "@babel/runtime" "^7.20.13" + "@react-pdf/font" "^4.0.8" + "@react-pdf/primitives" "^4.3.0" + "@react-pdf/stylesheet" "^6.2.1" "@sentry-internal/browser-utils@8.55.1": version "8.55.1" @@ -3356,6 +3369,11 @@ balanced-match@^1.0.0: resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base64-js@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978" + integrity sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw== + base64-js@^1.1.2, base64-js@^1.3.0, base64-js@^1.3.1, base64-js@^1.5.1: version "1.5.1" resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" @@ -3391,6 +3409,13 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" +bidi-js@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/bidi-js/-/bidi-js-1.0.3.tgz#6f8bcf3c877c4d9220ddf49b9bb6930c88f877d2" + integrity sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw== + dependencies: + require-from-string "^2.0.2" + big.js@^5.2.2: version "5.2.2" resolved "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz" @@ -3899,18 +3924,22 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" -color-name@^1.0.0, color-name@~1.1.4: +color-name@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-2.1.0.tgz#0b677385c1c4b4edfdeaf77e38fa338e3a40b693" + integrity sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg== + +color-name@~1.1.4: version "1.1.4" resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-string@^1.5.3: - version "1.9.1" - resolved "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz" - integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== +color-string@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-2.1.4.tgz#9dcf566ff976e23368c8bd673f5c35103ab41058" + integrity sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg== dependencies: - color-name "^1.0.0" - simple-swizzle "^0.2.2" + color-name "^2.0.0" color-support@^1.1.2, color-support@^1.1.3: version "1.1.3" @@ -4135,13 +4164,6 @@ cross-fetch@^3.0.4: dependencies: node-fetch "2.6.7" -cross-fetch@^3.1.5: - version "3.1.6" - resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz" - integrity sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g== - dependencies: - node-fetch "^2.6.11" - cross-spawn@^7.0.3: version "7.0.6" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz" @@ -4151,7 +4173,7 @@ cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" -crypto-js@^4.0.0, crypto-js@^4.1.1: +crypto-js@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz" integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw== @@ -4776,10 +4798,10 @@ emittery@^0.10.2: resolved "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz" integrity sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw== -emoji-regex@^10.2.1: - version "10.2.1" - resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.2.1.tgz" - integrity sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA== +emoji-regex-xs@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz#e8af22e5d9dbd7f7f22d280af3d19d2aab5b0724" + integrity sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg== emoji-regex@^8.0.0: version "8.0.0" @@ -5226,6 +5248,11 @@ fbjs@^2.0.0: setimmediate "^1.0.5" ua-parser-js "^0.7.18" +fflate@^0.8.2: + version "0.8.3" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.3.tgz#bc27d8eb30343d4d512abb03480202ce65d825fc" + integrity sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA== + file-loader@^6.2.0: version "6.2.0" resolved "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz" @@ -6231,11 +6258,6 @@ is-arrayish@^0.2.1: resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= -is-arrayish@^0.3.1: - version "0.3.2" - resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz" - integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== - is-bigint@^1.0.1: version "1.0.4" resolved "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz" @@ -6653,6 +6675,13 @@ istanbul-reports@^3.1.3: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" +jay-peg@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/jay-peg/-/jay-peg-1.1.1.tgz#fdf410b89fa7a295bf74424ffe4c9083dbe7c363" + integrity sha512-D62KEuBxz/ip2gQKOEhk/mx14o7eiFRaU+VNNSP4MOiIkwb/D6B3G1Mfas7C/Fit8EsSV2/IWjZElx/Gs6A4ww== + dependencies: + restructure "^3.0.0" + jest-changed-files@^28.0.2: version "28.0.2" resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-28.0.2.tgz" @@ -7145,6 +7174,11 @@ js-cookie@^3.0.5: resolved "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz" integrity sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw== +js-md5@^0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/js-md5/-/js-md5-0.8.3.tgz#921bab7efa95bfc9d62b87ee08a57f8fe4305b69" + integrity sha512-qR0HB5uP6wCuRMrWPTrkMaev7MJZwJuuw4fnwAzRgP4J4/F8RwtodOKpGp4XpqsLBFzzgqIO42efFAyz2Et6KQ== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" @@ -7383,6 +7417,14 @@ lilconfig@^2.0.3: resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.5.tgz" integrity sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg== +linebreak@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/linebreak/-/linebreak-1.1.0.tgz#831cf378d98bced381d8ab118f852bd50d81e46b" + integrity sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ== + dependencies: + base64-js "0.0.8" + unicode-trie "^2.0.0" + lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" @@ -7996,7 +8038,7 @@ node-fetch@^1.0.1: encoding "^0.1.11" is-stream "^1.0.1" -node-fetch@^2.6.11, node-fetch@^2.6.7: +node-fetch@^2.6.7: version "2.6.11" resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz" integrity sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w== @@ -8537,6 +8579,13 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +png-js@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/png-js/-/png-js-2.0.0.tgz#7bc521aea1d47f5e3bf42eeecdd77095cba98d5f" + integrity sha512-GdzJuUMc6ZSpxFJWVxtOH1bzYHym+TOnveqUjb+VJIbZWbZzyiRGFiKhbiielfpYbgMlhHVhsJ0FTazfuRFkMA== + dependencies: + fflate "^0.8.2" + posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz" @@ -9680,13 +9729,10 @@ saxes@^5.0.1: dependencies: xmlchars "^2.2.0" -scheduler@^0.17.0: - version "0.17.0" - resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.17.0.tgz" - integrity sha512-7rro8Io3tnCPuY4la/NuI5F2yfESpnfZyT6TtkXnSWVkcu0BCDJ+8gk5ozUaFaxpIyNuWAPXrH0yFcSi28fnDA== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" +scheduler@0.25.0-rc-603e6108-20241029: + version "0.25.0-rc-603e6108-20241029" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.25.0-rc-603e6108-20241029.tgz#684dd96647e104d23e0d29a37f18937daf82df19" + integrity sha512-pFwF6H1XrSdYYNLfOcGlM28/j8CGLu8IvdrxqhjWULe2bPcKiKW4CV+OWqR/9fT52mywx65l7ysNkjLKBda7eA== scheduler@^0.20.2: version "0.20.2" @@ -9965,13 +10011,6 @@ signal-exit@^3.0.0, signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== -simple-swizzle@^0.2.2: - version "0.2.2" - resolved "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz" - integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== - dependencies: - is-arrayish "^0.3.1" - sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz" @@ -11360,3 +11399,8 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +yoga-layout@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/yoga-layout/-/yoga-layout-3.2.1.tgz#d2d1ba06f0e81c2eb650c3e5ad8b0b4adde1e843" + integrity sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==