diff --git a/.cursor/prompts/playwright-mcp.md b/.cursor/prompts/playwright-mcp.md new file mode 100644 index 0000000..433ff3e --- /dev/null +++ b/.cursor/prompts/playwright-mcp.md @@ -0,0 +1,13 @@ +- You are a playwright test generator. +- You are given a scenario and you need to generate a playwright test for it. +- DO NOT generate test code based on the scenario alone. +- DO run steps one by one using the tools provided by the Playwright MCP. + +- When asked to explore a website: + 1. Navigate to the specified URL + 2. Explore 1 key functionality of the site and when finished close the browser. + 3. Implement a Playwright TypeScript test that uses @playwright/test based on message history using Playwright's best practices including role based locators, auto retrying assertions and with no added timeouts unless necessary as Playwright has built in retries and autowaiting if the correct locators and assertions are used. +- Save generated test file in the e2e directory +- Execute the test file and iterate until the test passes +- Include appropriate assertions to verify the expected behavior +- Structure tests properly with descriptive test titles and comments diff --git a/.env.example b/.env.example index 855ffc4..4a0fe4f 100644 --- a/.env.example +++ b/.env.example @@ -7,6 +7,9 @@ NEXT_PUBLIC_SENTRY_ENABLED="false" NEXT_PUBLIC_SENTRY_DSN="" +# dev mode only +NEXT_PUBLIC_DEBUG_LOCAL_STORAGE_UI="false" + # Resend (For emails) Create account and paste API keys in .env.local RESEND_API_KEY="" RESEND_AUDIENCE_ID="" diff --git a/.github/screenshots/stripe-tmp.png b/.github/screenshots/stripe-tmp.png new file mode 100644 index 0000000..cfa2648 Binary files /dev/null and b/.github/screenshots/stripe-tmp.png differ diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 0000000..2f0628a --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -0,0 +1,52 @@ +name: 🧪 Vitest Unit Tests +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + unit-tests: + name: Run unit tests + timeout-minutes: 10 + runs-on: ubuntu-latest + steps: + # we use pinned versions because there are safer to use: https://x.com/paulmillr/status/1900948425325031448 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + name: 🛎️ Checkout repository + + - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + name: 📦 Setup pnpm + + - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # 4.3.0 + name: 📚 Setup Node.js + with: + node-version: lts/* + cache: "pnpm" + + - name: 🚚 Install dependencies + run: pnpm install + + - name: 🧪 Run Vitest tests + id: vitest + run: pnpm vitest run --reporter=verbose + + - name: 📧 Send email on failure + if: failure() + uses: dawidd6/action-send-mail@611879133a9569642c41be66f4a323286e9b8a3b # v4 + with: + server_address: smtp.gmail.com + server_port: 587 + from: GitHub Actions + to: ${{ secrets.EMAIL_USERNAME }} + username: ${{ secrets.EMAIL_USERNAME }} + password: ${{ secrets.EMAIL_PASSWORD }} + subject: ❌ Unit Tests Failed for ${{ github.repository }} + body: | + Unit tests for ${{ github.repository }} have failed. + + Branch: ${{ github.ref_name }} + Commit: ${{ github.sha }} + + For more details, please check: + - GitHub Actions run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} diff --git a/.prettierrc.js b/.prettierrc.js index fafae7a..b6049d4 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -2,7 +2,15 @@ /** @typedef {import("@ianvs/prettier-plugin-sort-imports").PluginConfig} SortImportsConfig*/ const config = { - trailingComma: "es5", + trailingComma: "all", + singleQuote: false, + semi: true, + arrowParens: "always", + bracketSpacing: true, + endOfLine: "lf", + printWidth: 80, + tabWidth: 2, + useTabs: false, plugins: [require.resolve("prettier-plugin-tailwindcss")], }; diff --git a/README.md b/README.md index a16d5b8..abd863c 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,19 @@ -# [EasyInvoicePDF](https://easyinvoicepdf.com) +# 🧾 [EasyInvoicePDF](https://easyinvoicepdf.com) -[EasyInvoicePDF](https://easyinvoicepdf.com) – Free & Open-Source Invoice Generator | Live Preview, No Sign-Up, Runs in Your Browser. +> Free & Open-Source Invoice Generator. Create professional invoices instantly in your browser with live preview, multiple templates, and no sign-up required. **[Try it now → easyinvoicepdf.com](https://easyinvoicepdf.com)** -❤️ Love EasyInvoicePDF? Help keep it free and open-source! [Buy me a coffee](https://buymeacoffee.com/vladsazon) to support new features, better templates, and continued maintenance. Even a small contribution makes a big difference. Thank you for being part of this journey! ✨ +## Features + +- ⚡ **Live Preview**: See changes in real-time as you type +- 🔗 **Shareable Links**: Send invoices directly to clients without attachments +- ⭐ **No Sign-Up Required**: Start creating invoices immediately without any registration +- 📱 **Browser Only**: No server uploads, your data stays private +- 🌍 **Multi-Language**: Support for 10+ languages and all major currencies +- 🧮 **European VAT**: Automatic VAT calculation and formatting +- 🎨 **Multiple Templates**: Including modern **Stripe-style design** +- 📄 **Instant PDF**: One-click download ready for printing or sending + +**❤️ Support the project**: [Buy me a coffee](https://buymeacoffee.com/vladsazon) to help keep EasyInvoicePDF free and open-source! #### Default Invoice Template @@ -10,16 +21,7 @@ #### Stripe Invoice Template -stripe template - -## Features - -- **Live Preview**: See your invoice update in real-time as you make changes, ensuring it looks exactly how you want. -- **Shareable Links**: Generate links to share your invoices directly with clients without sending attachments. -- **Instant Download**: Download your invoice as a PDF file with one click, ready to be sent or printed. -- **Browser Only**: Runs entirely in your browser—no server-side processing or data storage. Your data stays private and secure. -- **Multiple Languages & Currencies**: Create invoices in multiple languages with support for all major currencies and automatic formatting. -- **European VAT Support**: Automatically calculate European VAT rates and totals for your invoices. +stripe template ## Demo Video 🎥 diff --git a/e2e/about.test.ts b/e2e/about.test.ts index 55b4d05..8957b9e 100644 --- a/e2e/about.test.ts +++ b/e2e/about.test.ts @@ -14,7 +14,7 @@ test.describe("About page", () => { await expect(page).toHaveURL("/en/about"); await expect(page).toHaveTitle( - "About | Free Invoice Generator – Live Preview, No Sign-Up" + "About | Free Invoice Generator – Live Preview, No Sign-Up", ); const header = page.getByRole("banner"); @@ -35,19 +35,19 @@ test.describe("About page", () => { level: 1, name: "Create professional invoices in seconds", exact: true, - }) + }), ).toBeVisible(); await expect( heroSection.getByText( - "EasyInvoicePDF is a free, open-source tool that lets you create, customize, and download professional invoices with real-time preview." - ) + "EasyInvoicePDF is a free, open-source tool that lets you create, customize, and download professional invoices with real-time preview.", + ), ).toBeVisible(); await expect( heroSection .getByText("No sign-up required. 100% free and open-source.") - .filter({ visible: true }) + .filter({ visible: true }), ).toBeVisible(); const video = heroSection.getByTestId("hero-about-page-video"); @@ -55,7 +55,7 @@ test.describe("About page", () => { await expect(video).toBeVisible(); await expect(video).toHaveAttribute( "poster", - `${STATIC_ASSETS_URL}/easy-invoice-video-placeholder.webp` + `${STATIC_ASSETS_URL}/easy-invoice-video-placeholder.webp`, ); await expect(video).toHaveAttribute("muted"); await expect(video).toHaveAttribute("loop"); @@ -66,7 +66,7 @@ test.describe("About page", () => { const videoSource = video.locator("source"); await expect(videoSource).toHaveAttribute( "src", - `${VIDEO_DEMO_URL}#t=0.001` + `${VIDEO_DEMO_URL}#t=0.001`, ); await expect(videoSource).toHaveAttribute("type", "video/mp4"); @@ -75,25 +75,25 @@ test.describe("About page", () => { await expect(featuresSection).toBeVisible(); await expect( - featuresSection.getByTestId("features-coming-soon") - ).toHaveText("Pro version and API coming soon"); + featuresSection.getByTestId("features-coming-soon"), + ).toHaveText("E-invoices support coming soon"); await expect( featuresSection.getByRole("heading", { level: 2, name: "Everything you need for professional invoicing", exact: true, - }) + }), ).toBeVisible(); await expect( featuresSection.getByText( - "Our simple yet powerful invoice generator includes all the features you need to create professional invoices quickly." - ) + "Our simple yet powerful invoice generator includes all the features you need to create professional invoices quickly.", + ), ).toBeVisible(); await expect( - featuresSection.getByText("Pro version and API coming soon") + featuresSection.getByText("E-invoices support coming soon"), ).toBeVisible(); // check FAQ section @@ -105,7 +105,7 @@ test.describe("About page", () => { level: 2, name: "Frequently Asked Questions", exact: true, - }) + }), ).toBeVisible(); await expect(faqSection.getByText("What is EasyInvoicePDF?")).toBeVisible(); @@ -120,13 +120,13 @@ test.describe("About page", () => { level: 2, name: "Subscribe to our newsletter", exact: true, - }) + }), ).toBeVisible(); await expect( subscribeFormSection.getByText( - "Get updates on new features and improvements from EasyInvoicePDF.com" - ) + "Get updates on new features and improvements from EasyInvoicePDF.com", + ), ).toBeVisible(); const subscribeForm = subscribeFormSection.getByTestId("subscribe-form"); @@ -140,7 +140,7 @@ test.describe("About page", () => { await expect(subscribeFormEmailInput).toHaveAttribute("required"); await expect(subscribeFormEmailInput).toHaveAttribute( "autocomplete", - "email" + "email", ); const subscribeFormButton = subscribeForm.getByRole("button", { @@ -180,18 +180,18 @@ test.describe("About page", () => { await expect(footer.getByText("Subscribe to our newsletter")).toBeVisible(); await expect( - footer.getByText("All emails will be sent in English") + footer.getByText("All emails will be sent in English"), ).toBeVisible(); const newsletterForm = footer.getByTestId("subscribe-form"); await expect(newsletterForm).toBeVisible(); await expect( - newsletterForm.getByPlaceholder("Enter your email") + newsletterForm.getByPlaceholder("Enter your email"), ).toBeVisible(); await expect( - newsletterForm.getByRole("button", { name: "Subscribe" }) + newsletterForm.getByRole("button", { name: "Subscribe" }), ).toBeVisible(); // now check all the rest of the footer links @@ -238,7 +238,7 @@ test.describe("About page", () => { await expect(shareFeedbackLink).toBeVisible(); await expect(shareFeedbackLink).toHaveAttribute( "href", - "https://pdfinvoicegenerator.userjot.com/?cursor=1&order=top&limit=10" + "https://pdfinvoicegenerator.userjot.com/?cursor=1&order=top&limit=10", ); await expect(shareFeedbackLink).toHaveAttribute("target", "_blank"); @@ -276,19 +276,19 @@ test.describe("About page", () => { level: 1, name: "Créez des factures professionnelles en quelques secondes", exact: true, - }) + }), ).toBeVisible(); await expect( heroSection.getByText( - "EasyInvoicePDF est un outil gratuit et open-source qui vous permet de créer, personnaliser et télécharger des factures professionnelles avec aperçu en temps réel. Fonctionne entièrement dans votre navigateur." - ) + "EasyInvoicePDF est un outil gratuit et open-source qui vous permet de créer, personnaliser et télécharger des factures professionnelles avec aperçu en temps réel. Fonctionne entièrement dans votre navigateur.", + ), ).toBeVisible(); await expect( heroSection .getByText("Aucune inscription requise. 100% gratuit et open-source.") - .filter({ visible: true }) + .filter({ visible: true }), ).toBeVisible(); // Check Features section in French @@ -296,11 +296,11 @@ test.describe("About page", () => { await expect(featuresSection).toBeVisible(); await expect(featuresSection.getByTestId("features-badge")).toHaveText( - "Fonctionnalités" + "Fonctionnalités", ); await expect( - featuresSection.getByTestId("features-coming-soon") + featuresSection.getByTestId("features-coming-soon"), ).toHaveText("Version Pro et API bientôt disponibles"); await expect( @@ -308,7 +308,7 @@ test.describe("About page", () => { level: 2, name: "Tout ce dont vous avez besoin pour une facturation professionnelle", exact: true, - }) + }), ).toBeVisible(); // check subscribe form section in French @@ -320,13 +320,13 @@ test.describe("About page", () => { level: 2, name: "Abonnez-vous à notre newsletter", exact: true, - }) + }), ).toBeVisible(); await expect( subscribeFormSection.getByText( - "Recevez des mises à jour sur les nouvelles fonctionnalités et améliorations de EasyInvoicePDF.com" - ) + "Recevez des mises à jour sur les nouvelles fonctionnalités et améliorations de EasyInvoicePDF.com", + ), ).toBeVisible(); // Check footer in French @@ -335,19 +335,19 @@ test.describe("About page", () => { // Check newsletter subscription form in French await expect( - footer.getByText("Abonnez-vous à notre newsletter") + footer.getByText("Abonnez-vous à notre newsletter"), ).toBeVisible(); await expect( - footer.getByText("Tous les emails seront envoyés en anglais") + footer.getByText("Tous les emails seront envoyés en anglais"), ).toBeVisible(); const newsletterForm = footer.getByTestId("subscribe-form"); await expect(newsletterForm).toBeVisible(); await expect( - newsletterForm.getByPlaceholder("Entrez votre email") + newsletterForm.getByPlaceholder("Entrez votre email"), ).toBeVisible(); await expect( - newsletterForm.getByRole("button", { name: "S'abonner", exact: true }) + newsletterForm.getByRole("button", { name: "S'abonner", exact: true }), ).toBeVisible(); const footerLinks = footer.getByTestId("footer-social-links"); @@ -395,21 +395,21 @@ test.describe("About page", () => { level: 1, name: "Erstellen Sie professionelle Rechnungen in Sekunden", exact: true, - }) + }), ).toBeVisible(); await expect( heroSection.getByText( - "EasyInvoicePDF ist ein kostenloses Open-Source-Tool, mit dem Sie professionelle Rechnungen mit Echtzeit-Vorschau erstellen, anpassen und herunterladen können." - ) + "EasyInvoicePDF ist ein kostenloses Open-Source-Tool, mit dem Sie professionelle Rechnungen mit Echtzeit-Vorschau erstellen, anpassen und herunterladen können.", + ), ).toBeVisible(); await expect( heroSection .getByText( - "Keine Anmeldung erforderlich. 100% kostenlos und Open-Source." + "Keine Anmeldung erforderlich. 100% kostenlos und Open-Source.", ) - .filter({ visible: true }) + .filter({ visible: true }), ).toBeVisible(); // Check Features section in German @@ -417,11 +417,11 @@ test.describe("About page", () => { await expect(featuresSection).toBeVisible(); await expect(featuresSection.getByTestId("features-badge")).toHaveText( - "Funktionen" + "Funktionen", ); await expect( - featuresSection.getByTestId("features-coming-soon") + featuresSection.getByTestId("features-coming-soon"), ).toHaveText("Pro-Version und API in Kürze verfügbar"); await expect( @@ -429,7 +429,7 @@ test.describe("About page", () => { level: 2, name: "Alles, was Sie für professionelle Rechnungsstellung brauchen", exact: true, - }) + }), ).toBeVisible(); // Check footer in German @@ -439,21 +439,21 @@ test.describe("About page", () => { // Check newsletter subscription form in German await expect(footer.getByText("Newsletter abonnieren")).toBeVisible(); await expect( - footer.getByText("Alle E-Mails werden in englischer Sprache versendet") + footer.getByText("Alle E-Mails werden in englischer Sprache versendet"), ).toBeVisible(); const newsletterForm = footer.getByTestId("subscribe-form"); await expect(newsletterForm).toBeVisible(); await expect( - newsletterForm.getByPlaceholder("E-Mail eingeben") + newsletterForm.getByPlaceholder("E-Mail eingeben"), ).toBeVisible(); await expect( - newsletterForm.getByRole("button", { name: "Abonnieren", exact: true }) + newsletterForm.getByRole("button", { name: "Abonnieren", exact: true }), ).toBeVisible(); const footerLinks = footer.getByTestId("footer-social-links"); await expect( - footerLinks.getByRole("link", { name: "Funktionen", exact: true }) + footerLinks.getByRole("link", { name: "Funktionen", exact: true }), ).toBeVisible(); }); @@ -473,7 +473,7 @@ test.describe("About page", () => { header.getByRole("link", { name: "Aller à l'application", exact: true, - }) + }), ).toBeVisible(); await expect(page).toHaveURL("/fr/about"); }); diff --git a/e2e/buyer.test.ts b/e2e/buyer.test.ts index a71f48d..20c61ef 100644 --- a/e2e/buyer.test.ts +++ b/e2e/buyer.test.ts @@ -44,7 +44,7 @@ test.describe("Buyer management", () => { // Verify VAT visibility switch is checked by default await expect( - manageBuyerDialog.getByRole("switch", { name: "Show in PDF" }).nth(0) + manageBuyerDialog.getByRole("switch", { name: "Show in PDF" }).nth(0), ).toBeChecked(); // Toggle VAT visibility switch @@ -59,7 +59,7 @@ test.describe("Buyer management", () => { .fill(testData.notes); const notesSwitch = manageBuyerDialog.getByTestId( - `buyerNotesDialogFieldVisibilitySwitch` + `buyerNotesDialogFieldVisibilitySwitch`, ); await expect(notesSwitch).toHaveRole("switch"); @@ -71,12 +71,12 @@ test.describe("Buyer management", () => { await expect( manageBuyerDialog.getByRole("switch", { name: "Apply to Current Invoice", - }) + }), ).toBeChecked(); // Cancel button is shown await expect( - manageBuyerDialog.getByRole("button", { name: "Cancel" }) + manageBuyerDialog.getByRole("button", { name: "Cancel" }), ).toBeVisible(); // Save buyer @@ -84,7 +84,7 @@ test.describe("Buyer management", () => { // Verify success toast message is visible await expect( - page.getByText("Buyer added successfully", { exact: true }) + page.getByText("Buyer added successfully", { exact: true }), ).toBeVisible(); // Verify buyer data is actually saved in localStorage @@ -123,13 +123,13 @@ test.describe("Buyer management", () => { if (desktopTooltipExists) { // Get desktop tooltip icons and hover over the first one because we use tooltip const desktopTooltips = buyerForm.getByTestId( - "form-section-tooltip-info-icon-desktop" + "form-section-tooltip-info-icon-desktop", ); await desktopTooltips.first().hover(); } else { // Get mobile tooltip icons and click the first one because we use popover const mobileTooltips = buyerForm.getByTestId( - "form-section-tooltip-info-icon-mobile" + "form-section-tooltip-info-icon-mobile", ); await mobileTooltips.first().click(); } @@ -138,7 +138,7 @@ test.describe("Buyer management", () => { const nameInput = buyerForm.getByRole("textbox", { name: "Name" }); await expect(nameInput).toHaveAttribute( "title", - "Buyer details are locked. Click the Edit Buyer button (Pencil icon) to modify." + "Buyer details are locked. Click the Edit Buyer button (Pencil icon) to modify.", ); // Buyer Name @@ -147,18 +147,18 @@ test.describe("Buyer management", () => { // Buyer Address await expect( - buyerForm.getByRole("textbox", { name: "Address" }) + buyerForm.getByRole("textbox", { name: "Address" }), ).toHaveAttribute("aria-readonly", "true"); await expect( - buyerForm.getByRole("textbox", { name: "Address" }) + buyerForm.getByRole("textbox", { name: "Address" }), ).toHaveValue(testData.address); // Buyer VAT Number await expect( - buyerForm.getByRole("textbox", { name: "VAT Number" }) + buyerForm.getByRole("textbox", { name: "VAT Number" }), ).toHaveAttribute("aria-readonly", "true"); await expect( - buyerForm.getByRole("textbox", { name: "VAT Number" }) + buyerForm.getByRole("textbox", { name: "VAT Number" }), ).toHaveValue(testData.vatNo); const vatNumberSwitch = buyerForm.getByTestId(`buyerVatNoFieldIsVisible`); @@ -168,30 +168,30 @@ test.describe("Buyer management", () => { // Buyer Email await expect( - buyerForm.getByRole("textbox", { name: "Email" }) + buyerForm.getByRole("textbox", { name: "Email" }), ).toHaveAttribute("aria-readonly", "true"); await expect(buyerForm.getByRole("textbox", { name: "Email" })).toHaveValue( - testData.email + testData.email, ); // Buyer Notes await expect( - buyerForm.getByRole("textbox", { name: "Notes" }) + buyerForm.getByRole("textbox", { name: "Notes" }), ).toHaveAttribute("aria-readonly", "true"); await expect(buyerForm.getByRole("textbox", { name: "Notes" })).toHaveValue( - testData.notes + testData.notes, ); const notesInvoiceFormSwitch = buyerForm.getByTestId( - `buyerNotesInvoiceFormFieldVisibilitySwitch` + `buyerNotesInvoiceFormFieldVisibilitySwitch`, ); await expect(notesInvoiceFormSwitch).toBeChecked(); await expect(notesInvoiceFormSwitch).toBeDisabled(); // Verify the buyer appears in the dropdown await expect( - buyerForm.getByRole("combobox", { name: "Select Buyer" }) + buyerForm.getByRole("combobox", { name: "Select Buyer" }), ).toContainText(testData.name); // ------- TEST EDIT FUNCTIONALITY ------- @@ -199,31 +199,31 @@ test.describe("Buyer management", () => { // Verify all fields are populated in edit dialog await expect( - manageBuyerDialog.getByRole("textbox", { name: "Name" }) + manageBuyerDialog.getByRole("textbox", { name: "Name" }), ).toHaveValue(testData.name); await expect( - manageBuyerDialog.getByRole("textbox", { name: "Address" }) + manageBuyerDialog.getByRole("textbox", { name: "Address" }), ).toHaveValue(testData.address); await expect( - manageBuyerDialog.getByRole("textbox", { name: "VAT Number" }) + manageBuyerDialog.getByRole("textbox", { name: "VAT Number" }), ).toHaveValue(testData.vatNo); await expect( - manageBuyerDialog.getByRole("textbox", { name: "Email" }) + manageBuyerDialog.getByRole("textbox", { name: "Email" }), ).toHaveValue(testData.email); // Verify visibility switch state persisted in edit dialog await expect( - manageBuyerDialog.getByRole("switch", { name: "Show in PDF" }).nth(0) + manageBuyerDialog.getByRole("switch", { name: "Show in PDF" }).nth(0), ).not.toBeChecked(); // Verify notes text await expect( - manageBuyerDialog.getByRole("textbox", { name: "Notes" }) + manageBuyerDialog.getByRole("textbox", { name: "Notes" }), ).toHaveValue(testData.notes); // Verify notes visibility switch is checked const notesDialogFormSwitch = manageBuyerDialog.getByTestId( - `buyerNotesDialogFieldVisibilitySwitch` + `buyerNotesDialogFieldVisibilitySwitch`, ); await expect(notesDialogFormSwitch).toHaveRole("switch"); @@ -252,18 +252,18 @@ test.describe("Buyer management", () => { // Verify success toast for update await expect( - page.getByText("Buyer updated successfully", { exact: true }) + page.getByText("Buyer updated successfully", { exact: true }), ).toBeVisible(); // ------- TEST UPDATED INFORMATION IN INVOICE FORM ------- // Verify updated information is displayed await expect(buyerForm.getByRole("textbox", { name: "Name" })).toHaveValue( - updatedName + updatedName, ); // Verify VAT visibility is now enabled await expect( - buyerForm.getByTestId(`buyerVatNoFieldIsVisible`) + buyerForm.getByTestId(`buyerVatNoFieldIsVisible`), ).toBeChecked(); // Verify notes visibility switch is unchecked @@ -305,7 +305,7 @@ test.describe("Buyer management", () => { // Verify buyer was added const buyerForm = page.getByTestId(`buyer-information-section`); await expect( - buyerForm.getByRole("combobox", { name: "Select Buyer" }) + buyerForm.getByRole("combobox", { name: "Select Buyer" }), ).toContainText(testData.name); // Click delete button @@ -315,8 +315,8 @@ test.describe("Buyer management", () => { await expect(page.getByRole("alertdialog")).toBeVisible(); await expect( page.getByText( - `Are you sure you want to delete "${testData.name}" buyer?` - ) + `Are you sure you want to delete "${testData.name}" buyer?`, + ), ).toBeVisible(); // Cancel button is shown @@ -335,8 +335,8 @@ test.describe("Buyer management", () => { await expect(page.getByRole("alertdialog")).toBeVisible(); await expect( page.getByText( - `Are you sure you want to delete "${testData.name}" buyer?` - ) + `Are you sure you want to delete "${testData.name}" buyer?`, + ), ).toBeVisible(); // Confirm deletion @@ -344,30 +344,30 @@ test.describe("Buyer management", () => { // Verify success message await expect( - page.getByText("Buyer deleted successfully", { exact: true }) + page.getByText("Buyer deleted successfully", { exact: true }), ).toBeVisible(); // Verify buyer is removed from dropdown // because we have only one buyer, dropdown will be completely hidden await expect( - buyerForm.getByRole("combobox", { name: "Select Buyer" }) + buyerForm.getByRole("combobox", { name: "Select Buyer" }), ).toBeHidden(); // Verify form is reset to default values await expect(buyerForm.getByRole("textbox", { name: "Name" })).toHaveValue( - DEFAULT_BUYER_DATA.name + DEFAULT_BUYER_DATA.name, ); await expect( - buyerForm.getByRole("textbox", { name: "Address" }) + buyerForm.getByRole("textbox", { name: "Address" }), ).toHaveValue(DEFAULT_BUYER_DATA.address); await expect(buyerForm.getByRole("textbox", { name: "Email" })).toHaveValue( - DEFAULT_BUYER_DATA.email + DEFAULT_BUYER_DATA.email, ); await expect( - buyerForm.getByRole("textbox", { name: "VAT Number" }) + buyerForm.getByRole("textbox", { name: "VAT Number" }), ).toHaveValue(DEFAULT_BUYER_DATA.vatNo); }); }); diff --git a/e2e/changelog.test.ts b/e2e/changelog.test.ts index 38940cb..28d755a 100644 --- a/e2e/changelog.test.ts +++ b/e2e/changelog.test.ts @@ -13,14 +13,14 @@ test.describe("Changelog page", () => { level: 1, name: "Changelog", exact: true, - }) + }), ).toBeVisible(); // Check subtitle await expect( page.getByText( - "All the latest updates, improvements, and fixes to EasyInvoicePDF" - ) + "All the latest updates, improvements, and fixes to EasyInvoicePDF", + ), ).toBeVisible(); // Check that changelog entries are present @@ -49,7 +49,7 @@ test.describe("Changelog page", () => { // Check that we're on an individual entry page by looking for "Back to All Posts" link await expect( - page.getByRole("link", { name: "Back to All Posts" }) + page.getByRole("link", { name: "Back to All Posts" }), ).toBeVisible(); }); @@ -84,24 +84,24 @@ test.describe("Changelog page", () => { // Check author information await expect(page.getByTestId("author-info-text")).toHaveText( - "Vlad SazonauFounder, EasyInvoicePDF" + "Vlad SazonauFounder, EasyInvoicePDF", ); await expect(page.getByTestId("author-info-img")).toBeVisible(); // Check social sharing buttons are present const twitterShareLink = page.locator( - 'a[href*="twitter.com/intent/tweet"]' + 'a[href*="twitter.com/intent/tweet"]', ); await expect(twitterShareLink).toBeVisible(); const linkedinShareLink = page.locator( - 'a[href*="linkedin.com/shareArticle"]' + 'a[href*="linkedin.com/shareArticle"]', ); await expect(linkedinShareLink).toBeVisible(); // Check "Go to App" CTA button const goToAppButtonContainer = page.getByTestId( - "go-to-app-button-container" + "go-to-app-button-container", ); const goToAppButton = goToAppButtonContainer.getByRole("link"); @@ -133,7 +133,7 @@ test.describe("Changelog page", () => { // Verify we're back on the main changelog page await expect(page).toHaveURL("/changelog"); await expect( - page.getByRole("heading", { level: 1, name: "Changelog" }) + page.getByRole("heading", { level: 1, name: "Changelog" }), ).toBeVisible(); }); @@ -151,7 +151,7 @@ test.describe("Changelog page", () => { // Verify the page loads correctly await expect( - page.getByRole("link", { name: "Back to All Posts" }) + page.getByRole("link", { name: "Back to All Posts" }), ).toBeVisible(); // Verify we can still navigate back diff --git a/e2e/default-invoice-template/pdf.test.ts b/e2e/default-invoice-template/pdf.test.ts index 55909d6..e6fa376 100644 --- a/e2e/default-invoice-template/pdf.test.ts +++ b/e2e/default-invoice-template/pdf.test.ts @@ -91,7 +91,7 @@ test.describe("PDF Preview", () => { // Save the file to a browser-specific temporary location const tmpPath = path.join( getDownloadDir({ browserName }), - suggestedFilename + suggestedFilename, ); await download.saveAs(tmpPath); @@ -108,12 +108,12 @@ test.describe("PDF Preview", () => { // Check invoice header details expect(pdfData.text).toContain( - `Invoice No. of: 1/${CURRENT_MONTH_AND_YEAR}` + `Invoice No. of: 1/${CURRENT_MONTH_AND_YEAR}`, ); expect(pdfData.text).toContain("Reverse Charge"); expect(pdfData.text).toContain(`Date of issue: ${TODAY}`); expect(pdfData.text).toContain( - `Date of sales/of executing the service: ${LAST_DAY_OF_CURRENT_MONTH}` + `Date of sales/of executing the service: ${LAST_DAY_OF_CURRENT_MONTH}`, ); // Check seller details @@ -154,7 +154,7 @@ test.describe("PDF Preview", () => { // Check page footer and metadata expect(pdfData.text).toContain( - `1/${CURRENT_MONTH_AND_YEAR}·€0.00 due ${PAYMENT_DATE}·Created with https://easyinvoicepdf.comPage 1 of 1` + `1/${CURRENT_MONTH_AND_YEAR}·€0.00 due ${PAYMENT_DATE}·Created with https://easyinvoicepdf.comPage 1 of 1`, ); }); @@ -195,7 +195,7 @@ test.describe("PDF Preview", () => { const pdfData = await pdf(dataBuffer); expect((pdfData.info as { Title: string }).Title).toContain( - `Faktura nr: 1/${CURRENT_MONTH_AND_YEAR} | Created with https://easyinvoicepdf.com` + `Faktura nr: 1/${CURRENT_MONTH_AND_YEAR} | Created with https://easyinvoicepdf.com`, ); // Verify PDF content @@ -209,7 +209,7 @@ test.describe("PDF Preview", () => { const lastDayOfCurrentMonth = dayjs().endOf("month").format("YYYY-MM-DD"); expect(pdfData.text).toContain( - `Data sprzedaży / wykonania usługi: ${lastDayOfCurrentMonth}` + `Data sprzedaży / wykonania usługi: ${lastDayOfCurrentMonth}`, ); expect(pdfData.text).toContain(`Razem do zapłaty: 0.00 EUR @@ -218,7 +218,7 @@ Pozostało do zapłaty: 0.00 EUR Kwota słownie: zero EUR 00/100`); expect(pdfData.text).toContain( - `1/${CURRENT_MONTH_AND_YEAR}·0,00 € do zapłaty do ${PAYMENT_DATE}·Utworzono za pomocą https://easyinvoicepdf.comStrona 1 z 1` + `1/${CURRENT_MONTH_AND_YEAR}·0,00 € do zapłaty do ${PAYMENT_DATE}·Utworzono za pomocą https://easyinvoicepdf.comStrona 1 z 1`, ); }); @@ -272,7 +272,7 @@ Kwota słownie: zero EUR 00/100`); // Toggle notes visibility on const sellerNotesSwitch = sellerSection.getByTestId( - `sellerNotesInvoiceFormFieldVisibilitySwitch` + `sellerNotesInvoiceFormFieldVisibilitySwitch`, ); await expect(sellerNotesSwitch).toHaveRole("switch"); @@ -302,7 +302,7 @@ Kwota słownie: zero EUR 00/100`); // Toggle notes visibility on const buyerNotesSwitch = buyerSection.getByTestId( - `buyerNotesInvoiceFormFieldVisibilitySwitch` + `buyerNotesInvoiceFormFieldVisibilitySwitch`, ); await expect(buyerNotesSwitch).toHaveRole("switch"); @@ -353,7 +353,7 @@ Kwota słownie: zero EUR 00/100`); // Save the file to a browser-specific temporary location const tmpPath = path.join( getDownloadDir({ browserName }), - suggestedFilename + suggestedFilename, ); await download.saveAs(tmpPath); @@ -368,12 +368,12 @@ Kwota słownie: zero EUR 00/100`); // Verify PDF content // Check invoice header details expect(pdfData.text).toContain( - `Invoice No. of: 1/${CURRENT_MONTH_AND_YEAR}` + `Invoice No. of: 1/${CURRENT_MONTH_AND_YEAR}`, ); expect(pdfData.text).toContain("HELLO FROM PLAYWRIGHT TEST!"); expect(pdfData.text).toContain(`Date of issue: ${today}`); expect(pdfData.text).toContain( - `Date of sales/of executing the service: ${lastDayOfCurrentMonth}` + `Date of sales/of executing the service: ${lastDayOfCurrentMonth}`, ); // Check seller details @@ -405,7 +405,7 @@ Kwota słownie: zero EUR 00/100`); expect(pdfData.text).toContain("Paid: 0.00 GBP"); expect(pdfData.text).toContain("Left to pay: 3 000.00 GBP"); expect(pdfData.text).toContain( - "Amount in words: three thousand GBP 00/100" + "Amount in words: three thousand GBP 00/100", ); // Check footer @@ -414,7 +414,7 @@ Kwota słownie: zero EUR 00/100`); expect(pdfData.text).toContain("Reverse charge"); expect(pdfData.text).toContain( - `1/${CURRENT_MONTH_AND_YEAR}·£3,000.00 due ${paymentDate}·Created with https://easyinvoicepdf.comPage 1 of 1` + `1/${CURRENT_MONTH_AND_YEAR}·£3,000.00 due ${paymentDate}·Created with https://easyinvoicepdf.comPage 1 of 1`, ); }); @@ -502,10 +502,10 @@ Kwota słownie: zero EUR 00/100`); // Verify preview tab is selected await expect( - page.getByRole("tabpanel", { name: "Preview PDF" }) + page.getByRole("tabpanel", { name: "Preview PDF" }), ).toBeVisible(); await expect( - page.getByRole("tabpanel", { name: "Edit Invoice" }) + page.getByRole("tabpanel", { name: "Edit Invoice" }), ).toBeHidden(); // Set up download handler @@ -523,7 +523,7 @@ Kwota słownie: zero EUR 00/100`); // Save the file to a browser-specific temporary location const tmpPath = path.join( getDownloadDir({ browserName }), - suggestedFilename + suggestedFilename, ); await download.saveAs(tmpPath); @@ -542,7 +542,7 @@ Kwota słownie: zero EUR 00/100`); const lastDayOfCurrentMonth = dayjs().endOf("month").format("YYYY-MM-DD"); expect(pdfData.text).toContain( - `Date de vente/prestation de service: ${lastDayOfCurrentMonth}` + `Date de vente/prestation de service: ${lastDayOfCurrentMonth}`, ); // Verify calculations in Polish @@ -555,7 +555,7 @@ Montant en lettres: cent quatre-vingt-quatre GBP 50/100`); await expect(page.getByTestId("download-pdf-toast")).toBeVisible(); await expect( - page.getByRole("link", { name: "Star on GitHub" }) + page.getByRole("link", { name: "Star on GitHub" }), ).toBeVisible(); await expect(page.getByTestId("toast-cta-btn")).toBeVisible(); @@ -565,10 +565,10 @@ Montant en lettres: cent quatre-vingt-quatre GBP 50/100`); // Verify form tab is selected and data persists await expect( - page.getByRole("tabpanel", { name: "Edit Invoice" }) + page.getByRole("tabpanel", { name: "Edit Invoice" }), ).toBeVisible(); await expect( - page.getByRole("tabpanel", { name: "Preview PDF" }) + page.getByRole("tabpanel", { name: "Preview PDF" }), ).toBeHidden(); // Verify form data persists @@ -576,30 +576,30 @@ Montant en lettres: cent quatre-vingt-quatre GBP 50/100`); await expect(invoiceNumberValueInput).toHaveValue("2/05-2024"); await expect( - finalSection.getByRole("textbox", { name: "Notes", exact: true }) + finalSection.getByRole("textbox", { name: "Notes", exact: true }), ).toHaveValue("Mobile test note"); // Verify seller information persists await expect( - sellerSection.getByRole("textbox", { name: "Name" }) + sellerSection.getByRole("textbox", { name: "Name" }), ).toHaveValue("Mobile Test Seller"); await expect( - sellerSection.getByRole("textbox", { name: "Address" }) + sellerSection.getByRole("textbox", { name: "Address" }), ).toHaveValue("456 Mobile St"); // Verify invoice item persists await expect( invoiceItemsSection.getByRole("spinbutton", { name: "Amount (Quantity)", - }) + }), ).toHaveValue("3"); await expect( invoiceItemsSection.getByRole("spinbutton", { name: "Net Price (Rate or Unit Price)", - }) + }), ).toHaveValue("50"); await expect( - invoiceItemsSection.getByRole("textbox", { name: "VAT", exact: true }) + invoiceItemsSection.getByRole("textbox", { name: "VAT", exact: true }), ).toHaveValue("23"); // Verify calculations are correct @@ -607,19 +607,19 @@ Montant en lettres: cent quatre-vingt-quatre GBP 50/100`); invoiceItemsSection.getByRole("textbox", { name: "Net Amount", exact: true, - }) + }), ).toHaveValue("150.00"); await expect( invoiceItemsSection.getByRole("textbox", { name: "VAT Amount", exact: true, - }) + }), ).toHaveValue("34.50"); await expect( invoiceItemsSection.getByRole("textbox", { name: "Pre-tax Amount", exact: true, - }) + }), ).toHaveValue("184.50"); }); @@ -641,11 +641,11 @@ Montant en lettres: cent quatre-vingt-quatre GBP 50/100`); }); await expect(invoiceNumberLabelInput).toHaveValue( - INITIAL_INVOICE_DATA.invoiceNumberObject.label + INITIAL_INVOICE_DATA.invoiceNumberObject.label, ); await expect(invoiceNumberValueInput).toHaveValue( - INITIAL_INVOICE_DATA.invoiceNumberObject.value + INITIAL_INVOICE_DATA.invoiceNumberObject.value, ); const languageSelect = page.getByRole("combobox", { @@ -655,11 +655,11 @@ Montant en lettres: cent quatre-vingt-quatre GBP 50/100`); await languageSelect.selectOption("pl"); await expect(invoiceNumberLabelInput).toHaveValue( - `${TRANSLATIONS.pl.invoiceNumber}:` + `${TRANSLATIONS.pl.invoiceNumber}:`, ); await expect(invoiceNumberValueInput).toHaveValue( - `1/${CURRENT_MONTH_AND_YEAR}` + `1/${CURRENT_MONTH_AND_YEAR}`, ); // I can fill in a new invoice number @@ -697,7 +697,7 @@ Montant en lettres: cent quatre-vingt-quatre GBP 50/100`); await languageSelect.selectOption("pt"); await expect(invoiceNumberLabelInput).toHaveValue( - `${TRANSLATIONS.pt.invoiceNumber}:` + `${TRANSLATIONS.pt.invoiceNumber}:`, ); await invoiceNumberLabelInput.fill("Fatura TEST PORTUGUESE N°:"); @@ -705,7 +705,7 @@ Montant en lettres: cent quatre-vingt-quatre GBP 50/100`); await expect( page.getByRole("button", { name: `Switch to default label ("Fatura N°:")`, - }) + }), ).toBeVisible(); // we wait until this button is visible and enabled, that means that the PDF preview has been regenerated @@ -738,7 +738,7 @@ Montant en lettres: cent quatre-vingt-quatre GBP 50/100`); // Verify PDF content expect(pdfData.text).toContain( - `Fatura TEST PORTUGUESE N°: 1/${CURRENT_MONTH_AND_YEAR}` + `Fatura TEST PORTUGUESE N°: 1/${CURRENT_MONTH_AND_YEAR}`, ); expect(pdfData.text).toContain("Data de emissão"); }); diff --git a/e2e/generate-invoice-link.test.ts b/e2e/generate-invoice-link.test.ts new file mode 100644 index 0000000..590468e --- /dev/null +++ b/e2e/generate-invoice-link.test.ts @@ -0,0 +1,299 @@ +import { expect, test } from "@playwright/test"; + +test.describe("Generate Invoice Link", () => { + test.beforeEach(async ({ page }) => { + await page.goto("/"); + }); + + test("can share invoice and data is persisted in new tab", async ({ + page, + context, + }) => { + // Fill in some test data + const invoiceNumberFieldset = page.getByRole("group", { + name: "Invoice Number", + }); + + const invoiceNumberValueField = invoiceNumberFieldset.getByRole("textbox", { + name: "Value", + }); + + await invoiceNumberValueField.fill("SHARE-TEST-001"); + + const finalSection = page.getByTestId(`final-section`); + + await finalSection + .getByRole("textbox", { name: "Notes", exact: true }) + .fill("Test note for sharing"); + + // Fill in seller information + const sellerSection = page.getByTestId("seller-information-section"); + await sellerSection + .getByRole("textbox", { name: "Name" }) + .fill("Test Seller"); + await sellerSection + .getByRole("textbox", { name: "Address" }) + .fill("123 Test St"); + await sellerSection + .getByRole("textbox", { name: "Email" }) + .fill("seller@test.com"); + + // Fill in an invoice item + const invoiceItemsSection = page.getByTestId("invoice-items-section"); + await invoiceItemsSection + .getByRole("spinbutton", { name: "Amount (Quantity)" }) + .fill("5"); + await invoiceItemsSection + .getByRole("spinbutton", { + name: "Net Price (Rate or Unit Price)", + }) + .fill("100"); + await invoiceItemsSection + .getByRole("textbox", { name: "VAT", exact: true }) + .fill("23"); + + // wait for debounce timeout + // eslint-disable-next-line playwright/no-wait-for-timeout + await page.waitForTimeout(600); + + // Generate share link + await page + .getByRole("button", { name: "Generate a link to invoice" }) + .click(); + + // Wait for URL to update with share data + await page.waitForURL((url) => url.searchParams.has("data")); + + // Get the current URL which should now contain the share data + const sharedUrl = page.url(); + expect(sharedUrl).toContain("?template=default&data="); + + // Open URL in new tab + const newPage = await context.newPage(); + await newPage.goto(sharedUrl); + + // Get elements from the new page context + const newInvoiceNumberFieldset = newPage.getByRole("group", { + name: "Invoice Number", + }); + + // Verify data is loaded in new tab + await expect( + newInvoiceNumberFieldset.getByRole("textbox", { name: "Value" }), + ).toHaveValue("SHARE-TEST-001"); + + const newPageFinalSection = newPage.getByTestId(`final-section`); + + await expect( + newPageFinalSection.getByRole("textbox", { name: "Notes", exact: true }), + ).toHaveValue("Test note for sharing"); + + // Verify seller information + const newSellerSection = newPage.getByTestId("seller-information-section"); + await expect( + newSellerSection.getByRole("textbox", { name: "Name" }), + ).toHaveValue("Test Seller"); + await expect( + newSellerSection.getByRole("textbox", { name: "Address" }), + ).toHaveValue("123 Test St"); + await expect( + newSellerSection.getByRole("textbox", { name: "Email" }), + ).toHaveValue("seller@test.com"); + + // Verify invoice item + const newInvoiceItemsSection = newPage.getByTestId("invoice-items-section"); + await expect( + newInvoiceItemsSection.getByRole("spinbutton", { + name: "Amount (Quantity)", + }), + ).toHaveValue("5"); + await expect( + newInvoiceItemsSection.getByRole("spinbutton", { + name: "Net Price (Rate or Unit Price)", + }), + ).toHaveValue("100"); + await expect( + newInvoiceItemsSection.getByRole("textbox", { name: "VAT", exact: true }), + ).toHaveValue("23"); + + // Close the new page + await newPage.close(); + }); + + test("shows notification when invoice link is broken", async ({ page }) => { + // Navigate to page with invalid data parameter + await page.goto("/?data=invalid-data-string"); + + // Verify error toast appears + await expect( + page.getByText("The shared invoice URL appears to be incorrect"), + ).toBeVisible(); + + // Verify error description is shown + await expect( + page.getByText( + "Please verify that you have copied the complete invoice URL. The link may be truncated or corrupted.", + ), + ).toBeVisible(); + + const clearUrlButton = page.getByRole("button", { + name: "Clear URL", + }); + // Verify clear URL button is present + await expect(clearUrlButton).toBeVisible(); + + // Click clear URL button + await clearUrlButton.click(); + + // Verify toast is dismissed + await expect( + page.getByText("The shared invoice URL appears to be incorrect"), + ).toBeHidden(); + + await expect( + page.getByText( + "Please verify that you have copied the complete invoice URL. The link may be truncated or corrupted.", + ), + ).toBeHidden(); + + // Wait for URL to be cleared and verify + await expect(page).toHaveURL("/?template=default"); + }); + + test("backwards compatibility: old uncompressed URLs work the same as new compressed URLs", async ({ + page, + }) => { + // Test URLs provided in the user query - these are old format URLs before compression was implemented + + const oldUncompressedUrl = + "N4IgNghgdg5grhGBTEAuEAHMIA0IAmEALkgGID2ATgLbFogCyTDABACI4sCaPXuIAYziVKSKAICe9AKoBlNvxLUsxFOgDORSgEsMKPGHIxy9ftqgA3ctoFIAcnGoAjJJQDyTgFZIBRNKEgXbHRSCABrImEIFihKdDwLCDA4NRAARgB6AAYADgBaACYsgoBWEABfPEISNwAzAEl1dRT6ItK83Ly0sqrVOtlXCxtUtpKO-IBmNLNLa1sAFQk9egAlJAtXdSQWAGEACwhKZBmrYcW9Um0kMHxGgDVtdW0nMDUtFLwtsFfKfxBtfD0NIAdgALFkAGw5UElCagiZZHogKAQaipABS5D2UHY5H0IAg+Hwoia9AAMuQoPhKZxpABpfiJIh2EzoNIFOElCGM4gsy7XW7qB5PF5vSgfEBIWjaYIgTxYqAAAWlYAAdAJyNR+BABBq4FBmY4XL82RyYdy8Dq9QaHM5XPybvdHs9Xmh3khPgB3bS1IgAIRsQLNXP46m9voDAgdguFLrFEqg5BI6noyb8eETyejTpFrtQtSSW0qICccAkrj+AKBwNhoIhWRhJWBDf4KLR9AAggI0bsTJaiSSU+g7EhPdwqGFOHYuLTZB2eczWelgxaQEy+VdHULnaK3eKPZKVfQdWjlRAZerNa2k0ghyBr1nNzGd3n3cXtEohwBtUDmU62eolFtY0czjPcE1RVJZHIX1PUObY2HWa5yAwNEDVbSDs23XN4wPIgliQOoAHF5mkUw8HwvRiNIrDY13fNCwPVFyH1PxUDSS1qBYg1aJfXC8H1D96C2SghlsfhBKIXicPAg8oCQIgAAUdHE9iSiyDSMwU5ThmksDUHdBI6GHRSFz0+jDORBSOy41i0G6DSsi0ogbO4qSn1Aiz9yMlzbPQ1AnLXYhXNY8zX28zBRHmCAAA8Qv8hzNMipBorivz3IFTzwpScoAF0KKTJJ7PUpKmWi0VZEcWhKAkLL+MwCAJDQogGAUvZyEBdBvVEFgtGgdRagrPAMEa5rWqIdr8DC+qRqasQiDYFp0FGcZClBUMtF0JBFMatwoDAcwkGkShZQfW9ViQygthYAQDiOfFM1vabZOGzZKQ7OAJqobQAC8kHweZyDWWxtA2Z6DIivQrvez72p0P6AfIRpmjIDzsP0t8gA"; + + // Navigate to old uncompressed URL + await page.goto(`/?data=${oldUncompressedUrl}`); + + // Verify the page loads without error + await expect(page).toHaveURL( + `/?data=${oldUncompressedUrl}&template=stripe`, + ); + + const oldUrl = page.url(); + + const generalInfoSection = page.getByTestId("general-information-section"); + + await expect( + generalInfoSection.getByRole("combobox", { + name: "Invoice PDF Language", + }), + ).toHaveValue("pl"); + + // Verify the Stripe template is selected + await expect( + page.getByRole("combobox", { name: "Invoice Template" }), + ).toHaveValue("stripe"); + + // Invoice Number + const invoiceNumberFieldset = page.getByRole("group", { + name: "Invoice Number", + }); + + await expect( + invoiceNumberFieldset.getByRole("textbox", { name: "Label" }), + ).toHaveValue("Faktura nr:"); + + await expect( + invoiceNumberFieldset.getByRole("textbox", { name: "Value" }), + ).toHaveValue("1/08-2025"); + + // Verify seller information is loaded + const sellerSection = page.getByTestId("seller-information-section"); + const sellerNameField = sellerSection.getByRole("textbox", { + name: "Name", + }); + await expect(sellerNameField).toHaveValue("John Doe"); + + // Verify buyer information is loaded + const buyerSection = page.getByTestId("buyer-information-section"); + const buyerNameField = buyerSection.getByRole("textbox", { name: "Name" }); + await expect(buyerNameField).toHaveValue("Acme Co"); + + // Verify invoice items are loaded + const invoiceItemsSection = page.getByTestId("invoice-items-section"); + const itemAmountField = invoiceItemsSection.getByRole("spinbutton", { + name: "Net Price (Rate or Unit Price)", + }); + await expect(itemAmountField).toHaveValue("15000"); + + // ______________________________________________ + // now check that the compressed URL of the same invoice works the same + // ______________________________________________ + + const newCompressedUrl = + "/?template=stripe&data=N4IghiBcIA4DYgDQgEZRAWSxgBAEURwE0SikQBjdAVQGU9yATdAZwBcAnASxgFNz+0cgDMooAB7oAYmADWbAK4cwOAHYdoyAJ7oAjAHoADAA4AtACZD5gKwgAvsgDm6SzdMnTu28gAWLq9buZgDMuuRc6ABKvABuvBwsvDgAwj5gHI78yABWUJwKvMiyYiAAXnoA7AAshgBsxlXWwVXBht4gAILoAFIA9j6q+L1ZIABC6AAyvaqM04TUANLkyXrmzda15AyQ+YUgAKLo2f2qAAIAtmBccAB0FL3n5FKr65vIAOJ5HAXIABIvjTeIAAkl8fiA2Og2Lx2OQFFBhGA4IkHCAEJBQOVoLoKk0qrVDI1rBVCeQutAOhRzklkr1yONoAA5XgAd2IvQ4skIjKI81oXWQK2xa0BWzBe0O0DAVN4Fyut3uj2QkKEyHhO2+vFRj0gAG1QIZxchukbOuhaL1hGwWekknhYrw4L0YNTVJDkEsNeCJuhyBgEUjEshGVBdMgAPKmgAKrHiMS4FBGAEVTZFQ9ZDJnkLRTQAVdCMmPIaimgBq6czhmQAHVTQANKBVkBkL17ABaFczdgAushVJ2m3TW8gYOgWVwOElOGBVCxhPFyABHU0cfxuDzmKrkFi+5VRB0JJIUNIZEbq3bIGKmlniuxAA"; + + await page.goto(newCompressedUrl); + + // Verify the page loads without error + await expect(page).toHaveURL(newCompressedUrl); + + const newUrl = page.url(); + + const newGeneralInfoSection = page.getByTestId( + "general-information-section", + ); + + await expect( + newGeneralInfoSection.getByRole("combobox", { + name: "Invoice PDF Language", + }), + ).toHaveValue("pl"); + + // Verify the Stripe template is selected + await expect( + page.getByRole("combobox", { name: "Invoice Template" }), + ).toHaveValue("stripe"); + + // Invoice Number + const newInvoiceNumberFieldset = page.getByRole("group", { + name: "Invoice Number", + }); + + await expect( + newInvoiceNumberFieldset.getByRole("textbox", { name: "Label" }), + ).toHaveValue("Faktura nr:"); + + await expect( + newInvoiceNumberFieldset.getByRole("textbox", { name: "Value" }), + ).toHaveValue("1/08-2025"); + + // Verify seller information is loaded + const newSellerSection = page.getByTestId("seller-information-section"); + const newSellerNameField = newSellerSection.getByRole("textbox", { + name: "Name", + }); + await expect(newSellerNameField).toHaveValue("John Doe"); + + // Verify buyer information is loaded + const newBuyerSection = page.getByTestId("buyer-information-section"); + const newBuyerNameField = newBuyerSection.getByRole("textbox", { + name: "Name", + }); + await expect(newBuyerNameField).toHaveValue("Acme Co"); + + // Verify invoice items are loaded + const newInvoiceItemsSection = page.getByTestId("invoice-items-section"); + const newItemAmountField = newInvoiceItemsSection.getByRole("spinbutton", { + name: "Net Price (Rate or Unit Price)", + }); + await expect(newItemAmountField).toHaveValue("15000"); + + // Verify the compressed URL is shorter than the original + expect(newUrl.length).toBeLessThan(oldUrl.length); + + // Calculate and verify the compression ratio + const compressionRatio = + ((oldUrl.length - newUrl.length) / oldUrl.length) * 100; + + // Verify significant compression occurred (at least 20% reduction) + expect(compressionRatio).toBeGreaterThan(20); + }); +}); diff --git a/e2e/invoice-form.test.ts b/e2e/invoice-form.test.ts index 836ba52..a47bd38 100644 --- a/e2e/invoice-form.test.ts +++ b/e2e/invoice-form.test.ts @@ -13,7 +13,7 @@ import { import { expect, test } from "@playwright/test"; import dayjs from "dayjs"; import { INITIAL_INVOICE_DATA } from "../src/app/constants"; -import { GITHUB_URL, VIDEO_DEMO_URL } from "@/config"; +import { VIDEO_DEMO_URL } from "@/config"; test.describe("Invoice Generator Page", () => { test.beforeEach(async ({ page }) => { @@ -22,45 +22,90 @@ test.describe("Invoice Generator Page", () => { test("should redirect from /:locale/app to /", async ({ page }) => { await page.goto("/en/app"); - await expect(page).toHaveURL("/"); + await expect(page).toHaveURL("/?template=default"); + }); + + test("displays correct OG meta tags for default template", async ({ + page, + }) => { + // Navigate to default template + await page.goto("/?template=default"); + + await expect(page).toHaveURL("/?template=default"); + + const templateCombobox = page.getByRole("combobox", { + name: "Invoice Template", + }); + await expect(templateCombobox).toHaveValue("default"); + + // Check that OG image changed to Stripe template + await expect(page.locator('meta[property="og:image"]')).toHaveAttribute( + "content", + "https://static.easyinvoicepdf.com/easy-invoice-opengraph-image.png?v=5", + ); + + // Check other meta tags for Stripe template + await expect(page.locator('meta[property="og:title"]')).toHaveAttribute( + "content", + "App | Free Invoice Generator – Live Preview, No Sign-Up", + ); + await expect( + page.locator('meta[property="og:description"]'), + ).toHaveAttribute( + "content", + "Create and download professional invoices instantly with EasyInvoicePDF.com. Free and open-source. No signup required.", + ); + await expect(page.locator('meta[property="og:site_name"]')).toHaveAttribute( + "content", + "EasyInvoicePDF.com | Free Invoice Generator", + ); + + // Verify OG image dimensions + await expect( + page.locator('meta[property="og:image:width"]'), + ).toHaveAttribute("content", "1200"); + await expect( + page.locator('meta[property="og:image:height"]'), + ).toHaveAttribute("content", "630"); + await expect(page.locator('meta[property="og:image:alt"]')).toHaveAttribute( + "content", + "EasyInvoicePDF.com - Free Invoice Generator with Live PDF Preview", + ); }); test("displays correct buttons and links in header", async ({ page }) => { // Check URL is correct - await expect(page).toHaveURL("/"); + await expect(page).toHaveURL("/?template=default"); // Check title and branding await expect(page).toHaveTitle( - "App | Free Invoice Generator – Live Preview, No Sign-Up" + "App | Free Invoice Generator – Live Preview, No Sign-Up", ); const header = page.getByTestId("header"); await expect(header).toBeVisible(); await expect( - header.getByRole("link", { name: "EasyInvoicePDF" }) + header.getByRole("link", { name: "EasyInvoicePDF" }), ).toBeVisible(); await expect( - header.getByText("Free Invoice Generator with Live PDF Preview") + header.getByText("Free Invoice Generator with Live PDF Preview"), ).toBeVisible(); // Check main action buttons await expect( - page.getByRole("link", { name: "Support Project" }) + page.getByRole("link", { name: "Support Project" }), ).toBeVisible(); await expect( - page.getByRole("button", { name: "Generate a link to invoice" }) + page.getByRole("button", { name: "Generate a link to invoice" }), ).toBeVisible(); await expect( - page.getByRole("link", { name: "Download PDF in English" }) + page.getByRole("link", { name: "Download PDF in English" }), ).toBeVisible(); await expect( - header.getByRole("link", { name: "Open Source" }) - ).toBeVisible(); - await expect( - header.getByRole("link", { name: "Share your feedback" }) + header.getByRole("link", { name: "Share your feedback" }), ).toBeVisible(); const howItWorksButton = header.getByRole("button", { @@ -73,13 +118,13 @@ test.describe("Invoice Generator Page", () => { await howItWorksButton.click(); await expect( - page.getByRole("heading", { name: "How EasyInvoicePDF Works" }) + page.getByRole("heading", { name: "How EasyInvoicePDF Works" }), ).toBeVisible(); await expect( page.getByText( - "Watch this quick demo to learn how to create and customize your invoices." - ) + "Watch this quick demo to learn how to create and customize your invoices.", + ), ).toBeVisible(); // Check that video is displayed in dialog @@ -99,19 +144,18 @@ test.describe("Invoice Generator Page", () => { await expect(dialog).toBeHidden(); await expect( - dialog.getByRole("heading", { name: "How EasyInvoicePDF Works" }) + dialog.getByRole("heading", { name: "How EasyInvoicePDF Works" }), ).toBeHidden(); - await expect( - header.getByRole("link", { name: "Open Source" }) - ).toHaveAttribute("href", GITHUB_URL); + // Verify GitHub Star CTA button is visible + await expect(page.getByTestId("github-star-cta-button")).toBeVisible(); // Verify buttons are enabled await expect( - page.getByRole("button", { name: "Generate a link to invoice" }) + page.getByRole("button", { name: "Generate a link to invoice" }), ).toBeEnabled(); await expect( - page.getByRole("link", { name: "Download PDF in English" }) + page.getByRole("link", { name: "Download PDF in English" }), ).toBeEnabled(); }); @@ -135,7 +179,7 @@ test.describe("Invoice Generator Page", () => { // **CHECK GENERAL INFORMATION SECTION** const generalInfoSection = page.getByTestId(`general-information-section`); await expect( - generalInfoSection.getByText("General Information", { exact: true }) + generalInfoSection.getByText("General Information", { exact: true }), ).toBeVisible(); // Check all supported languages are available as options with correct labels @@ -151,7 +195,7 @@ test.describe("Invoice Generator Page", () => { const languageName = LANGUAGE_TO_LABEL[lang]; await expect( - languageSelect.locator(`option[value="${lang}"]`) + languageSelect.locator(`option[value="${lang}"]`), ).toHaveText(languageName); } @@ -171,7 +215,7 @@ test.describe("Invoice Generator Page", () => { `${currency} ${currencySymbol} ${currencyFullName}`.trim(); await expect( - currencySelect.locator(`option[value="${currency}"]`) + currencySelect.locator(`option[value="${currency}"]`), ).toHaveText(expectedLabel); } @@ -188,9 +232,9 @@ test.describe("Invoice Generator Page", () => { const isDefault = dateFormat === SUPPORTED_DATE_FORMATS[0]; await expect( - dateFormatSelect.locator(`option[value="${dateFormat}"]`) + dateFormatSelect.locator(`option[value="${dateFormat}"]`), ).toHaveText( - `${dateFormat} (Preview: ${preview}) ${isDefault ? "(default)" : ""}` + `${dateFormat} (Preview: ${preview}) ${isDefault ? "(default)" : ""}`, ); } @@ -200,106 +244,106 @@ test.describe("Invoice Generator Page", () => { }); await expect( - invoiceNumberFieldset.getByRole("textbox", { name: "Label" }) + invoiceNumberFieldset.getByRole("textbox", { name: "Label" }), ).toHaveValue(INITIAL_INVOICE_DATA.invoiceNumberObject.label); await expect( - invoiceNumberFieldset.getByRole("textbox", { name: "Value" }) + invoiceNumberFieldset.getByRole("textbox", { name: "Value" }), ).toHaveValue(INITIAL_INVOICE_DATA.invoiceNumberObject.value); // Date of Issue await expect( - generalInfoSection.getByRole("textbox", { name: "Date of Issue" }) + generalInfoSection.getByRole("textbox", { name: "Date of Issue" }), ).toHaveValue(INITIAL_INVOICE_DATA.dateOfIssue); // Date of Service await expect( - generalInfoSection.getByRole("textbox", { name: "Date of Service" }) + generalInfoSection.getByRole("textbox", { name: "Date of Service" }), ).toHaveValue(INITIAL_INVOICE_DATA.dateOfService); // Invoice Type await expect( - generalInfoSection.getByRole("textbox", { name: "Invoice Type" }) + generalInfoSection.getByRole("textbox", { name: "Invoice Type" }), ).toHaveValue(INITIAL_INVOICE_DATA.invoiceType); // Visibility toggles await expect( - generalInfoSection.getByRole("switch", { name: "Show in PDF" }) + generalInfoSection.getByRole("switch", { name: "Show in PDF" }), ).toBeChecked(); // **CHECK SELLER INFORMATION SECTION** const sellerSection = page.getByTestId(`seller-information-section`); await expect( - sellerSection.getByText("Seller Information", { exact: true }) + sellerSection.getByText("Seller Information", { exact: true }), ).toBeVisible(); // Name field await expect( - sellerSection.getByRole("textbox", { name: "Name" }) + sellerSection.getByRole("textbox", { name: "Name" }), ).toHaveValue(INITIAL_INVOICE_DATA.seller.name); // Address field await expect( - sellerSection.getByRole("textbox", { name: "Address" }) + sellerSection.getByRole("textbox", { name: "Address" }), ).toHaveValue(INITIAL_INVOICE_DATA.seller.address); // VAT Number field and visibility toggle await expect( - sellerSection.getByRole("textbox", { name: "VAT Number" }) + sellerSection.getByRole("textbox", { name: "VAT Number" }), ).toHaveValue(INITIAL_INVOICE_DATA.seller.vatNo); await expect( - sellerSection.getByRole("switch", { name: /Show in PDF/i }).nth(0) + sellerSection.getByRole("switch", { name: /Show in PDF/i }).nth(0), ).toBeChecked(); // Email field await expect( - sellerSection.getByRole("textbox", { name: "Email" }) + sellerSection.getByRole("textbox", { name: "Email" }), ).toHaveValue(INITIAL_INVOICE_DATA.seller.email); // Account Number field and visibility toggle await expect( - sellerSection.getByRole("textbox", { name: "Account Number" }) + sellerSection.getByRole("textbox", { name: "Account Number" }), ).toHaveValue(INITIAL_INVOICE_DATA.seller.accountNumber); await expect( - sellerSection.getByRole("switch", { name: /Show in PDF/i }).nth(1) + sellerSection.getByRole("switch", { name: /Show in PDF/i }).nth(1), ).toBeChecked(); // SWIFT/BIC field and visibility toggle await expect( - sellerSection.getByRole("textbox", { name: "SWIFT/BIC" }) + sellerSection.getByRole("textbox", { name: "SWIFT/BIC" }), ).toHaveValue(INITIAL_INVOICE_DATA.seller.swiftBic); await expect( - sellerSection.getByRole("switch", { name: /Show in PDF/i }).nth(2) + sellerSection.getByRole("switch", { name: /Show in PDF/i }).nth(2), ).toBeChecked(); // Verify Seller Management button is present await expect( - sellerSection.getByRole("button", { name: "New Seller" }) + sellerSection.getByRole("button", { name: "New Seller" }), ).toBeVisible(); // **CHECK BUYER INFORMATION SECTION** const buyerSection = page.getByTestId(`buyer-information-section`); await expect( - buyerSection.getByText("Buyer Information", { exact: true }) + buyerSection.getByText("Buyer Information", { exact: true }), ).toBeVisible(); // Name field await expect( - buyerSection.getByRole("textbox", { name: "Name" }) + buyerSection.getByRole("textbox", { name: "Name" }), ).toHaveValue(INITIAL_INVOICE_DATA.buyer.name); // Address field await expect( - buyerSection.getByRole("textbox", { name: "Address" }) + buyerSection.getByRole("textbox", { name: "Address" }), ).toHaveValue(INITIAL_INVOICE_DATA.buyer.address); // VAT Number field and visibility toggle await expect( - buyerSection.getByRole("textbox", { name: "VAT Number" }) + buyerSection.getByRole("textbox", { name: "VAT Number" }), ).toHaveValue(INITIAL_INVOICE_DATA.buyer.vatNo); const buyerVatNoFieldIsVisibleSwitch = buyerSection.getByTestId( - `buyerVatNoFieldIsVisible` + `buyerVatNoFieldIsVisible`, ); await expect(buyerVatNoFieldIsVisibleSwitch).toHaveRole("switch"); @@ -307,29 +351,31 @@ test.describe("Invoice Generator Page", () => { // Email field await expect( - buyerSection.getByRole("textbox", { name: "Email" }) + buyerSection.getByRole("textbox", { name: "Email" }), ).toHaveValue(INITIAL_INVOICE_DATA.buyer.email); // Verify Buyer Management button is present await expect( - buyerSection.getByRole("button", { name: "New Buyer" }) + buyerSection.getByRole("button", { name: "New Buyer" }), ).toBeVisible(); // **Check INVOICE ITEMS section** const invoiceItemsSection = page.getByTestId(`invoice-items-section`); await expect( - invoiceItemsSection.getByText("Invoice Items", { exact: true }) + invoiceItemsSection.getByText("Invoice Items", { exact: true }), ).toBeVisible(); // Check visibility toggles in settings await expect( - invoiceItemsSection.getByRole("switch", { name: /Show "Number" Column/i }) + invoiceItemsSection.getByRole("switch", { + name: /Show "Number" Column/i, + }), ).toBeChecked(); await expect( invoiceItemsSection.getByRole("switch", { name: /Show "VAT Table Summary"/i, - }) + }), ).toBeChecked(); // Check first invoice item fields @@ -337,54 +383,54 @@ test.describe("Invoice Generator Page", () => { // Name field and visibility toggle await expect( - invoiceItemsSection.getByRole("textbox", { name: "Name" }) + invoiceItemsSection.getByRole("textbox", { name: "Name" }), ).toHaveValue(firstItem.name); await expect( - invoiceItemsSection.getByRole("switch", { name: /Show in PDF/i }).nth(0) + invoiceItemsSection.getByRole("switch", { name: /Show in PDF/i }).nth(0), ).toBeChecked(); // Type of GTU field and visibility toggle await expect( - invoiceItemsSection.getByRole("textbox", { name: "Type of GTU" }) + invoiceItemsSection.getByRole("textbox", { name: "Type of GTU" }), ).toHaveValue(firstItem.typeOfGTU); await expect( - invoiceItemsSection.getByRole("switch", { name: /Show in PDF/i }).nth(1) + invoiceItemsSection.getByRole("switch", { name: /Show in PDF/i }).nth(1), ).not.toBeChecked(); // we don't want to show this in PDF by default // Amount field and visibility toggle await expect( invoiceItemsSection.getByRole("spinbutton", { name: "Amount (Quantity)", - }) + }), ).toHaveValue(firstItem.amount.toString()); await expect( - invoiceItemsSection.getByRole("switch", { name: /Show in PDF/i }).nth(2) + invoiceItemsSection.getByRole("switch", { name: /Show in PDF/i }).nth(2), ).toBeChecked(); // Unit field and visibility toggle await expect( - invoiceItemsSection.getByRole("textbox", { name: "Unit" }) + invoiceItemsSection.getByRole("textbox", { name: "Unit" }), ).toHaveValue(firstItem.unit); await expect( - invoiceItemsSection.getByRole("switch", { name: /Show in PDF/i }).nth(3) + invoiceItemsSection.getByRole("switch", { name: /Show in PDF/i }).nth(3), ).toBeChecked(); // Net Price field and visibility toggle await expect( invoiceItemsSection.getByRole("spinbutton", { name: "Net Price (Rate or Unit Price)", - }) + }), ).toHaveValue(firstItem.netPrice.toString()); await expect( - invoiceItemsSection.getByRole("switch", { name: /Show in PDF/i }).nth(4) + invoiceItemsSection.getByRole("switch", { name: /Show in PDF/i }).nth(4), ).toBeChecked(); // VAT field and visibility toggle await expect( - invoiceItemsSection.getByRole("textbox", { name: "VAT", exact: true }) + invoiceItemsSection.getByRole("textbox", { name: "VAT", exact: true }), ).toHaveValue(firstItem.vat); await expect( - invoiceItemsSection.getByRole("switch", { name: /Show in PDF/i }).nth(5) + invoiceItemsSection.getByRole("switch", { name: /Show in PDF/i }).nth(5), ).toBeChecked(); // Net Amount field (read-only) and visibility toggle @@ -392,15 +438,15 @@ test.describe("Invoice Generator Page", () => { invoiceItemsSection.getByRole("textbox", { name: "Net Amount", exact: true, - }) + }), ).toHaveValue( firstItem.netAmount.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2, - }) + }), ); await expect( - invoiceItemsSection.getByRole("switch", { name: /Show in PDF/i }).nth(6) + invoiceItemsSection.getByRole("switch", { name: /Show in PDF/i }).nth(6), ).toBeChecked(); // VAT Amount field (read-only) and visibility toggle @@ -408,15 +454,15 @@ test.describe("Invoice Generator Page", () => { invoiceItemsSection.getByRole("textbox", { name: "VAT Amount", exact: true, - }) + }), ).toHaveValue( firstItem.vatAmount.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2, - }) + }), ); await expect( - invoiceItemsSection.getByRole("switch", { name: /Show in PDF/i }).nth(7) + invoiceItemsSection.getByRole("switch", { name: /Show in PDF/i }).nth(7), ).toBeChecked(); // Pre-tax Amount field (read-only) and visibility toggle @@ -424,20 +470,20 @@ test.describe("Invoice Generator Page", () => { invoiceItemsSection.getByRole("textbox", { name: "Pre-tax Amount", exact: true, - }) + }), ).toHaveValue( firstItem.preTaxAmount.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2, - }) + }), ); await expect( - invoiceItemsSection.getByRole("switch", { name: /Show in PDF/i }).nth(8) + invoiceItemsSection.getByRole("switch", { name: /Show in PDF/i }).nth(8), ).toBeChecked(); // Verify Add Invoice Item button is present await expect( - invoiceItemsSection.getByRole("button", { name: "Add invoice item" }) + invoiceItemsSection.getByRole("button", { name: "Add invoice item" }), ).toBeVisible(); }); @@ -449,7 +495,7 @@ test.describe("Invoice Generator Page", () => { .getByRole("button", { name: "Add invoice item" }) .click(); await expect( - invoiceItemsSection.getByText("Item 2", { exact: true }) + invoiceItemsSection.getByText("Item 2", { exact: true }), ).toBeVisible(); // Fill in new item details @@ -462,7 +508,7 @@ test.describe("Invoice Generator Page", () => { // Set up dialog handler before triggering the action page.on("dialog", async (dialog) => { expect(dialog.message()).toBe( - "Are you sure you want to delete invoice item #2?" + "Are you sure you want to delete invoice item #2?", ); await dialog.accept(); }); @@ -473,7 +519,7 @@ test.describe("Invoice Generator Page", () => { .click(); await expect( - invoiceItemsSection.getByText("Item 2", { exact: true }) + invoiceItemsSection.getByText("Item 2", { exact: true }), ).toBeHidden(); }); @@ -499,13 +545,13 @@ test.describe("Invoice Generator Page", () => { invoiceItemsSection.getByRole("textbox", { name: "Net Amount", exact: true, - }) + }), ).toHaveValue("200.00"); await expect( invoiceItemsSection.getByRole("textbox", { name: "VAT Amount", exact: true, - }) + }), ).toHaveValue("46.00"); const finalSection = page.getByTestId(`final-section`); @@ -513,7 +559,7 @@ test.describe("Invoice Generator Page", () => { finalSection.getByRole("textbox", { name: "Total", exact: true, - }) + }), ).toHaveValue("246.00"); }); @@ -533,15 +579,15 @@ test.describe("Invoice Generator Page", () => { await invoiceItemsSection.getByRole("textbox", { name: "Name" }).clear(); await expect( - page.getByText("Seller name is required", { exact: true }) + page.getByText("Seller name is required", { exact: true }), ).toBeVisible(); await expect( - page.getByText("Buyer name is required", { exact: true }) + page.getByText("Buyer name is required", { exact: true }), ).toBeVisible(); await expect( - page.getByText("Item name is required", { exact: true }) + page.getByText("Item name is required", { exact: true }), ).toBeVisible(); const dateOfIssue = dayjs().format("YYYY-MM-DD"); @@ -562,7 +608,7 @@ test.describe("Invoice Generator Page", () => { // Check if the date of issue is filled in correctly await expect( - page.getByRole("textbox", { name: "Date of Issue" }) + page.getByRole("textbox", { name: "Date of Issue" }), ).toHaveValue(dateOfIssue); // Fill in seller name @@ -578,11 +624,11 @@ test.describe("Invoice Generator Page", () => { // Check for error messages to be hidden await expect( - page.getByText("Seller name is required", { exact: true }) + page.getByText("Seller name is required", { exact: true }), ).toBeHidden(); await expect( - page.getByText("Buyer name is required", { exact: true }) + page.getByText("Buyer name is required", { exact: true }), ).toBeHidden(); }); @@ -636,12 +682,12 @@ test.describe("Invoice Generator Page", () => { "textbox", { name: "Value", - } + }, ); await expect(invoiceNumberValueField2).toHaveValue("TEST/2024"); await expect( - finalSection.getByRole("textbox", { name: "Notes", exact: true }) + finalSection.getByRole("textbox", { name: "Notes", exact: true }), ).toHaveValue("Test note"); }); @@ -659,7 +705,7 @@ test.describe("Invoice Generator Page", () => { await expect(netAmountFormElement).toHaveText("€EUR"); await expect( - invoiceItemsSection.getByText("Preview: €0.00 (zero EUR 00/100)") + invoiceItemsSection.getByText("Preview: €0.00 (zero EUR 00/100)"), ).toBeVisible(); const currencySelect = page.getByRole("combobox", { name: "Currency" }); @@ -687,8 +733,8 @@ test.describe("Invoice Generator Page", () => { "Preview: $100.75 (one hundred USD 75/100)", { exact: true, - } - ) + }, + ), ).toBeVisible(); const finalSection = page.getByTestId(`final-section`); @@ -696,7 +742,7 @@ test.describe("Invoice Generator Page", () => { finalSection.getByRole("textbox", { name: "Total", exact: true, - }) + }), ).toHaveValue("201.50"); }); @@ -716,7 +762,7 @@ test.describe("Invoice Generator Page", () => { const sectionElement = page.getByTestId(section.id); await expect(sectionElement).toBeVisible(); await expect( - sectionElement.getByRole("region", { name: section.label }) + sectionElement.getByRole("region", { name: section.label }), ).toBeVisible(); } @@ -735,25 +781,25 @@ test.describe("Invoice Generator Page", () => { await expect( page .getByTestId("general-information-section") - .getByRole("region", { name: "General Information" }) + .getByRole("region", { name: "General Information" }), ).toBeVisible(); await expect( page .getByTestId("seller-information-section") - .getByRole("region", { name: "Seller Information" }) + .getByRole("region", { name: "Seller Information" }), ).toBeHidden(); await expect( page .getByTestId("buyer-information-section") - .getByRole("region", { name: "Buyer Information" }) + .getByRole("region", { name: "Buyer Information" }), ).toBeVisible(); await expect( page .getByTestId("invoice-items-section") - .getByRole("region", { name: "Invoice Items" }) + .getByRole("region", { name: "Invoice Items" }), ).toBeHidden(); // Verify the state is saved in localStorage @@ -779,25 +825,25 @@ test.describe("Invoice Generator Page", () => { await expect( page .getByTestId("general-information-section") - .getByRole("region", { name: "General Information" }) + .getByRole("region", { name: "General Information" }), ).toBeVisible(); await expect( page .getByTestId("seller-information-section") - .getByRole("region", { name: "Seller Information" }) + .getByRole("region", { name: "Seller Information" }), ).toBeHidden(); await expect( page .getByTestId("buyer-information-section") - .getByRole("region", { name: "Buyer Information" }) + .getByRole("region", { name: "Buyer Information" }), ).toBeVisible(); await expect( page .getByTestId("invoice-items-section") - .getByRole("region", { name: "Invoice Items" }) + .getByRole("region", { name: "Invoice Items" }), ).toBeHidden(); // Toggle states after reload @@ -815,25 +861,25 @@ test.describe("Invoice Generator Page", () => { await expect( page .getByTestId("general-information-section") - .getByRole("region", { name: "General Information" }) + .getByRole("region", { name: "General Information" }), ).toBeHidden(); await expect( page .getByTestId("seller-information-section") - .getByRole("region", { name: "Seller Information" }) + .getByRole("region", { name: "Seller Information" }), ).toBeVisible(); await expect( page .getByTestId("buyer-information-section") - .getByRole("region", { name: "Buyer Information" }) + .getByRole("region", { name: "Buyer Information" }), ).toBeVisible(); await expect( page .getByTestId("invoice-items-section") - .getByRole("region", { name: "Invoice Items" }) + .getByRole("region", { name: "Invoice Items" }), ).toBeHidden(); // Verify updated state is saved in localStorage @@ -871,20 +917,20 @@ test.describe("Invoice Generator Page", () => { await amountInput.fill("1000000000000"); // 1 trillion await expect( - page.getByText("Amount must not exceed 9 999 999 999.99") + page.getByText("Amount must not exceed 9 999 999 999.99"), ).toBeVisible(); // Test valid values await amountInput.fill("1"); await expect(page.getByText("Amount must be positive")).toBeHidden(); await expect( - page.getByText("Amount must not exceed 9 999 999 999.99") + page.getByText("Amount must not exceed 9 999 999 999.99"), ).toBeHidden(); await amountInput.fill("9999999999.99"); // Maximum valid value await expect(page.getByText("Amount must be positive")).toBeHidden(); await expect( - page.getByText("Amount must not exceed 9 999 999 999.99") + page.getByText("Amount must not exceed 9 999 999 999.99"), ).toBeHidden(); // **NET PRICE FIELD** @@ -900,7 +946,7 @@ test.describe("Invoice Generator Page", () => { // Test exceeding maximum value await netPriceInput.fill("1000000000000"); // 1 trillion await expect( - page.getByText("Net price must not exceed 100 billion") + page.getByText("Net price must not exceed 100 billion"), ).toBeVisible(); // Test zero value @@ -911,7 +957,7 @@ test.describe("Invoice Generator Page", () => { await netPriceInput.fill("1"); await expect(page.getByText("Net price must be >= 0")).toBeHidden(); await expect( - page.getByText("Net price must not exceed 100 billion") + page.getByText("Net price must not exceed 100 billion"), ).toBeHidden(); // **VAT FIELD** @@ -930,7 +976,7 @@ test.describe("Invoice Generator Page", () => { await vatInput.fill("abc"); await expect( - page.getByText("Must be a valid number (0-100) or NP or OO") + page.getByText("Must be a valid number (0-100) or NP or OO"), ).toBeVisible(); // Try valid values @@ -939,12 +985,12 @@ test.describe("Invoice Generator Page", () => { await vatInput.fill("NP"); await expect( - page.getByText("Must be a valid number (0-100) or NP or OO") + page.getByText("Must be a valid number (0-100) or NP or OO"), ).toBeHidden(); await vatInput.fill("OO"); await expect( - page.getByText("Must be a valid number (0-100) or NP or OO") + page.getByText("Must be a valid number (0-100) or NP or OO"), ).toBeHidden(); }); @@ -1017,171 +1063,29 @@ test.describe("Invoice Generator Page", () => { invoiceItemsSection.getByRole("textbox", { name: "Net Amount", exact: true, - }) + }), ).toHaveValue(testCase.expected.net); await expect( invoiceItemsSection.getByRole("textbox", { name: "VAT Amount", exact: true, - }) + }), ).toHaveValue(testCase.expected.vatAmount); await expect( invoiceItemsSection.getByRole("textbox", { name: "Pre-tax Amount", exact: true, - }) + }), ).toHaveValue(testCase.expected.total); await expect( page.getByRole("textbox", { name: "Total", exact: true, - }) + }), ).toHaveValue(testCase.expected.total); } }); - - test("can share invoice and data is persisted in new tab", async ({ - page, - context, - }) => { - // Fill in some test data - const invoiceNumberFieldset = page.getByRole("group", { - name: "Invoice Number", - }); - - const invoiceNumberValueField = invoiceNumberFieldset.getByRole("textbox", { - name: "Value", - }); - - await invoiceNumberValueField.fill("SHARE-TEST-001"); - - const finalSection = page.getByTestId(`final-section`); - - await finalSection - .getByRole("textbox", { name: "Notes", exact: true }) - .fill("Test note for sharing"); - - // Fill in seller information - const sellerSection = page.getByTestId("seller-information-section"); - await sellerSection - .getByRole("textbox", { name: "Name" }) - .fill("Test Seller"); - await sellerSection - .getByRole("textbox", { name: "Address" }) - .fill("123 Test St"); - await sellerSection - .getByRole("textbox", { name: "Email" }) - .fill("seller@test.com"); - - // Fill in an invoice item - const invoiceItemsSection = page.getByTestId("invoice-items-section"); - await invoiceItemsSection - .getByRole("spinbutton", { name: "Amount (Quantity)" }) - .fill("5"); - await invoiceItemsSection - .getByRole("spinbutton", { - name: "Net Price (Rate or Unit Price)", - }) - .fill("100"); - await invoiceItemsSection - .getByRole("textbox", { name: "VAT", exact: true }) - .fill("23"); - - // wait for debounce timeout - // eslint-disable-next-line playwright/no-wait-for-timeout - await page.waitForTimeout(600); - - // Generate share link - await page - .getByRole("button", { name: "Generate a link to invoice" }) - .click(); - - // Wait for URL to update with share data - await page.waitForURL((url) => url.searchParams.has("data")); - - // Get the current URL which should now contain the share data - const sharedUrl = page.url(); - expect(sharedUrl).toContain("?data="); - - // Open URL in new tab - const newPage = await context.newPage(); - await newPage.goto(sharedUrl); - - // Verify data is loaded in new tab - await expect( - invoiceNumberFieldset.getByRole("textbox", { name: "Value" }) - ).toHaveValue("SHARE-TEST-001"); - - const newPageFinalSection = newPage.getByTestId(`final-section`); - - await expect( - newPageFinalSection.getByRole("textbox", { name: "Notes", exact: true }) - ).toHaveValue("Test note for sharing"); - - // Verify seller information - const newSellerSection = newPage.getByTestId("seller-information-section"); - await expect( - newSellerSection.getByRole("textbox", { name: "Name" }) - ).toHaveValue("Test Seller"); - await expect( - newSellerSection.getByRole("textbox", { name: "Address" }) - ).toHaveValue("123 Test St"); - await expect( - newSellerSection.getByRole("textbox", { name: "Email" }) - ).toHaveValue("seller@test.com"); - - // Verify invoice item - const newInvoiceItemsSection = newPage.getByTestId("invoice-items-section"); - await expect( - newInvoiceItemsSection.getByRole("spinbutton", { - name: "Amount (Quantity)", - }) - ).toHaveValue("5"); - await expect( - newInvoiceItemsSection.getByRole("spinbutton", { - name: "Net Price (Rate or Unit Price)", - }) - ).toHaveValue("100"); - await expect( - newInvoiceItemsSection.getByRole("textbox", { name: "VAT", exact: true }) - ).toHaveValue("23"); - - // Close the new page - await newPage.close(); - }); - - test("shows notification when invoice link is broken", async ({ page }) => { - // Navigate to page with invalid data parameter - await page.goto("/?data=invalid-data-string"); - - // Verify error toast appears - await expect( - page.getByText("The shared invoice URL appears to be incorrect") - ).toBeVisible(); - - // Verify error description is shown - await expect( - page.getByText( - "Please verify that you have copied the complete invoice URL. The link may be truncated or corrupted." - ) - ).toBeVisible(); - - // Verify clear URL button is present - await expect(page.getByRole("button", { name: "Clear URL" })).toBeVisible(); - - // Click clear URL button - await page.getByRole("button", { name: "Clear URL" }).click(); - - // Verify toast is dismissed - await expect( - page.getByText("The shared invoice URL appears to be incorrect") - ).toBeHidden(); - - // Wait for URL to be cleared and verify - await expect(page).toHaveURL("/"); - await expect(page).not.toHaveURL(/\?data=/); - }); }); diff --git a/e2e/not-found-page.test.ts b/e2e/not-found-page.test.ts index 9c96c6f..5449a36 100644 --- a/e2e/not-found-page.test.ts +++ b/e2e/not-found-page.test.ts @@ -12,7 +12,7 @@ test.describe("Not Found page", () => { // Verify error message is displayed await expect(page.getByText("404")).toBeVisible(); await expect( - page.getByRole("heading", { name: "This page could not be found." }) + page.getByRole("heading", { name: "This page could not be found." }), ).toBeVisible(); // Check return home link @@ -34,7 +34,7 @@ test.describe("Not Found page", () => { // Verify error message is displayed in English (default locale) await expect(page.getByText("404")).toBeVisible(); await expect( - page.getByRole("heading", { name: "This page could not be found." }) + page.getByRole("heading", { name: "This page could not be found." }), ).toBeVisible(); // Check return home link diff --git a/e2e/seller.test.ts b/e2e/seller.test.ts index 2f660a5..591b1fc 100644 --- a/e2e/seller.test.ts +++ b/e2e/seller.test.ts @@ -55,13 +55,13 @@ test.describe("Seller management", () => { // Verify all switches are checked by default await expect( - manageSellerDialog.getByRole("switch", { name: "Show in PDF" }).nth(0) + manageSellerDialog.getByRole("switch", { name: "Show in PDF" }).nth(0), ).toBeChecked(); await expect( - manageSellerDialog.getByRole("switch", { name: "Show in PDF" }).nth(1) + manageSellerDialog.getByRole("switch", { name: "Show in PDF" }).nth(1), ).toBeChecked(); await expect( - manageSellerDialog.getByRole("switch", { name: "Show in PDF" }).nth(2) + manageSellerDialog.getByRole("switch", { name: "Show in PDF" }).nth(2), ).toBeChecked(); // Toggle some visibility switches @@ -80,7 +80,7 @@ test.describe("Seller management", () => { .fill(testData.notes); const notesSellerSwitch = manageSellerDialog.getByTestId( - `sellerNotesDialogFieldVisibilitySwitch` + `sellerNotesDialogFieldVisibilitySwitch`, ); await expect(notesSellerSwitch).toHaveRole("switch"); @@ -92,12 +92,12 @@ test.describe("Seller management", () => { await expect( manageSellerDialog.getByRole("switch", { name: "Apply to Current Invoice", - }) + }), ).toBeChecked(); // Cancel button is shown await expect( - manageSellerDialog.getByRole("button", { name: "Cancel" }) + manageSellerDialog.getByRole("button", { name: "Cancel" }), ).toBeVisible(); // Save seller @@ -134,7 +134,7 @@ test.describe("Seller management", () => { // Verify success toast message is visible await expect( - page.getByText("Seller added successfully", { exact: true }) + page.getByText("Seller added successfully", { exact: true }), ).toBeVisible(); // ------- TEST SAVED DETAILS IN INVOICE FORM ------- @@ -145,7 +145,7 @@ test.describe("Seller management", () => { const nameInput = sellerForm.getByRole("textbox", { name: "Name" }); await expect(nameInput).toHaveAttribute( "title", - "Seller details are locked. Click the Edit Seller button (Pencil icon) to modify." + "Seller details are locked. Click the Edit Seller button (Pencil icon) to modify.", ); // Seller Name @@ -154,18 +154,18 @@ test.describe("Seller management", () => { // Seller Address await expect( - sellerForm.getByRole("textbox", { name: "Address" }) + sellerForm.getByRole("textbox", { name: "Address" }), ).toHaveAttribute("aria-readonly", "true"); await expect( - sellerForm.getByRole("textbox", { name: "Address" }) + sellerForm.getByRole("textbox", { name: "Address" }), ).toHaveValue(testData.address); // Seller VAT Number await expect( - sellerForm.getByRole("textbox", { name: "VAT Number" }) + sellerForm.getByRole("textbox", { name: "VAT Number" }), ).toHaveAttribute("aria-readonly", "true"); await expect( - sellerForm.getByRole("textbox", { name: "VAT Number" }) + sellerForm.getByRole("textbox", { name: "VAT Number" }), ).toHaveValue(testData.vatNo); const vatNumberSwitch = sellerForm.getByTestId(`sellerVatNoFieldIsVisible`); @@ -175,22 +175,22 @@ test.describe("Seller management", () => { // Seller Email await expect( - sellerForm.getByRole("textbox", { name: "Email" }) + sellerForm.getByRole("textbox", { name: "Email" }), ).toHaveAttribute("aria-readonly", "true"); await expect( - sellerForm.getByRole("textbox", { name: "Email" }) + sellerForm.getByRole("textbox", { name: "Email" }), ).toHaveValue(testData.email); // Seller Account Number await expect( - sellerForm.getByRole("textbox", { name: "Account Number" }) + sellerForm.getByRole("textbox", { name: "Account Number" }), ).toHaveAttribute("aria-readonly", "true"); await expect( - sellerForm.getByRole("textbox", { name: "Account Number" }) + sellerForm.getByRole("textbox", { name: "Account Number" }), ).toHaveValue(testData.accountNumber); const accountNumberSwitch = sellerForm.getByTestId( - `sellerAccountNumberFieldIsVisible` + `sellerAccountNumberFieldIsVisible`, ); // Verify Account Number switch is visible await expect(accountNumberSwitch).not.toBeChecked(); @@ -198,14 +198,14 @@ test.describe("Seller management", () => { // Seller SWIFT/BIC await expect( - sellerForm.getByRole("textbox", { name: "SWIFT/BIC" }) + sellerForm.getByRole("textbox", { name: "SWIFT/BIC" }), ).toHaveAttribute("aria-readonly", "true"); await expect( - sellerForm.getByRole("textbox", { name: "SWIFT/BIC" }) + sellerForm.getByRole("textbox", { name: "SWIFT/BIC" }), ).toHaveValue(testData.swiftBic); const swiftBicSwitch = sellerForm.getByTestId( - `sellerSwiftBicFieldIsVisible` + `sellerSwiftBicFieldIsVisible`, ); // Verify SWIFT/BIC switch is visible await expect(swiftBicSwitch).not.toBeChecked(); @@ -213,14 +213,14 @@ test.describe("Seller management", () => { // Seller Notes await expect( - sellerForm.getByRole("textbox", { name: "Notes" }) + sellerForm.getByRole("textbox", { name: "Notes" }), ).toHaveAttribute("aria-readonly", "true"); await expect( - sellerForm.getByRole("textbox", { name: "Notes" }) + sellerForm.getByRole("textbox", { name: "Notes" }), ).toHaveValue(testData.notes); const notesSwitch = sellerForm.getByTestId( - `sellerNotesInvoiceFormFieldVisibilitySwitch` + `sellerNotesInvoiceFormFieldVisibilitySwitch`, ); // Verify Notes switch is visible await expect(notesSwitch).toBeChecked(); @@ -228,7 +228,7 @@ test.describe("Seller management", () => { // Verify the seller appears in the dropdown await expect( - sellerForm.getByRole("combobox", { name: "Select Seller" }) + sellerForm.getByRole("combobox", { name: "Select Seller" }), ).toContainText(testData.name); // Test edit functionality @@ -237,43 +237,43 @@ test.describe("Seller management", () => { // ------- TEST EDIT FUNCTIONALITY IN SELLER MANAGEMENT DIALOG ------- // Verify all fields are populated in edit dialog await expect( - manageSellerDialog.getByRole("textbox", { name: "Name" }) + manageSellerDialog.getByRole("textbox", { name: "Name" }), ).toHaveValue(testData.name); await expect( - manageSellerDialog.getByRole("textbox", { name: "Address" }) + manageSellerDialog.getByRole("textbox", { name: "Address" }), ).toHaveValue(testData.address); await expect( - manageSellerDialog.getByRole("textbox", { name: "VAT Number" }) + manageSellerDialog.getByRole("textbox", { name: "VAT Number" }), ).toHaveValue(testData.vatNo); await expect( - manageSellerDialog.getByRole("textbox", { name: "Email" }) + manageSellerDialog.getByRole("textbox", { name: "Email" }), ).toHaveValue(testData.email); await expect( - manageSellerDialog.getByRole("textbox", { name: "Account Number" }) + manageSellerDialog.getByRole("textbox", { name: "Account Number" }), ).toHaveValue(testData.accountNumber); await expect( - manageSellerDialog.getByRole("textbox", { name: "SWIFT/BIC" }) + manageSellerDialog.getByRole("textbox", { name: "SWIFT/BIC" }), ).toHaveValue(testData.swiftBic); // Verify visibility switches state persisted in edit dialog await expect( - manageSellerDialog.getByRole("switch", { name: "Show in PDF" }).nth(0) + manageSellerDialog.getByRole("switch", { name: "Show in PDF" }).nth(0), ).toBeChecked(); await expect( - manageSellerDialog.getByRole("switch", { name: "Show in PDF" }).nth(1) + manageSellerDialog.getByRole("switch", { name: "Show in PDF" }).nth(1), ).not.toBeChecked(); await expect( - manageSellerDialog.getByRole("switch", { name: "Show in PDF" }).nth(2) + manageSellerDialog.getByRole("switch", { name: "Show in PDF" }).nth(2), ).not.toBeChecked(); // Verify notes text await expect( - manageSellerDialog.getByRole("textbox", { name: "Notes" }) + manageSellerDialog.getByRole("textbox", { name: "Notes" }), ).toHaveValue(testData.notes); // Verify notes visibility switch is checked const notesManageSellerDialogFormSwitch = manageSellerDialog.getByTestId( - `sellerNotesDialogFieldVisibilitySwitch` + `sellerNotesDialogFieldVisibilitySwitch`, ); await expect(notesManageSellerDialogFormSwitch).toBeChecked(); @@ -323,7 +323,7 @@ test.describe("Seller management", () => { // Verify seller was added const sellerForm = page.getByTestId(`seller-information-section`); await expect( - sellerForm.getByRole("combobox", { name: "Select Seller" }) + sellerForm.getByRole("combobox", { name: "Select Seller" }), ).toContainText(testData.name); // Click delete button @@ -349,8 +349,8 @@ test.describe("Seller management", () => { await expect( page.getByText( - `Are you sure you want to delete "${testData.name}" seller?` - ) + `Are you sure you want to delete "${testData.name}" seller?`, + ), ).toBeVisible(); // Confirm deletion @@ -358,33 +358,33 @@ test.describe("Seller management", () => { // Verify success message await expect( - page.getByText("Seller deleted successfully", { exact: true }) + page.getByText("Seller deleted successfully", { exact: true }), ).toBeVisible(); // Verify seller is removed from dropdown // because we have only one seller, dropdown will be completely hidden await expect( - sellerForm.getByRole("combobox", { name: "Select Seller" }) + sellerForm.getByRole("combobox", { name: "Select Seller" }), ).toBeHidden(); // Verify form is reset to default values await expect(sellerForm.getByRole("textbox", { name: "Name" })).toHaveValue( - DEFAULT_SELLER_DATA.name + DEFAULT_SELLER_DATA.name, ); await expect( - sellerForm.getByRole("textbox", { name: "Address" }) + sellerForm.getByRole("textbox", { name: "Address" }), ).toHaveValue(DEFAULT_SELLER_DATA.address); await expect( - sellerForm.getByRole("textbox", { name: "Email" }) + sellerForm.getByRole("textbox", { name: "Email" }), ).toHaveValue(DEFAULT_SELLER_DATA.email); await expect( - sellerForm.getByRole("textbox", { name: "VAT Number" }) + sellerForm.getByRole("textbox", { name: "VAT Number" }), ).toHaveValue(DEFAULT_SELLER_DATA.vatNo); await expect( - sellerForm.getByRole("textbox", { name: "Account Number" }) + sellerForm.getByRole("textbox", { name: "Account Number" }), ).toHaveValue(DEFAULT_SELLER_DATA.accountNumber); await expect( - sellerForm.getByRole("textbox", { name: "SWIFT/BIC" }) + sellerForm.getByRole("textbox", { name: "SWIFT/BIC" }), ).toHaveValue(DEFAULT_SELLER_DATA.swiftBic); }); }); diff --git a/e2e/stripe-invoice-template/share-logic.test.ts b/e2e/stripe-invoice-template/share-logic.test.ts index 3527720..cb0a145 100644 --- a/e2e/stripe-invoice-template/share-logic.test.ts +++ b/e2e/stripe-invoice-template/share-logic.test.ts @@ -15,11 +15,19 @@ test.describe("Stripe Invoice Sharing Logic", () => { test("can share invoice with Stripe template and *WITHOUT* logo", async ({ page, }) => { + // Verify default template is selected by default + await expect(page).toHaveURL("/?template=default"); + // Switch to Stripe template await page .getByRole("combobox", { name: "Invoice Template" }) .selectOption("stripe"); + // Wait for URL to be updated + await page.waitForURL("/?template=stripe"); + + await expect(page).toHaveURL("/?template=stripe"); + // Verify share button is still enabled (no logo uploaded) const shareButton = page.getByRole("button", { name: "Generate a link to invoice", @@ -33,7 +41,65 @@ test.describe("Stripe Invoice Sharing Logic", () => { // Verify URL contains shared data await page.waitForURL((url) => url.searchParams.has("data")); - expect(page.url()).toContain("?data="); + const url = page.url(); + expect(url).toContain(`?template=stripe&data=`); + + // Verify data parameter is not empty + const urlObj = new URL(url); + const dataParam = urlObj.searchParams.get("data"); + expect(dataParam).toBeTruthy(); + expect(dataParam).not.toBe(""); + + // ------------------------------------------------------------ + // Open URL in new tab + // ------------------------------------------------------------ + const context = page.context(); + const newPage = await context.newPage(); + await newPage.goto(url); + + const newUrl = newPage.url(); + expect(newUrl).toContain(`?template=stripe&data=`); + + // Verify data parameter is not empty + const newUrlObj = new URL(newUrl); + const newDataParam = newUrlObj.searchParams.get("data"); + expect(newDataParam).toBeTruthy(); + expect(newDataParam).not.toBe(""); + + // Verify stripe template UI elements are visible + const newPageGeneralInfoSection = newPage.getByTestId( + "general-information-section", + ); + + // Verify logo upload section is visible (but empty since no logo was shared) + await expect( + newPageGeneralInfoSection.getByText("Company Logo (Optional)"), + ).toBeVisible(); + + // Verify payment URL section is visible + await expect( + newPageGeneralInfoSection.getByRole("textbox", { + name: "Payment Link URL (Optional)", + }), + ).toBeVisible(); + + const finalSection = newPage.getByTestId(`final-section`); + + // Verify that signature fields are hidden (there are only for default template) + await expect( + finalSection.getByRole("switch", { + name: 'Show "Person Authorized to Receive" Signature Field in the PDF', + }), + ).toBeHidden(); + + await expect( + finalSection.getByRole("switch", { + name: 'Show "Person Authorized to Issue" Signature Field in the PDF', + }), + ).toBeHidden(); + + // Close the new page + await newPage.close(); }); test("cannot share invoice with Stripe template and *WITH* logo", async ({ @@ -49,7 +115,7 @@ test.describe("Stripe Invoice Sharing Logic", () => { // Wait for logo to be uploaded const generalInfoSection = page.getByTestId("general-information-section"); await expect( - generalInfoSection.getByAltText("Company logo preview") + generalInfoSection.getByAltText("Company logo preview"), ).toBeVisible(); // Verify share button is disabled @@ -60,17 +126,16 @@ test.describe("Stripe Invoice Sharing Logic", () => { // click over share button to verify tooltip // on mobile, we need to click the button to show the toast because it's better UX for user (you can't hover on mobile) - // eslint-disable-next-line playwright/no-force-option - await shareButton.click({ force: true }); + await shareButton.click(); await expect(page.getByText("Unable to Share Invoice")).toBeVisible({ - timeout: 700, + timeout: 2000, }); await expect( page.getByText( - "Invoices with logos cannot be shared. Please remove the logo to generate a shareable link. You can still download the invoice as PDF and share it." - ) + "Invoices with logos cannot be shared. Please remove the logo to generate a shareable link. You can still download the invoice as PDF and share it.", + ), ).toBeVisible(); }); @@ -86,7 +151,7 @@ test.describe("Stripe Invoice Sharing Logic", () => { await page.evaluate((base64Data) => { const fileInput = document.querySelector( - "#logoUpload" + "#logoUpload", ) as HTMLInputElement; if (fileInput) { @@ -105,9 +170,13 @@ test.describe("Stripe Invoice Sharing Logic", () => { } }, SMALL_TEST_IMAGE_BASE64); + // Wait for logo to be uploaded + // eslint-disable-next-line playwright/no-wait-for-timeout + await page.waitForTimeout(600); + const generalInfoSection = page.getByTestId("general-information-section"); await expect( - generalInfoSection.getByAltText("Company logo preview") + generalInfoSection.getByAltText("Company logo preview"), ).toBeVisible(); // Verify share button is disabled @@ -123,7 +192,7 @@ test.describe("Stripe Invoice Sharing Logic", () => { // Wait for logo to be removed await expect( - generalInfoSection.getByAltText("Company logo preview") + generalInfoSection.getByAltText("Company logo preview"), ).toBeHidden(); // Verify share button is enabled again @@ -133,10 +202,18 @@ test.describe("Stripe Invoice Sharing Logic", () => { // Test that sharing works await shareButton.click(); await page.waitForURL((url) => url.searchParams.has("data")); - expect(page.url()).toContain("?data="); + + const url = page.url(); + expect(url).toContain(`?template=stripe&data=`); + + // Verify data parameter is not empty + const urlObj = new URL(url); + const dataParam = urlObj.searchParams.get("data"); + expect(dataParam).toBeTruthy(); + expect(dataParam).not.toBe(""); await expect( - page.getByText("Invoice link copied to clipboard!") + page.getByText("Invoice link copied to clipboard!"), ).toBeVisible(); }); @@ -157,7 +234,7 @@ test.describe("Stripe Invoice Sharing Logic", () => { // Upload a logo await page.evaluate((base64Data) => { const fileInput = document.querySelector( - "#logoUpload" + "#logoUpload", ) as HTMLInputElement; if (fileInput) { const byteString = atob(base64Data.split(",")[1]); @@ -194,12 +271,14 @@ test.describe("Stripe Invoice Sharing Logic", () => { .getByRole("combobox", { name: "Invoice Template" }) .selectOption("stripe"); + await page.waitForURL("/?template=stripe"); + await page.evaluate(uploadBase64LogoAsFile, SMALL_TEST_IMAGE_BASE64); // Wait for upload and verify share button is disabled const generalInfoSection = page.getByTestId("general-information-section"); await expect( - generalInfoSection.getByAltText("Company logo preview") + generalInfoSection.getByAltText("Company logo preview"), ).toBeVisible(); const shareButton = page.getByRole("button", { @@ -229,12 +308,14 @@ test.describe("Stripe Invoice Sharing Logic", () => { // Reload the page await page.reload(); + await page.waitForURL("/?template=stripe"); + // Verify state persists after reload await expect( - page.getByRole("combobox", { name: "Invoice Template" }) + page.getByRole("combobox", { name: "Invoice Template" }), ).toHaveValue("stripe"); await expect( - generalInfoSection.getByAltText("Company logo preview") + generalInfoSection.getByAltText("Company logo preview"), ).toBeVisible(); // Verify share button is still disabled @@ -275,8 +356,8 @@ test.describe("Stripe Invoice Sharing Logic", () => { await expect( page.getByText( - "Invoices with logos cannot be shared. Please remove the logo to generate a shareable link. You can still download the invoice as PDF and share it." - ) + "Invoices with logos cannot be shared. Please remove the logo to generate a shareable link. You can still download the invoice as PDF and share it.", + ), ).toBeVisible(); // Remove logo and verify sharing works again diff --git a/e2e/stripe-invoice-template/template.test.ts b/e2e/stripe-invoice-template/template.test.ts index b97b4b0..9df2520 100644 --- a/e2e/stripe-invoice-template/template.test.ts +++ b/e2e/stripe-invoice-template/template.test.ts @@ -68,24 +68,75 @@ test.describe("Stripe Invoice Template", () => { await page.evaluate(() => localStorage.clear()); }); + test("displays correct OG meta tags for Stripe template", async ({ + page, + }) => { + // Navigate to Stripe template + await page.goto("/?template=stripe"); + + await expect(page).toHaveURL("/?template=stripe"); + + const templateCombobox = page.getByRole("combobox", { + name: "Invoice Template", + }); + await expect(templateCombobox).toHaveValue("stripe"); + + // Check that OG image changed to Stripe template + await expect(page.locator('meta[property="og:image"]')).toHaveAttribute( + "content", + "https://static.easyinvoicepdf.com/stripe-og.png", + ); + + // Check other meta tags for Stripe template + await expect(page.locator('meta[property="og:title"]')).toHaveAttribute( + "content", + "Stripe Invoice Template | Free Invoice Generator", + ); + await expect( + page.locator('meta[property="og:description"]'), + ).toHaveAttribute( + "content", + "Create and download professional invoices instantly with EasyInvoicePDF.com. Free and open-source. No signup required.", + ); + await expect(page.locator('meta[property="og:site_name"]')).toHaveAttribute( + "content", + "EasyInvoicePDF.com | Free Invoice Generator", + ); + + // Verify OG image dimensions + await expect( + page.locator('meta[property="og:image:width"]'), + ).toHaveAttribute("content", "1200"); + await expect( + page.locator('meta[property="og:image:height"]'), + ).toHaveAttribute("content", "630"); + await expect(page.locator('meta[property="og:image:alt"]')).toHaveAttribute( + "content", + "Stripe Invoice Template", + ); + }); + test("logo upload section and payment link URL section only appear for Stripe template", async ({ page, }) => { + // Verify default template is selected by default + await expect(page).toHaveURL("/?template=default"); + const generalInfoSection = page.getByTestId("general-information-section"); // Initially default template - logo section should not be visible await expect( - generalInfoSection.getByText("Company Logo (Optional)") + generalInfoSection.getByText("Company Logo (Optional)"), ).toBeHidden(); await expect( - generalInfoSection.getByTestId("stripe-logo-upload-input") + generalInfoSection.getByTestId("stripe-logo-upload-input"), ).toBeHidden(); // Payment URL section should not be visible await expect( generalInfoSection.getByRole("textbox", { name: "Payment Link URL (Optional)", - }) + }), ).toBeHidden(); // Switch to Stripe template @@ -93,26 +144,31 @@ test.describe("Stripe Invoice Template", () => { .getByRole("combobox", { name: "Invoice Template" }) .selectOption("stripe"); + // Wait for URL to be updated + await page.waitForURL("/?template=stripe"); + + await expect(page).toHaveURL("/?template=stripe"); + // Logo section should now be visible await expect( - generalInfoSection.getByTestId("stripe-logo-upload-input") + generalInfoSection.getByTestId("stripe-logo-upload-input"), ).toBeVisible(); await expect( - generalInfoSection.getByText("Company Logo (Optional)") + generalInfoSection.getByText("Company Logo (Optional)"), ).toBeVisible(); await expect( - generalInfoSection.getByText("Click to upload your company logo") + generalInfoSection.getByText("Click to upload your company logo"), ).toBeVisible(); await expect( - generalInfoSection.getByText("JPEG, PNG or WebP (max 3MB)") + generalInfoSection.getByText("JPEG, PNG or WebP (max 3MB)"), ).toBeVisible(); // Payment URL section should now be visible await expect( generalInfoSection.getByRole("textbox", { name: "Payment Link URL (Optional)", - }) + }), ).toBeVisible(); // Switch back to default template @@ -122,18 +178,18 @@ test.describe("Stripe Invoice Template", () => { // Logo section should be hidden again await expect( - generalInfoSection.getByText("Company Logo (Optional)") + generalInfoSection.getByText("Company Logo (Optional)"), ).toBeHidden(); await expect( - generalInfoSection.getByTestId("stripe-logo-upload-input") + generalInfoSection.getByTestId("stripe-logo-upload-input"), ).toBeHidden(); // Payment URL section should be hidden again await expect( generalInfoSection.getByRole("textbox", { name: "Payment Link URL (Optional)", - }) + }), ).toBeHidden(); }); @@ -148,7 +204,7 @@ test.describe("Stripe Invoice Template", () => { // Create a mock file input event with invalid file type await page.evaluate(() => { const fileInput = document.querySelector( - "#logoUpload" + "#logoUpload", ) as HTMLInputElement; if (fileInput) { // Create a mock file with invalid type @@ -164,7 +220,7 @@ test.describe("Stripe Invoice Template", () => { // Should show error toast await expect( - page.getByText("Please select a valid image file (JPEG, PNG or WebP)") + page.getByText("Please select a valid image file (JPEG, PNG or WebP)"), ).toBeVisible(); }); @@ -179,7 +235,7 @@ test.describe("Stripe Invoice Template", () => { // Create a mock file input event with large file await page.evaluate(() => { const fileInput = document.querySelector( - "#logoUpload" + "#logoUpload", ) as HTMLInputElement; if (fileInput) { // Create a mock file that's too large (4MB) @@ -198,7 +254,7 @@ test.describe("Stripe Invoice Template", () => { // Should show error toast await expect( - page.getByText("Image size must be less than 3MB") + page.getByText("Image size must be less than 3MB"), ).toBeVisible(); }); @@ -220,22 +276,22 @@ test.describe("Stripe Invoice Template", () => { // Should show logo preview await expect( - generalInfoSection.getByAltText("Company logo preview") + generalInfoSection.getByAltText("Company logo preview"), ).toBeVisible(); await expect( generalInfoSection.getByText( - "Logo uploaded successfully. Click the X to remove it." - ) + "Logo uploaded successfully. Click the X to remove it.", + ), ).toBeVisible(); // Should show remove button await expect( - generalInfoSection.getByRole("button", { name: "Remove logo" }) + generalInfoSection.getByRole("button", { name: "Remove logo" }), ).toBeVisible(); // Upload area should be hidden await expect( - generalInfoSection.getByText("Click to upload your company logo") + generalInfoSection.getByText("Click to upload your company logo"), ).toBeHidden(); }); @@ -252,7 +308,7 @@ test.describe("Stripe Invoice Template", () => { // Wait for logo to be uploaded await expect( - generalInfoSection.getByAltText("Company logo preview") + generalInfoSection.getByAltText("Company logo preview"), ).toBeVisible(); // Click remove button @@ -265,12 +321,12 @@ test.describe("Stripe Invoice Template", () => { // Logo preview should be hidden await expect( - generalInfoSection.getByAltText("Company logo preview") + generalInfoSection.getByAltText("Company logo preview"), ).toBeHidden(); // Upload area should be visible again await expect( - generalInfoSection.getByText("Click to upload your company logo") + generalInfoSection.getByText("Click to upload your company logo"), ).toBeVisible(); }); @@ -340,7 +396,7 @@ test.describe("Stripe Invoice Template", () => { const pdfData = await pdf(dataBuffer); expect((pdfData.info as { Title: string }).Title).toContain( - `Invoice 1/${CURRENT_MONTH_AND_YEAR} | Created with https://easyinvoicepdf.com` + `Invoice 1/${CURRENT_MONTH_AND_YEAR} | Created with https://easyinvoicepdf.com`, ); expect(pdfData.text).toContain("Invoice"); @@ -355,7 +411,7 @@ test.describe("Stripe Invoice Template", () => { expect(pdfData.text).toContain( "Account Number: Seller account num-\nber\n" + "SWIFT/BIC number: Seller swift bic\n" + - "Bill to\n" + "Bill to\n", ); expect(pdfData.text).toContain("Bill to"); @@ -371,7 +427,7 @@ test.describe("Stripe Invoice Template", () => { expect(pdfData.text).toContain("DescriptionQtyUnit PriceAmount"); expect(pdfData.text).toContain("Item name"); expect(pdfData.text).toContain( - `${START_OF_CURRENT_MONTH} – ${LAST_DAY_OF_CURRENT_MONTH}` + `${START_OF_CURRENT_MONTH} – ${LAST_DAY_OF_CURRENT_MONTH}`, ); expect(pdfData.text).toContain("1€0.00€0.00"); expect(pdfData.text).toContain("Subtotal€0.00"); @@ -379,7 +435,7 @@ test.describe("Stripe Invoice Template", () => { expect(pdfData.text).toContain("Amount Due€0.00"); expect(pdfData.text).toContain("Reverse charge"); expect(pdfData.text).toContain( - `1/${CURRENT_MONTH_AND_YEAR}·€0.00 due ${PAYMENT_DATE}·Created with https://easyinvoicepdf.comPage 1 of 1` + `1/${CURRENT_MONTH_AND_YEAR}·€0.00 due ${PAYMENT_DATE}·Created with https://easyinvoicepdf.comPage 1 of 1`, ); }); @@ -407,7 +463,7 @@ test.describe("Stripe Invoice Template", () => { // Should not show error for valid URL await expect(paymentUrlInput).toHaveValue( - "https://buy.stripe.com/test_payment_link" + "https://buy.stripe.com/test_payment_link", ); }); @@ -429,7 +485,7 @@ test.describe("Stripe Invoice Template", () => { // Wait a moment for any debounced localStorage updates // eslint-disable-next-line playwright/no-wait-for-timeout - await page.waitForTimeout(500); + await page.waitForTimeout(600); // Verify data is actually saved in localStorage const storedData = (await page.evaluate((key) => { @@ -449,19 +505,19 @@ test.describe("Stripe Invoice Template", () => { // Verify template is still Stripe await expect( - page.getByRole("combobox", { name: "Invoice Template" }) + page.getByRole("combobox", { name: "Invoice Template" }), ).toHaveValue("stripe"); // Verify payment URL persists await expect( generalInfoSection.getByRole("textbox", { name: "Payment Link URL (Optional)", - }) + }), ).toHaveValue("https://buy.stripe.com/test_payment_link"); // Verify logo persists await expect( - generalInfoSection.getByAltText("Company logo preview") + generalInfoSection.getByAltText("Company logo preview"), ).toBeVisible(); }); }); diff --git a/e2e/stripe-invoice-template/utils.ts b/e2e/stripe-invoice-template/utils.ts index 1bbf433..cbfb54a 100644 --- a/e2e/stripe-invoice-template/utils.ts +++ b/e2e/stripe-invoice-template/utils.ts @@ -10,7 +10,7 @@ export const SMALL_TEST_IMAGE_BASE64 = export const uploadBase64LogoAsFile = (base64Data: string) => { // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion const fileInput = document.querySelector( - "#logoUpload" + "#logoUpload", ) as HTMLInputElement | null; if (!fileInput) { diff --git a/eslint.config.mjs b/eslint.config.mjs index 62e0bd9..1424830 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -97,5 +97,5 @@ export default tseslint.config( projectService: true, }, }, - } + }, ); diff --git a/knip.ts b/knip.ts index fe9b225..d2e80fb 100644 --- a/knip.ts +++ b/knip.ts @@ -15,7 +15,6 @@ const config: KnipConfig = { "@ianvs/prettier-plugin-sort-imports", "react-email", "react-scan", - "@stagewise/toolbar-next", ], ignore: [ "lint-staged.config.js", diff --git a/lint-staged.config.js b/lint-staged.config.js index a8c267a..9c91b4e 100644 --- a/lint-staged.config.js +++ b/lint-staged.config.js @@ -4,7 +4,7 @@ module.exports = { "*": () => [ `pnpm run type-check:go`, `pnpm run lint`, - `pnpm run knip`, + // `pnpm run knip`, // TODO: temporarily disabled due to issues with knip `pnpm run prettify --write`, ], }; diff --git a/messages/en.json b/messages/en.json index a5c8a90..e5812bb 100644 --- a/messages/en.json +++ b/messages/en.json @@ -11,7 +11,7 @@ "badge": "Features", "title": "Everything you need for professional invoicing", "description": "Our simple yet powerful invoice generator includes all the features you need to create professional invoices quickly.", - "comingSoon": "Pro version and API coming soon", + "comingSoon": "E-invoices support coming soon", "items": { "livePreview": { "title": "Live Preview", diff --git a/next.config.mjs b/next.config.mjs index 9c1c77c..97d2252 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -22,7 +22,7 @@ async function validatei18nAndTranslationFiles() { // Import the translations schema using jiti // @ts-ignore const { translationsSchema, TRANSLATIONS } = await loadTsFileViaJiti.import( - "./src/app/schema/translations.ts" + "./src/app/schema/translations.ts", ); const result = translationsSchema.safeParse(TRANSLATIONS); @@ -41,7 +41,7 @@ async function validatei18nAndTranslationFiles() { // Import the messages schema using jiti // @ts-ignore const { messagesSchema } = await loadTsFileViaJiti.import( - "./src/app/schema/i18n-schema.ts" + "./src/app/schema/i18n-schema.ts", ); // Validate messages @@ -52,7 +52,7 @@ async function validatei18nAndTranslationFiles() { const validationPromises = is18nJSONMessageFiles.map(async (file) => { try { const messages = JSON.parse( - await fs.promises.readFile(path.join(messagesDir, file), "utf8") + await fs.promises.readFile(path.join(messagesDir, file), "utf8"), ); const result = messagesSchema.safeParse(messages); @@ -83,7 +83,7 @@ async function validatei18nAndTranslationFiles() { const hasErrors = results.some( (result) => result.status === "rejected" || - (result.status === "fulfilled" && !result.value.success) + (result.status === "fulfilled" && !result.value.success), ); if (hasErrors) { @@ -93,7 +93,7 @@ async function validatei18nAndTranslationFiles() { } else if (!result.value.success) { console.error( `❌ Invalid i18n messages in ${result.value.file}:`, - result.value.error + result.value.error, ); } }); diff --git a/package.json b/package.json index 164f536..55a5dfd 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "pdf-invoice-generator", "version": "0.1.0", "private": true, - "packageManager": "pnpm@10.9.0", + "packageManager": "pnpm@10.14.0", "engines": { "node": ">=20.0.0" }, @@ -17,7 +17,8 @@ "prettify": "prettier --write --cache '**/*.{ts?(x),json,js,mjs,yml,yaml,md}'", "knip": "knip", "update-deps": "pnpm upgrade --interactive --latest", - "test": "pnpm exec playwright test --reporter=list", + "vitest": "vitest --reporter=verbose", + "vitest:ui": "vitest --ui", "e2e": "pnpm exec playwright test --reporter=list", "e2e:ui": "pnpm exec playwright test --ui", "dedupe": "pnpm dedupe", @@ -31,6 +32,7 @@ "@hookform/resolvers": "3.9.0", "@mdx-js/loader": "3.1.0", "@mdx-js/react": "3.1.0", + "@microlink/react-json-view": "1.27.0", "@next/mdx": "15.3.3", "@radix-ui/react-accordion": "1.2.3", "@radix-ui/react-alert-dialog": "1.1.6", @@ -88,15 +90,14 @@ "@eslint/eslintrc": "3.3.1", "@next/eslint-plugin-next": "15.2.3", "@playwright/test": "1.52.0", - "@stagewise/toolbar-next": "0.1.2", "@types/file-saver": "2.0.7", "@types/node": "22.8.1", "@types/pdf-parse": "1.1.5", "@types/react": "18.3.12", "@types/react-dom": "18.3.1", - "@typescript/native-preview": "7.0.0-dev.20250525.1", + "@typescript/native-preview": "7.0.0-dev.20250819.1", "autoprefixer": "10.4.21", - "eslint": "9.26.0", + "eslint": "9.33.0", "eslint-config-next": "15.2.3", "eslint-plugin-playwright": "2.2.0", "eslint-plugin-react-you-might-not-need-an-effect": "0.0.39", @@ -110,7 +111,8 @@ "react-scan": "0.3.4", "schema-dts": "1.1.5", "tailwindcss": "3.4.14", - "typescript": "5.8.3", - "typescript-eslint": "8.32.0" + "typescript": "5.9.2", + "typescript-eslint": "8.40.0", + "vitest": "3.2.4" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e7a5cb7..186bcb6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,13 +12,16 @@ importers: version: 3.9.0(react-hook-form@7.53.1(react@18.3.1)) "@mdx-js/loader": specifier: 3.1.0 - version: 3.1.0(acorn@8.14.1)(webpack@5.98.0(esbuild@0.25.0)) + version: 3.1.0(acorn@8.15.0)(webpack@5.98.0) "@mdx-js/react": specifier: 3.1.0 version: 3.1.0(@types/react@18.3.12)(react@18.3.1) + "@microlink/react-json-view": + specifier: 1.27.0 + version: 1.27.0(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) "@next/mdx": specifier: 15.3.3 - version: 15.3.3(@mdx-js/loader@3.1.0(acorn@8.14.1)(webpack@5.98.0(esbuild@0.25.0)))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1)) + version: 15.3.3(@mdx-js/loader@3.1.0(acorn@8.15.0)(webpack@5.98.0))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1)) "@radix-ui/react-accordion": specifier: 1.2.3 version: 1.2.3(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -63,10 +66,10 @@ importers: version: 4.3.0(react@18.3.1) "@sentry/nextjs": specifier: 9.3.0 - version: 9.3.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(next@14.2.15(@babel/core@7.26.9)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(webpack@5.98.0(esbuild@0.25.0)) + version: 9.3.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(next@14.2.15(@babel/core@7.26.9)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(webpack@5.98.0) "@t3-oss/env-nextjs": specifier: 0.13.4 - version: 0.13.4(arktype@2.1.20)(typescript@5.8.3)(zod@3.24.4) + version: 0.13.4(arktype@2.1.20)(typescript@5.9.2)(zod@3.24.4) "@tailwindcss/typography": specifier: 0.5.16 version: 0.5.16(tailwindcss@3.4.14) @@ -99,7 +102,7 @@ importers: version: 1.11.13 eslint-plugin-react-hooks: specifier: 5.2.0 - version: 5.2.0(eslint@9.26.0(jiti@2.4.2)) + version: 5.2.0(eslint@9.33.0(jiti@2.4.2)) file-saver: specifier: 2.0.5 version: 2.0.5 @@ -126,7 +129,7 @@ importers: version: 14.2.15(@babel/core@7.26.9)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-intl: specifier: 4.0.2 - version: 4.0.2(next@14.2.15(@babel/core@7.26.9)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(typescript@5.8.3) + version: 4.0.2(next@14.2.15(@babel/core@7.26.9)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(typescript@5.9.2) pdf-parse: specifier: 1.1.1 version: 1.1.1 @@ -182,9 +185,6 @@ importers: "@playwright/test": specifier: 1.52.0 version: 1.52.0 - "@stagewise/toolbar-next": - specifier: 0.1.2 - version: 0.1.2(@types/react@18.3.12)(jiti@2.4.2)(next@14.2.15(@babel/core@7.26.9)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(postcss@8.5.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tsx@4.19.4)(typescript@5.8.3)(use-sync-external-store@1.5.0(react@18.3.1))(vite@6.3.5(@types/node@22.8.1)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.4)(yaml@2.7.0))(yaml@2.7.0) "@types/file-saver": specifier: 2.0.7 version: 2.0.7 @@ -201,29 +201,29 @@ importers: specifier: 18.3.1 version: 18.3.1 "@typescript/native-preview": - specifier: 7.0.0-dev.20250525.1 - version: 7.0.0-dev.20250525.1 + specifier: 7.0.0-dev.20250819.1 + version: 7.0.0-dev.20250819.1 autoprefixer: specifier: 10.4.21 version: 10.4.21(postcss@8.5.3) eslint: - specifier: 9.26.0 - version: 9.26.0(jiti@2.4.2) + specifier: 9.33.0 + version: 9.33.0(jiti@2.4.2) eslint-config-next: specifier: 15.2.3 - version: 15.2.3(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) + version: 15.2.3(eslint@9.33.0(jiti@2.4.2))(typescript@5.9.2) eslint-plugin-playwright: specifier: 2.2.0 - version: 2.2.0(eslint@9.26.0(jiti@2.4.2)) + version: 2.2.0(eslint@9.33.0(jiti@2.4.2)) eslint-plugin-react-you-might-not-need-an-effect: specifier: 0.0.39 - version: 0.0.39(eslint@9.26.0(jiti@2.4.2)) + version: 0.0.39(eslint@9.33.0(jiti@2.4.2)) husky: specifier: 9.1.7 version: 9.1.7 knip: specifier: 5.55.1 - version: 5.55.1(@types/node@22.8.1)(typescript@5.8.3) + version: 5.55.1(@types/node@22.8.1)(typescript@5.9.2) lint-staged: specifier: 15.5.0 version: 15.5.0 @@ -249,11 +249,14 @@ importers: specifier: 3.4.14 version: 3.4.14 typescript: - specifier: 5.8.3 - version: 5.8.3 + specifier: 5.9.2 + version: 5.9.2 typescript-eslint: - specifier: 8.32.0 - version: 8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) + specifier: 8.40.0 + version: 8.40.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.9.2) + vitest: + specifier: 3.2.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.8.1)(jiti@2.4.2)(terser@5.43.1)(tsx@4.19.4)(yaml@2.7.0) packages: "@alloc/quick-lru@5.2.0": @@ -424,22 +427,22 @@ packages: integrity: sha512-6b9Ab2UiZwJYA9iMyboYyW9yJvAO9V753ZhS+DHKEjZRKAxPPOb7MXXu84lsPFG+vZt6FRFniZ8rXi+zCIw4yQ==, } - "@emnapi/core@1.3.1": + "@emnapi/core@1.4.5": resolution: { - integrity: sha512-pVGjBIt1Y6gg3EJN8jTcfpP/+uuRksIo055oE/OBkDNcjZqVbfkWCksG1Jp4yZnj3iKWyWX8fdG/j6UDYPbFog==, + integrity: sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==, } - "@emnapi/runtime@1.3.1": + "@emnapi/runtime@1.4.5": resolution: { - integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==, + integrity: sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==, } - "@emnapi/wasi-threads@1.0.1": + "@emnapi/wasi-threads@1.0.4": resolution: { - integrity: sha512-iIBu7mwkq4UQGeMEM8bLwNK962nXdhodeScX4slfQnRhEMMzvYivHhutCIk8uojvmASXXPC2WNEjwxFWk72Oqw==, + integrity: sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==, } "@esbuild/aix-ppc64@0.25.0": @@ -683,24 +686,24 @@ packages: } engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } - "@eslint/config-array@0.20.0": + "@eslint/config-array@0.21.0": resolution: { - integrity: sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==, + integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==, } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - "@eslint/config-helpers@0.2.2": + "@eslint/config-helpers@0.3.1": resolution: { - integrity: sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==, + integrity: sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==, } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - "@eslint/core@0.13.0": + "@eslint/core@0.15.2": resolution: { - integrity: sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==, + integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==, } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } @@ -711,10 +714,10 @@ packages: } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - "@eslint/js@9.26.0": + "@eslint/js@9.33.0": resolution: { - integrity: sha512-I9XlJawFdSMvWjDt6wksMCrgns5ggLNfFwFvnShsleWruvXM514Qxk8V246efTw+eo9JABvVz+u3q2RiAowKxQ==, + integrity: sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==, } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } @@ -725,10 +728,10 @@ packages: } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - "@eslint/plugin-kit@0.2.8": + "@eslint/plugin-kit@0.3.5": resolution: { - integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==, + integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==, } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } @@ -753,15 +756,6 @@ packages: react: ">=16.8.0" react-dom: ">=16.8.0" - "@floating-ui/react@0.26.28": - resolution: - { - integrity: sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==, - } - peerDependencies: - react: ">=16.8.0" - react-dom: ">=16.8.0" - "@floating-ui/utils@0.2.9": resolution: { @@ -804,16 +798,6 @@ packages: integrity: sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg==, } - "@headlessui/react@2.2.2": - resolution: - { - integrity: sha512-zbniWOYBQ8GHSUIOPY7BbdIn6PzUOq0z41RFrF30HbjsxG6Rrfk+6QulR8Kgf2Vwj2a/rE6i62q5vo+2gI5dJA==, - } - engines: { node: ">=10" } - peerDependencies: - react: ^18 || ^19 || ^19.0.0-rc - react-dom: ^18 || ^19 || ^19.0.0-rc - "@hookform/resolvers@3.9.0": resolution: { @@ -1050,12 +1034,11 @@ packages: } engines: { node: ">=12" } - "@jridgewell/gen-mapping@0.3.8": + "@jridgewell/gen-mapping@0.3.12": resolution: { - integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==, + integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==, } - engines: { node: ">=6.0.0" } "@jridgewell/resolve-uri@3.1.2": resolution: @@ -1064,29 +1047,22 @@ packages: } engines: { node: ">=6.0.0" } - "@jridgewell/set-array@1.2.1": + "@jridgewell/source-map@0.3.10": resolution: { - integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==, - } - engines: { node: ">=6.0.0" } - - "@jridgewell/source-map@0.3.6": - resolution: - { - integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==, + integrity: sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q==, } - "@jridgewell/sourcemap-codec@1.5.0": + "@jridgewell/sourcemap-codec@1.5.4": resolution: { - integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==, + integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==, } - "@jridgewell/trace-mapping@0.3.25": + "@jridgewell/trace-mapping@0.3.29": resolution: { - integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==, + integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==, } "@mdx-js/loader@3.1.0": @@ -1115,17 +1091,20 @@ packages: "@types/react": ">=16" react: ">=16" - "@modelcontextprotocol/sdk@1.11.1": + "@microlink/react-json-view@1.27.0": resolution: { - integrity: sha512-9LfmxKTb1v+vUS1/emSk1f5ePmTLkb9Le9AxOB5T0XM59EUumwcS45z05h7aiZx3GI0Bl7mjb3FMEglYj+acuQ==, + integrity: sha512-/IwWmMuRR2edvxrRYRBJzjyi4vGvIn/ltM8wqesz+HLZsoGKIUgwiwEkblOLZqXj8BGWmeRnyAdCqf0uACqRFw==, } - engines: { node: ">=18" } + engines: { node: ">=17" } + peerDependencies: + react: ">= 15" + react-dom: ">= 15" - "@napi-rs/wasm-runtime@0.2.7": + "@napi-rs/wasm-runtime@0.2.12": resolution: { - integrity: sha512-5yximcFK5FNompXfJFoWanu5l8v1hNGqNHh9du1xETp9HWk/B/PzvchX55WYOPaIeNglG8++68AAiauBAtbnzw==, + integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==, } "@next/env@14.2.15": @@ -1661,14 +1640,6 @@ packages: engines: { node: ">=18" } hasBin: true - "@preact/compat@18.3.1": - resolution: - { - integrity: sha512-Kog4PSRxtT4COtOXjsuQPV1vMXpUzREQfv+6Dmcy9/rMk0HOPK0HTE9fspFjAmY8R80T/T8gtgmZ68u5bOSngw==, - } - peerDependencies: - preact: "*" - "@preact/signals-core@1.8.0": resolution: { @@ -3801,10 +3772,10 @@ packages: integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==, } - "@rushstack/eslint-patch@1.11.0": + "@rushstack/eslint-patch@1.12.0": resolution: { - integrity: sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==, + integrity: sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw==, } "@schummar/icu-type-parser@1.21.5": @@ -4006,31 +3977,6 @@ packages: integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==, } - "@stagewise/toolbar-next@0.1.2": - resolution: - { - integrity: sha512-6/Z03eLmDz8AyMYIMvX/nnEBIK/gajtLi21aIho3u0qP+RM/7tDbphN9e4ssKOKj46MEPp/C8LkSR4ISOSbMQQ==, - } - peerDependencies: - "@types/react": ">=18.0.0" - next: ">=14.0.0" - react: ">=18.0.0" - - "@stagewise/toolbar-react@0.1.2": - resolution: - { - integrity: sha512-Vxt7f3TwFuHOgfxTFT4aBcojynat15qMUA+hArwb3EJxwjZR8VuN3Phi0K5RnniutphiBn5iNypzwGs5mFWmVw==, - } - peerDependencies: - "@types/react": ">=18.0.0" - react: ">=18.0.0" - - "@stagewise/toolbar@0.2.1": - resolution: - { - integrity: sha512-ITA68sqtXRklV6TDDyhT+3GDQqdtZFcZY9WBdPv6XUoATBHcOjpblt0yvQoOuMXAohbBGky4qgbDqxYgLsYbGQ==, - } - "@swc/counter@0.1.3": resolution: { @@ -4092,25 +4038,22 @@ packages: peerDependencies: tailwindcss: ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" - "@tanstack/react-virtual@3.13.9": + "@tybys/wasm-util@0.10.0": resolution: { - integrity: sha512-SPWC8kwG/dWBf7Py7cfheAPOxuvIv4fFQ54PdmYbg7CpXfsKxkucak43Q0qKsxVthhUJQ1A7CIMAIplq4BjVwA==, - } - 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 - - "@tanstack/virtual-core@3.13.9": - resolution: - { - integrity: sha512-3jztt0jpaoJO5TARe2WIHC1UQC3VMLAFUW5mmMo0yrkwtDB2AQP0+sh10BVUpWrnvHjSLvzFizydtEGLCJKFoQ==, + integrity: sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==, } - "@tybys/wasm-util@0.9.0": + "@types/base16@1.0.5": resolution: { - integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==, + integrity: sha512-OzOWrTluG9cwqidEzC/Q6FAmIPcnZfm8BFRlIx0+UIUqnuAmi5OS88O0RpT3Yz6qdmqObvUhasrbNsCofE4W9A==, + } + + "@types/chai@5.2.2": + resolution: + { + integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==, } "@types/connect@3.4.38": @@ -4131,6 +4074,12 @@ packages: integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==, } + "@types/deep-eql@4.0.2": + resolution: + { + integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==, + } + "@types/eslint-scope@3.7.7": resolution: { @@ -4155,6 +4104,12 @@ packages: integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==, } + "@types/estree@1.0.8": + resolution: + { + integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==, + } + "@types/file-saver@2.0.7": resolution: { @@ -4179,6 +4134,12 @@ packages: integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==, } + "@types/lodash@4.17.20": + resolution: + { + integrity: sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==, + } + "@types/mdast@4.0.4": resolution: { @@ -4295,144 +4256,162 @@ packages: integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==, } - "@typescript-eslint/eslint-plugin@8.32.0": + "@typescript-eslint/eslint-plugin@8.40.0": resolution: { - integrity: sha512-/jU9ettcntkBFmWUzzGgsClEi2ZFiikMX5eEQsmxIAWMOn4H3D4rvHssstmAHGVvrYnaMqdWWWg0b5M6IN/MTQ==, + integrity: sha512-w/EboPlBwnmOBtRbiOvzjD+wdiZdgFeo17lkltrtn7X37vagKKWJABvyfsJXTlHe6XBzugmYgd4A4nW+k8Mixw==, } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: - "@typescript-eslint/parser": ^8.0.0 || ^8.0.0-alpha.0 + "@typescript-eslint/parser": ^8.40.0 eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" + typescript: ">=4.8.4 <6.0.0" - "@typescript-eslint/parser@8.32.0": + "@typescript-eslint/parser@8.40.0": resolution: { - integrity: sha512-B2MdzyWxCE2+SqiZHAjPphft+/2x2FlO9YBx7eKE1BCb+rqBlQdhtAEhzIEdozHd55DXPmxBdpMygFJjfjjA9A==, + integrity: sha512-jCNyAuXx8dr5KJMkecGmZ8KI61KBUhkCob+SD+C+I5+Y1FWI2Y3QmY4/cxMCC5WAsZqoEtEETVhUiUMIGCf6Bw==, } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" + typescript: ">=4.8.4 <6.0.0" - "@typescript-eslint/scope-manager@8.32.0": + "@typescript-eslint/project-service@8.40.0": resolution: { - integrity: sha512-jc/4IxGNedXkmG4mx4nJTILb6TMjL66D41vyeaPWvDUmeYQzF3lKtN15WsAeTr65ce4mPxwopPSo1yUUAWw0hQ==, + integrity: sha512-/A89vz7Wf5DEXsGVvcGdYKbVM9F7DyFXj52lNYUDS1L9yJfqjW/fIp5PgMuEJL/KeqVTe2QSbXAGUZljDUpArw==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + typescript: ">=4.8.4 <6.0.0" + + "@typescript-eslint/scope-manager@8.40.0": + resolution: + { + integrity: sha512-y9ObStCcdCiZKzwqsE8CcpyuVMwRouJbbSrNuThDpv16dFAj429IkM6LNb1dZ2m7hK5fHyzNcErZf7CEeKXR4w==, } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - "@typescript-eslint/type-utils@8.32.0": + "@typescript-eslint/tsconfig-utils@8.40.0": resolution: { - integrity: sha512-t2vouuYQKEKSLtJaa5bB4jHeha2HJczQ6E5IXPDPgIty9EqcJxpr1QHQ86YyIPwDwxvUmLfP2YADQ5ZY4qddZg==, + integrity: sha512-jtMytmUaG9d/9kqSl/W3E3xaWESo4hFDxAIHGVW/WKKtQhesnRIJSAJO6XckluuJ6KDB5woD1EiqknriCtAmcw==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + typescript: ">=4.8.4 <6.0.0" + + "@typescript-eslint/type-utils@8.40.0": + resolution: + { + integrity: sha512-eE60cK4KzAc6ZrzlJnflXdrMqOBaugeukWICO2rB0KNvwdIMaEaYiywwHMzA1qFpTxrLhN9Lp4E/00EgWcD3Ow==, } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" + typescript: ">=4.8.4 <6.0.0" - "@typescript-eslint/types@8.32.0": + "@typescript-eslint/types@8.40.0": resolution: { - integrity: sha512-O5Id6tGadAZEMThM6L9HmVf5hQUXNSxLVKeGJYWNhhVseps/0LddMkp7//VDkzwJ69lPL0UmZdcZwggj9akJaA==, + integrity: sha512-ETdbFlgbAmXHyFPwqUIYrfc12ArvpBhEVgGAxVYSwli26dn8Ko+lIo4Su9vI9ykTZdJn+vJprs/0eZU0YMAEQg==, } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - "@typescript-eslint/typescript-estree@8.32.0": + "@typescript-eslint/typescript-estree@8.40.0": resolution: { - integrity: sha512-pU9VD7anSCOIoBFnhTGfOzlVFQIA1XXiQpH/CezqOBaDppRwTglJzCC6fUQGpfwey4T183NKhF1/mfatYmjRqQ==, + integrity: sha512-k1z9+GJReVVOkc1WfVKs1vBrR5MIKKbdAjDTPvIK3L8De6KbFfPFt6BKpdkdk7rZS2GtC/m6yI5MYX+UsuvVYQ==, } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: - typescript: ">=4.8.4 <5.9.0" + typescript: ">=4.8.4 <6.0.0" - "@typescript-eslint/utils@8.32.0": + "@typescript-eslint/utils@8.40.0": resolution: { - integrity: sha512-8S9hXau6nQ/sYVtC3D6ISIDoJzS1NsCK+gluVhLN2YkBPX+/1wkwyUiDKnxRh15579WoOIyVWnoyIf3yGI9REw==, + integrity: sha512-Cgzi2MXSZyAUOY+BFwGs17s7ad/7L+gKt6Y8rAVVWS+7o6wrjeFN4nVfTpbE25MNcxyJ+iYUXflbs2xR9h4UBg==, } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" + typescript: ">=4.8.4 <6.0.0" - "@typescript-eslint/visitor-keys@8.32.0": + "@typescript-eslint/visitor-keys@8.40.0": resolution: { - integrity: sha512-1rYQTCLFFzOI5Nl0c8LUpJT8HxpwVRn9E4CkMsYfuN6ctmQqExjSTzzSk0Tz2apmXy7WU6/6fyaZVVA/thPN+w==, + integrity: sha512-8CZ47QwalyRjsypfwnbI3hKy5gJDPmrkLjkgMxhi0+DZZ2QNx2naS6/hWoVYUHU7LU2zleF68V9miaVZvhFfTA==, } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - "@typescript/native-preview-darwin-arm64@7.0.0-dev.20250525.1": + "@typescript/native-preview-darwin-arm64@7.0.0-dev.20250819.1": resolution: { - integrity: sha512-XyxwSahQ+9cqMYuvfeZ4niJpRTHUgCMhqN9eaXzMK193mXp7GAlJ4UnZm0NPF538mIXi9PTSAPwmbMYiDMyN1g==, + integrity: sha512-sZAKCg5lW10rg4REkm6Qv14+f2OVN4dX+AfznnxZp3iqj9am+0JCg67la3IVlhznY3jwvqJU6QK54vblK+my5A==, } engines: { node: ">=20.6.0" } cpu: [arm64] os: [darwin] - "@typescript/native-preview-darwin-x64@7.0.0-dev.20250525.1": + "@typescript/native-preview-darwin-x64@7.0.0-dev.20250819.1": resolution: { - integrity: sha512-9qnW/SX2BHzIT7BO5ubdl69FGVA6C6x1c5akLYtlMAjg6ybKBIc2bmoVWd2eSR+B0382AoG4tziLRtDQhcfe+Q==, + integrity: sha512-EkcJWqnldvsEM59hNicAFGp0a3y4ebp60uJdlgxuOC3kFHffMHTUSxhuxySY4djKV9uei5RZR2O5s3XDrjcKoA==, } engines: { node: ">=20.6.0" } cpu: [x64] os: [darwin] - "@typescript/native-preview-linux-arm64@7.0.0-dev.20250525.1": + "@typescript/native-preview-linux-arm64@7.0.0-dev.20250819.1": resolution: { - integrity: sha512-cLYmhcr3rrzVONUKLJkaMaWQCplmprBdpiiiQR8r+sp7nIFv6bCtYgE/p23bPgI5+FRixmCvP3XglDwU+ZRGBA==, + integrity: sha512-nG07i0oVCISBVPqoHKPpBwYUg3XJhuo/QOScvTagJ4o1XS63HCZOldC0cu26ZyvlrgBaWmteVMzyaaIluwupNQ==, } engines: { node: ">=20.6.0" } cpu: [arm64] os: [linux] - "@typescript/native-preview-linux-arm@7.0.0-dev.20250525.1": + "@typescript/native-preview-linux-arm@7.0.0-dev.20250819.1": resolution: { - integrity: sha512-yi/8tRc3E2Y+RdV/KOVsJOsrz9eN9YYCFgf90tG3nlo+8UlA7oJvXQ2ealeadUX3kHU3uALWU4qnfNC6oTy2sg==, + integrity: sha512-yhfOqmxyscXYd6gkrvmDInVx5fntetIrIBHGw1ElNmnmlu5HgguXfEXLxYNMVnBRTzrHtHYPQ8jRd79X1uni+g==, } engines: { node: ">=20.6.0" } cpu: [arm] os: [linux] - "@typescript/native-preview-linux-x64@7.0.0-dev.20250525.1": + "@typescript/native-preview-linux-x64@7.0.0-dev.20250819.1": resolution: { - integrity: sha512-oCr24rfRoppLS1MwGXhXZN1JMwCWfkKdJdzhSY8XJ10TTVLGhhjYaWgD51SlIJNfWH9i+e1/9x2ULIjb3/MrLA==, + integrity: sha512-HrVUxN+VBDiiyi3xk4Ih323ZXAyLWu4CNlI1MGqazhOvahKUjTTvuyzdyTWI7Bzf25KcHIXqfyD0PTqQQRMnVw==, } engines: { node: ">=20.6.0" } cpu: [x64] os: [linux] - "@typescript/native-preview-win32-arm64@7.0.0-dev.20250525.1": + "@typescript/native-preview-win32-arm64@7.0.0-dev.20250819.1": resolution: { - integrity: sha512-xqS00+QZz1oEq7e9EIqs6dnmV4oE24/xRyXqY9b2FLPIXJJNHFKyT8/iLbtrSeOUFOBabpZuVMGGVhi0onTXoA==, + integrity: sha512-TRSeuNIA3CI8BmZhioj+QBFdsywadJWKvdC/dhawaYJJuhwz4jbPD7lg/SA2gWihqDC7ZhPq6eqSgaxobW5/uw==, } engines: { node: ">=20.6.0" } cpu: [arm64] os: [win32] - "@typescript/native-preview-win32-x64@7.0.0-dev.20250525.1": + "@typescript/native-preview-win32-x64@7.0.0-dev.20250819.1": resolution: { - integrity: sha512-tHPIhwhQ3c+J5ENJgfwsEDpMuuID2GPvhYH4Zcy1I2oGJ15DIb53Sqc1dRw+syV3j4/TnQDNDOuUWjJAQvi/aA==, + integrity: sha512-LTLw0FtGmrSZ3mrERcDWJ1P7tvGMZAi5dhbwXBhGHiawX8kwWHZI3J+TqDnU2zrxL22164qsAee2LI4J2bwpNw==, } engines: { node: ">=20.6.0" } cpu: [x64] os: [win32] - "@typescript/native-preview@7.0.0-dev.20250525.1": + "@typescript/native-preview@7.0.0-dev.20250819.1": resolution: { - integrity: sha512-8rcNG2CkBwRc/5zDkbHgGZTYT2oHu4E9r3Ed3JnWrCb0Vf9jSK8bd7lpbTAOIza790Zn6FOiYI9T7SmcTVzxDQ==, + integrity: sha512-+gl0Sg+Ol5QibPacZziwHyImPtPzuYV1BF/n99LG6U9BAMOXoBamU+9Y5C+0gUdesmz5Ik9ZJpUuX4HTBzULig==, } engines: { node: ">=20.6.0" } hasBin: true @@ -4443,90 +4422,154 @@ packages: integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==, } - "@unrs/rspack-resolver-binding-darwin-arm64@1.2.2": + "@unrs/resolver-binding-android-arm-eabi@1.11.1": resolution: { - integrity: sha512-i7z0B+C0P8Q63O/5PXJAzeFtA1ttY3OR2VSJgGv18S+PFNwD98xHgAgPOT1H5HIV6jlQP8Avzbp09qxJUdpPNw==, + integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==, + } + cpu: [arm] + os: [android] + + "@unrs/resolver-binding-android-arm64@1.11.1": + resolution: + { + integrity: sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==, + } + cpu: [arm64] + os: [android] + + "@unrs/resolver-binding-darwin-arm64@1.11.1": + resolution: + { + integrity: sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==, } cpu: [arm64] os: [darwin] - "@unrs/rspack-resolver-binding-darwin-x64@1.2.2": + "@unrs/resolver-binding-darwin-x64@1.11.1": resolution: { - integrity: sha512-YEdFzPjIbDUCfmehC6eS+AdJYtFWY35YYgWUnqqTM2oe/N58GhNy5yRllxYhxwJ9GcfHoNc6Ubze1yjkNv+9Qg==, + integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==, } cpu: [x64] os: [darwin] - "@unrs/rspack-resolver-binding-freebsd-x64@1.2.2": + "@unrs/resolver-binding-freebsd-x64@1.11.1": resolution: { - integrity: sha512-TU4ntNXDgPN2giQyyzSnGWf/dVCem5lvwxg0XYvsvz35h5H19WrhTmHgbrULMuypCB3aHe1enYUC9rPLDw45mA==, + integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==, } cpu: [x64] os: [freebsd] - "@unrs/rspack-resolver-binding-linux-arm-gnueabihf@1.2.2": + "@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1": resolution: { - integrity: sha512-ik3w4/rU6RujBvNWiDnKdXi1smBhqxEDhccNi/j2rHaMjm0Fk49KkJ6XKsoUnD2kZ5xaMJf9JjailW/okfUPIw==, + integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==, } cpu: [arm] os: [linux] - "@unrs/rspack-resolver-binding-linux-arm64-gnu@1.2.2": + "@unrs/resolver-binding-linux-arm-musleabihf@1.11.1": resolution: { - integrity: sha512-fp4Azi8kHz6TX8SFmKfyScZrMLfp++uRm2srpqRjsRZIIBzH74NtSkdEUHImR4G7f7XJ+sVZjCc6KDDK04YEpQ==, + integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==, + } + cpu: [arm] + os: [linux] + + "@unrs/resolver-binding-linux-arm64-gnu@1.11.1": + resolution: + { + integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==, } cpu: [arm64] os: [linux] - "@unrs/rspack-resolver-binding-linux-arm64-musl@1.2.2": + "@unrs/resolver-binding-linux-arm64-musl@1.11.1": resolution: { - integrity: sha512-gMiG3DCFioJxdGBzhlL86KcFgt9HGz0iDhw0YVYPsShItpN5pqIkNrI+L/Q/0gfDiGrfcE0X3VANSYIPmqEAlQ==, + integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==, } cpu: [arm64] os: [linux] - "@unrs/rspack-resolver-binding-linux-x64-gnu@1.2.2": + "@unrs/resolver-binding-linux-ppc64-gnu@1.11.1": resolution: { - integrity: sha512-n/4n2CxaUF9tcaJxEaZm+lqvaw2gflfWQ1R9I7WQgYkKEKbRKbpG/R3hopYdUmLSRI4xaW1Cy0Bz40eS2Yi4Sw==, + integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==, + } + cpu: [ppc64] + os: [linux] + + "@unrs/resolver-binding-linux-riscv64-gnu@1.11.1": + resolution: + { + integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==, + } + cpu: [riscv64] + os: [linux] + + "@unrs/resolver-binding-linux-riscv64-musl@1.11.1": + resolution: + { + integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==, + } + cpu: [riscv64] + os: [linux] + + "@unrs/resolver-binding-linux-s390x-gnu@1.11.1": + resolution: + { + integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==, + } + cpu: [s390x] + os: [linux] + + "@unrs/resolver-binding-linux-x64-gnu@1.11.1": + resolution: + { + integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==, } cpu: [x64] os: [linux] - "@unrs/rspack-resolver-binding-linux-x64-musl@1.2.2": + "@unrs/resolver-binding-linux-x64-musl@1.11.1": resolution: { - integrity: sha512-cHyhAr6rlYYbon1L2Ag449YCj3p6XMfcYTP0AQX+KkQo025d1y/VFtPWvjMhuEsE2lLvtHm7GdJozj6BOMtzVg==, + integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==, } cpu: [x64] os: [linux] - "@unrs/rspack-resolver-binding-wasm32-wasi@1.2.2": + "@unrs/resolver-binding-wasm32-wasi@1.11.1": resolution: { - integrity: sha512-eogDKuICghDLGc32FtP+WniG38IB1RcGOGz0G3z8406dUdjJvxfHGuGs/dSlM9YEp/v0lEqhJ4mBu6X2nL9pog==, + integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==, } engines: { node: ">=14.0.0" } cpu: [wasm32] - "@unrs/rspack-resolver-binding-win32-arm64-msvc@1.2.2": + "@unrs/resolver-binding-win32-arm64-msvc@1.11.1": resolution: { - integrity: sha512-7sWRJumhpXSi2lccX8aQpfFXHsSVASdWndLv8AmD8nDRA/5PBi8IplQVZNx2mYRx6+Bp91Z00kuVqpXO9NfCTg==, + integrity: sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==, } cpu: [arm64] os: [win32] - "@unrs/rspack-resolver-binding-win32-x64-msvc@1.2.2": + "@unrs/resolver-binding-win32-ia32-msvc@1.11.1": resolution: { - integrity: sha512-hewo/UMGP1a7O6FG/ThcPzSJdm/WwrYDNkdGgWl6M18H6K6MSitklomWpT9MUtT5KGj++QJb06va/14QBC4pvw==, + integrity: sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==, + } + cpu: [ia32] + os: [win32] + + "@unrs/resolver-binding-win32-x64-msvc@1.11.1": + resolution: + { + integrity: sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==, } cpu: [x64] os: [win32] @@ -4578,6 +4621,56 @@ packages: vue-router: optional: true + "@vitest/expect@3.2.4": + resolution: + { + integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==, + } + + "@vitest/mocker@3.2.4": + resolution: + { + integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==, + } + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + "@vitest/pretty-format@3.2.4": + resolution: + { + integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==, + } + + "@vitest/runner@3.2.4": + resolution: + { + integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==, + } + + "@vitest/snapshot@3.2.4": + resolution: + { + integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==, + } + + "@vitest/spy@3.2.4": + resolution: + { + integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==, + } + + "@vitest/utils@3.2.4": + resolution: + { + integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==, + } + "@webassemblyjs/ast@1.14.1": resolution: { @@ -4693,13 +4786,6 @@ packages: } engines: { node: ">= 0.6" } - accepts@2.0.0: - resolution: - { - integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==, - } - engines: { node: ">= 0.6" } - acorn-import-attributes@1.9.5: resolution: { @@ -4716,10 +4802,10 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn@8.14.1: + acorn@8.15.0: resolution: { - integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==, + integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==, } engines: { node: ">=0.4.0" } hasBin: true @@ -4856,10 +4942,10 @@ packages: } engines: { node: ">= 0.4" } - array-includes@3.1.8: + array-includes@3.1.9: resolution: { - integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==, + integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==, } engines: { node: ">= 0.4" } @@ -4905,6 +4991,13 @@ packages: } engines: { node: ">= 0.4" } + assertion-error@2.0.1: + resolution: + { + integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==, + } + engines: { node: ">=12" } + ast-types-flow@0.0.8: resolution: { @@ -4974,6 +5067,12 @@ packages: integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==, } + base16@1.0.0: + resolution: + { + integrity: sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==, + } + base64-js@0.0.8: resolution: { @@ -5027,13 +5126,6 @@ packages: integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==, } - body-parser@2.2.0: - resolution: - { - integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==, - } - engines: { node: ">=18" } - brace-expansion@1.1.11: resolution: { @@ -5065,10 +5157,10 @@ packages: integrity: sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==, } - browserslist@4.24.4: + browserslist@4.25.2: resolution: { - integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==, + integrity: sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==, } engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 } hasBin: true @@ -5091,15 +5183,6 @@ packages: integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==, } - bundle-require@5.1.0: - resolution: - { - integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==, - } - engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } - peerDependencies: - esbuild: ">=0.18" - busboy@1.6.0: resolution: { @@ -5107,13 +5190,6 @@ packages: } engines: { node: ">=10.16.0" } - bytes@3.1.2: - resolution: - { - integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==, - } - engines: { node: ">= 0.8" } - cac@6.7.14: resolution: { @@ -5156,10 +5232,10 @@ packages: } engines: { node: ">= 6" } - caniuse-lite@1.0.30001707: + caniuse-lite@1.0.30001733: resolution: { - integrity: sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw==, + integrity: sha512-e4QKw/O2Kavj2VQTKZWrwzkt3IxOmIlU6ajRb6LP64LHpBo1J67k2Hi4Vu/TgJWsNtynurfS0uK3MaUTCPfu5Q==, } canvas@3.1.0: @@ -5175,6 +5251,13 @@ packages: integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==, } + chai@5.2.1: + resolution: + { + integrity: sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==, + } + engines: { node: ">=18" } + chalk@3.0.0: resolution: { @@ -5220,6 +5303,13 @@ packages: integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==, } + check-error@2.1.1: + resolution: + { + integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==, + } + engines: { node: ">= 16" } + chokidar@3.6.0: resolution: { @@ -5329,6 +5419,12 @@ packages: integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==, } + color-convert@1.9.3: + resolution: + { + integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==, + } + color-convert@2.0.1: resolution: { @@ -5336,6 +5432,12 @@ packages: } engines: { node: ">=7.0.0" } + color-name@1.1.3: + resolution: + { + integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==, + } + color-name@1.1.4: resolution: { @@ -5348,6 +5450,12 @@ packages: integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==, } + color@3.2.1: + resolution: + { + integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==, + } + color@4.2.3: resolution: { @@ -5413,46 +5521,12 @@ packages: integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==, } - confbox@0.1.8: - resolution: - { - integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==, - } - - consola@3.4.2: - resolution: - { - integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==, - } - engines: { node: ^14.18.0 || >=16.10.0 } - - content-disposition@1.0.0: - resolution: - { - integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==, - } - engines: { node: ">= 0.6" } - - content-type@1.0.5: - resolution: - { - integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==, - } - engines: { node: ">= 0.6" } - convert-source-map@2.0.0: resolution: { integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==, } - cookie-signature@1.2.2: - resolution: - { - integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==, - } - engines: { node: ">=6.6.0" } - cookie@0.7.2: resolution: { @@ -5460,13 +5534,6 @@ packages: } engines: { node: ">= 0.6" } - copy-anything@3.0.5: - resolution: - { - integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==, - } - engines: { node: ">=12.13" } - core-util-is@1.0.3: resolution: { @@ -5570,10 +5637,10 @@ packages: supports-color: optional: true - debug@4.4.0: + debug@4.4.1: resolution: { - integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==, + integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==, } engines: { node: ">=6.0" } peerDependencies: @@ -5601,6 +5668,13 @@ packages: } engines: { node: ">=10" } + deep-eql@5.0.2: + resolution: + { + integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==, + } + engines: { node: ">=6" } + deep-extend@0.6.0: resolution: { @@ -5648,13 +5722,6 @@ packages: } engines: { node: ">=0.4.0" } - depd@2.0.0: - resolution: - { - integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==, - } - engines: { node: ">= 0.8" } - dequal@2.0.3: resolution: { @@ -5763,16 +5830,10 @@ packages: integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==, } - ee-first@1.1.1: + electron-to-chromium@1.5.199: resolution: { - integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==, - } - - electron-to-chromium@1.5.109: - resolution: - { - integrity: sha512-AidaH9JETVRr9DIPGfp1kAarm/W6hRJTPuCnkF+2MqhF4KaAgRIcBc8nvjk+YMXZhwfISof/7WG29eS4iGxQLQ==, + integrity: sha512-3gl0S7zQd88kCAZRO/DnxtBKuhMO4h0EaQIN3YgZfV6+pW+5+bf2AdQeHNESCoaQqo/gjGVYEf2YM4O5HJQqpQ==, } emoji-regex@10.4.0: @@ -5793,13 +5854,6 @@ packages: integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==, } - encodeurl@2.0.0: - resolution: - { - integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==, - } - engines: { node: ">= 0.8" } - end-of-stream@1.4.4: resolution: { @@ -5820,10 +5874,10 @@ packages: } engines: { node: ">=10.2.0" } - enhanced-resolve@5.18.1: + enhanced-resolve@5.18.3: resolution: { - integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==, + integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==, } engines: { node: ">=10.13.0" } @@ -5841,10 +5895,10 @@ packages: } engines: { node: ">=18" } - es-abstract@1.23.9: + es-abstract@1.24.0: resolution: { - integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==, + integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==, } engines: { node: ">= 0.4" } @@ -5930,12 +5984,6 @@ packages: } engines: { node: ">=6" } - escape-html@1.0.3: - resolution: - { - integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==, - } - escape-string-regexp@4.0.0: resolution: { @@ -5968,10 +6016,10 @@ packages: integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==, } - eslint-import-resolver-typescript@3.9.1: + eslint-import-resolver-typescript@3.10.1: resolution: { - integrity: sha512-euxa5rTGqHeqVxmOHT25hpk58PxkQ4mNoX6Yun4ooGaCHAxOCojJYNvjmyeOQxj/LyW+3fulH0+xtk+p2kPPTw==, + integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==, } engines: { node: ^14.18.0 || >=16.0.0 } peerDependencies: @@ -5984,10 +6032,10 @@ packages: eslint-plugin-import-x: optional: true - eslint-module-utils@2.12.0: + eslint-module-utils@2.12.1: resolution: { - integrity: sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==, + integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==, } engines: { node: ">=4" } peerDependencies: @@ -6008,10 +6056,10 @@ packages: eslint-import-resolver-webpack: optional: true - eslint-plugin-import@2.31.0: + eslint-plugin-import@2.32.0: resolution: { - integrity: sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==, + integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==, } engines: { node: ">=4" } peerDependencies: @@ -6057,10 +6105,10 @@ packages: peerDependencies: eslint: ">=7.0.0" - eslint-plugin-react@7.37.4: + eslint-plugin-react@7.37.5: resolution: { - integrity: sha512-BGP0jRmfYyvOyvMoRX/uoUeW+GqNj9y16bPQzqAHf3AYII/tDs+jMN0dBVkl88/OZwNGwrVFxE7riHsXVfy/LQ==, + integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==, } engines: { node: ">=4" } peerDependencies: @@ -6073,10 +6121,10 @@ packages: } engines: { node: ">=8.0.0" } - eslint-scope@8.3.0: + eslint-scope@8.4.0: resolution: { - integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==, + integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==, } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } @@ -6103,17 +6151,17 @@ packages: } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } - eslint-visitor-keys@4.2.0: + eslint-visitor-keys@4.2.1: resolution: { - integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==, + integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==, } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - eslint@9.26.0: + eslint@9.33.0: resolution: { - integrity: sha512-Hx0MOjPh6uK9oq9nVsATZKE/Wlbai7KFjfCuw9UHaguDW3x+HF0O5nIi3ud39TWgrTjTO5nHxmL3R1eANinWHQ==, + integrity: sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==, } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } hasBin: true @@ -6123,10 +6171,10 @@ packages: jiti: optional: true - espree@10.3.0: + espree@10.4.0: resolution: { - integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==, + integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==, } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } @@ -6213,13 +6261,6 @@ packages: } engines: { node: ">=0.10.0" } - etag@1.8.1: - resolution: - { - integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==, - } - engines: { node: ">= 0.6" } - eventemitter3@5.0.1: resolution: { @@ -6233,20 +6274,6 @@ packages: } engines: { node: ">=0.8.x" } - eventsource-parser@3.0.1: - resolution: - { - integrity: sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==, - } - engines: { node: ">=18.0.0" } - - eventsource@3.0.7: - resolution: - { - integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==, - } - engines: { node: ">=18.0.0" } - execa@8.0.1: resolution: { @@ -6261,21 +6288,12 @@ packages: } engines: { node: ">=6" } - express-rate-limit@7.5.0: + expect-type@1.2.2: resolution: { - integrity: sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==, + integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==, } - engines: { node: ">= 16" } - peerDependencies: - express: ^4.11 || 5 || ^5.0.0-beta.1 - - express@5.1.0: - resolution: - { - integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==, - } - engines: { node: ">= 18" } + engines: { node: ">=12.0.0" } extend@3.0.2: resolution: @@ -6333,10 +6351,10 @@ packages: integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==, } - fd-package-json@1.2.0: + fd-package-json@2.0.0: resolution: { - integrity: sha512-45LSPmWf+gC5tdCQMNH4s9Sr00bIkiD9aN7dc5hqkrEw1geRYyDQS1v1oMHAW3ysfxfndqGsrDREHHjNNbKUfA==, + integrity: sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==, } fdir@6.4.4: @@ -6370,13 +6388,6 @@ packages: } engines: { node: ">=8" } - finalhandler@2.1.0: - resolution: - { - integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==, - } - engines: { node: ">= 0.8" } - find-up@5.0.0: resolution: { @@ -6384,12 +6395,6 @@ packages: } engines: { node: ">=10" } - fix-dts-default-cjs-exports@1.0.1: - resolution: - { - integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==, - } - flat-cache@4.0.1: resolution: { @@ -6430,10 +6435,10 @@ packages: } engines: { node: ">= 6" } - formatly@0.2.3: + formatly@0.2.4: resolution: { - integrity: sha512-WH01vbXEjh9L3bqn5V620xUAWs32CmK4IzWRRY6ep5zpa/mrisL4d9+pRVuETORVDTQw8OycSO1WC68PL51RaA==, + integrity: sha512-lIN7GpcvX/l/i24r/L9bnJ0I8Qn01qijWpQpDDvTLL29nKqSaJJu4h20+7VJ6m2CAhQ2/En/GbxDiHCzq/0MyA==, } engines: { node: ">=18.3.0" } hasBin: true @@ -6444,26 +6449,12 @@ packages: integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==, } - forwarded@0.2.0: - resolution: - { - integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==, - } - engines: { node: ">= 0.6" } - fraction.js@4.3.7: resolution: { integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==, } - fresh@2.0.0: - resolution: - { - integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==, - } - engines: { node: ">= 0.8" } - fs-constants@1.0.0: resolution: { @@ -6574,10 +6565,10 @@ packages: } engines: { node: ">= 0.4" } - get-tsconfig@4.10.0: + get-tsconfig@4.10.1: resolution: { - integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==, + integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==, } github-from-package@0.0.0: @@ -6807,13 +6798,6 @@ packages: integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==, } - http-errors@2.0.0: - resolution: - { - integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==, - } - engines: { node: ">= 0.8" } - https-proxy-agent@5.0.1: resolution: { @@ -6849,13 +6833,6 @@ packages: integrity: sha512-fXHXcGFTXOvZTSkPJuGOQf5Lv5T/R2itiiCVPg9LxAje5D00O0pP83yJShFq5V89Ly//Gt6acj7z8pbBr34stw==, } - iconv-lite@0.6.3: - resolution: - { - integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==, - } - engines: { node: ">=0.10.0" } - ieee754@1.2.1: resolution: { @@ -6869,6 +6846,13 @@ packages: } engines: { node: ">= 4" } + ignore@7.0.5: + resolution: + { + integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==, + } + engines: { node: ">= 4" } + immediate@3.0.6: resolution: { @@ -6926,13 +6910,6 @@ packages: integrity: sha512-UmdmHUmp5CIKKjSoE10la5yfU+AYJAaiYLsodbjL4lji83JNvgOQUjGaGhGrpFCb0Uh7sl7qfP1IyILa8Z40ug==, } - ipaddr.js@1.9.1: - resolution: - { - integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==, - } - engines: { node: ">= 0.10" } - is-alphabetical@2.0.1: resolution: { @@ -6986,10 +6963,10 @@ packages: } engines: { node: ">= 0.4" } - is-bun-module@1.3.0: + is-bun-module@2.0.0: resolution: { - integrity: sha512-DgXeu5UWI0IsMQundYb5UAOzm6G2eVnarJ0byP6Tm55iZNKceD59LNPA2L4VvsScTtHcw0yEkVwSf7PC+QoLSA==, + integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==, } is-callable@1.2.7: @@ -7095,6 +7072,13 @@ packages: } engines: { node: ">= 0.4" } + is-negative-zero@2.0.3: + resolution: + { + integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==, + } + engines: { node: ">= 0.4" } + is-number-object@1.1.1: resolution: { @@ -7116,12 +7100,6 @@ packages: } engines: { node: ">=12" } - is-promise@4.0.0: - resolution: - { - integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==, - } - is-reference@1.2.1: resolution: { @@ -7224,13 +7202,6 @@ packages: } engines: { node: ">= 0.4" } - is-what@4.1.16: - resolution: - { - integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==, - } - engines: { node: ">=12.13" } - isarray@1.0.0: resolution: { @@ -7269,12 +7240,6 @@ packages: integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==, } - javascript-time-ago@2.5.11: - resolution: - { - integrity: sha512-Zeyf5R7oM1fSMW9zsU3YgAYwE0bimEeF54Udn2ixGd8PUwu+z1Yc5t4Y8YScJDMHD6uCx6giLt3VJR5K4CMwbg==, - } - jay-peg@1.1.1: resolution: { @@ -7302,19 +7267,18 @@ packages: } hasBin: true - joycon@3.1.1: - resolution: - { - integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==, - } - engines: { node: ">=10" } - js-tokens@4.0.0: resolution: { integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==, } + js-tokens@9.0.1: + resolution: + { + integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==, + } + js-yaml@4.1.0: resolution: { @@ -7503,13 +7467,6 @@ packages: } engines: { node: ">=18.0.0" } - load-tsconfig@0.2.5: - resolution: - { - integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==, - } - engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } - loader-runner@4.3.0: resolution: { @@ -7530,6 +7487,12 @@ packages: integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==, } + lodash.curry@4.1.1: + resolution: + { + integrity: sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA==, + } + lodash.isplainobject@4.0.6: resolution: { @@ -7542,12 +7505,6 @@ packages: integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==, } - lodash.sortby@4.7.0: - resolution: - { - integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==, - } - log-symbols@4.1.0: resolution: { @@ -7575,6 +7532,12 @@ packages: } hasBin: true + loupe@3.2.0: + resolution: + { + integrity: sha512-2NCfZcT5VGVNX9mSZIxLRkEAegDGBpuQZBy13desuHeVORmBDyAET4TkJr4SjqQy3A8JDofMN6LpkK8Xcm/dlw==, + } + lru-cache@10.4.3: resolution: { @@ -7595,14 +7558,6 @@ packages: peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 - lucide-react@0.503.0: - resolution: - { - integrity: sha512-HGGkdlPWQ0vTF8jJ5TdIqhQXZi6uh3LnNgfZ8MHiuxFfX3RZeA79r2MW2tHAZKlAVfoNE8esm3p+O6VkIvpj6w==, - } - peerDependencies: - react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 - lz-string@1.5.0: resolution: { @@ -7773,20 +7728,6 @@ packages: integrity: sha512-aa5tG6sDoK+k70B9iEX1NeyfT8ObCKhNDs6lJVpwF6r8vhUfuKMslIcirq6HIUYuuUYLefcEQOn9bSBOvawtwg==, } - media-typer@1.1.0: - resolution: - { - integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==, - } - engines: { node: ">= 0.8" } - - merge-descriptors@2.0.0: - resolution: - { - integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==, - } - engines: { node: ">=18" } - merge-refs@1.3.0: resolution: { @@ -8035,13 +7976,6 @@ packages: } engines: { node: ">= 0.6" } - mime-db@1.54.0: - resolution: - { - integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==, - } - engines: { node: ">= 0.6" } - mime-types@2.1.35: resolution: { @@ -8049,13 +7983,6 @@ packages: } engines: { node: ">= 0.6" } - mime-types@3.0.1: - resolution: - { - integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==, - } - engines: { node: ">= 0.6" } - mimic-fn@2.1.0: resolution: { @@ -8130,12 +8057,6 @@ packages: integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==, } - mlly@1.7.4: - resolution: - { - integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==, - } - module-details-from-path@1.0.3: resolution: { @@ -8182,6 +8103,14 @@ packages: integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==, } + napi-postinstall@0.3.3: + resolution: + { + integrity: sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==, + } + engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 } + hasBin: true + natural-compare@1.4.0: resolution: { @@ -8393,13 +8322,6 @@ packages: } engines: { node: ">= 0.4" } - on-finished@2.4.1: - resolution: - { - integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==, - } - engines: { node: ">= 0.8" } - once@1.4.0: resolution: { @@ -8505,13 +8427,6 @@ packages: integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==, } - parseurl@1.3.3: - resolution: - { - integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==, - } - engines: { node: ">= 0.8" } - path-exists@4.0.0: resolution: { @@ -8546,13 +8461,6 @@ packages: } engines: { node: ">=16 || 14 >=14.18" } - path-to-regexp@8.2.0: - resolution: - { - integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==, - } - engines: { node: ">=16" } - path2d@0.2.2: resolution: { @@ -8566,6 +8474,13 @@ packages: integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==, } + pathval@2.0.1: + resolution: + { + integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==, + } + engines: { node: ">= 14.16" } + pdf-parse@1.1.1: resolution: { @@ -8619,10 +8534,10 @@ packages: } engines: { node: ">=8.6" } - picomatch@4.0.2: + picomatch@4.0.3: resolution: { - integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==, + integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==, } engines: { node: ">=12" } @@ -8648,19 +8563,6 @@ packages: } engines: { node: ">= 6" } - pkce-challenge@5.0.0: - resolution: - { - integrity: sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==, - } - engines: { node: ">=16.20.0" } - - pkg-types@1.3.1: - resolution: - { - integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==, - } - playwright-core@1.52.0: resolution: { @@ -8717,27 +8619,6 @@ packages: ts-node: optional: true - postcss-load-config@6.0.1: - resolution: - { - integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==, - } - engines: { node: ">= 18" } - peerDependencies: - jiti: ">=1.21.0" - postcss: ">=8.0.9" - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - jiti: - optional: true - postcss: - optional: true - tsx: - optional: true - yaml: - optional: true - postcss-nested@6.2.0: resolution: { @@ -8747,14 +8628,6 @@ packages: peerDependencies: postcss: ^8.2.14 - postcss-prefix-selector@2.1.1: - resolution: - { - integrity: sha512-ZBgf427Et6+XnrnJ9VXtJEKCjJwTvn2wn/qMg+wvvlRhIeFIAxdbrlZZ0CSsWYMJfcyPLBh8ogj5O1kb/Mcx3g==, - } - peerDependencies: - postcss: ^8.0.0 - postcss-selector-parser@6.0.10: resolution: { @@ -8944,13 +8817,6 @@ packages: integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==, } - proxy-addr@2.0.7: - resolution: - { - integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==, - } - engines: { node: ">= 0.10" } - proxy-from-env@1.1.0: resolution: { @@ -8995,20 +8861,6 @@ packages: integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==, } - range-parser@1.2.1: - resolution: - { - integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==, - } - engines: { node: ">= 0.6" } - - raw-body@3.0.0: - resolution: - { - integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==, - } - engines: { node: ">= 0.8" } - rc@1.2.8: resolution: { @@ -9034,6 +8886,12 @@ packages: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-base16-styling@0.9.1: + resolution: + { + integrity: sha512-1s0CY1zRBOQ5M3T61wetEpvQmsYSNtWEcdYzyZNxKa8t7oDvaOn9d21xrGezGAHFWLM7SHcktPuPTrvoqxSfKw==, + } + react-dom@18.3.1: resolution: { @@ -9077,6 +8935,12 @@ packages: integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==, } + react-lifecycles-compat@3.0.4: + resolution: + { + integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==, + } + react-pdf@9.2.1: resolution: { @@ -9179,6 +9043,15 @@ packages: "@types/react": optional: true + react-textarea-autosize@8.5.9: + resolution: + { + integrity: sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==, + } + engines: { node: ">=10" } + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react@18.3.1: resolution: { @@ -9269,12 +9142,6 @@ packages: integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==, } - relative-time-format@1.1.6: - resolution: - { - integrity: sha512-aCv3juQw4hT1/P/OrVltKWLlp15eW1GRcwP1XdxHrPdZE9MtgqFpegjnTjLhi2m2WI9MT/hQQtE+tjEWG1hgkQ==, - } - remark-gfm@4.0.1: resolution: { @@ -9333,13 +9200,6 @@ packages: } engines: { node: ">=4" } - resolve-from@5.0.0: - resolution: - { - integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==, - } - engines: { node: ">=8" } - resolve-pkg-maps@1.0.0: resolution: { @@ -9417,20 +9277,6 @@ packages: engines: { node: ">=18.0.0", npm: ">=8.0.0" } hasBin: true - router@2.2.0: - resolution: - { - integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==, - } - engines: { node: ">= 18" } - - rspack-resolver@1.2.2: - resolution: - { - integrity: sha512-Fwc19jMBA3g+fxDJH2B4WxwZjE0VaaOL7OX/A4Wn5Zv7bOD/vyPZhzXfaO73Xc2GAlfi96g5fGUa378WbIGfFw==, - } - deprecated: Please migrate to the brand new `@rspack/resolver` or `unrs-resolver` instead - run-parallel@1.2.0: resolution: { @@ -9470,12 +9316,6 @@ packages: } engines: { node: ">= 0.4" } - safer-buffer@2.1.2: - resolution: - { - integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==, - } - scheduler@0.23.2: resolution: { @@ -9514,34 +9354,20 @@ packages: } hasBin: true - semver@7.7.1: + semver@7.7.2: resolution: { - integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==, + integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==, } engines: { node: ">=10" } hasBin: true - send@1.2.0: - resolution: - { - integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==, - } - engines: { node: ">= 18" } - serialize-javascript@6.0.2: resolution: { integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==, } - serve-static@2.2.0: - resolution: - { - integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==, - } - engines: { node: ">= 18" } - set-function-length@1.2.2: resolution: { @@ -9569,12 +9395,6 @@ packages: integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==, } - setprototypeof@1.2.0: - resolution: - { - integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==, - } - sharp@0.33.5: resolution: { @@ -9630,6 +9450,12 @@ packages: } engines: { node: ">= 0.4" } + siginfo@2.0.0: + resolution: + { + integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==, + } + signal-exit@3.0.7: resolution: { @@ -9681,10 +9507,10 @@ packages: } engines: { node: ">=18" } - smol-toml@1.3.4: + smol-toml@1.4.2: resolution: { - integrity: sha512-UOPtVuYkzYGee0Bd2Szz8d2G3RfMfJ2t3qVdZUAozZyAk+a0Sxa+QKix0YCwjL/A1RR0ar44nCxaoN9FxdJGwA==, + integrity: sha512-rInDH6lCNiEyn3+hH8KVGFdbjc099j47+OSgbMrfDYX1CmXLfdKd7qi6IfcWj2wFxvSVkuI46M+wPGYfEOEj6g==, } engines: { node: ">= 18" } @@ -9744,13 +9570,6 @@ packages: } engines: { node: ">= 8" } - source-map@0.8.0-beta.0: - resolution: - { - integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==, - } - engines: { node: ">= 8" } - space-separated-tokens@2.0.2: resolution: { @@ -9763,6 +9582,12 @@ packages: integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==, } + stackback@0.0.2: + resolution: + { + integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==, + } + stacktrace-parser@0.1.11: resolution: { @@ -9770,12 +9595,18 @@ packages: } engines: { node: ">=6" } - statuses@2.0.1: + std-env@3.9.0: resolution: { - integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==, + integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==, } - engines: { node: ">= 0.8" } + + stop-iteration-iterator@1.1.0: + resolution: + { + integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==, + } + engines: { node: ">= 0.4" } streamsearch@1.1.0: resolution: @@ -9920,6 +9751,12 @@ packages: } engines: { node: ">=14.16" } + strip-literal@3.0.0: + resolution: + { + integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==, + } + style-to-js@1.1.17: resolution: { @@ -9972,13 +9809,6 @@ packages: engines: { node: ">=16 || 14 >=14.17" } hasBin: true - superjson@2.2.2: - resolution: - { - integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==, - } - engines: { node: ">=16" } - supports-color@7.2.0: resolution: { @@ -10006,24 +9836,12 @@ packages: integrity: sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==, } - tabbable@6.2.0: - resolution: - { - integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==, - } - tailwind-merge@2.6.0: resolution: { integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==, } - tailwind-merge@3.3.0: - resolution: - { - integrity: sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ==, - } - tailwindcss-animate@1.0.7: resolution: { @@ -10040,16 +9858,10 @@ packages: engines: { node: ">=14.0.0" } hasBin: true - tailwindcss@4.1.7: + tapable@2.2.2: resolution: { - integrity: sha512-kr1o/ErIdNhTz8uzAYL7TpaUuzKIE6QPQ4qmSdxnoX/lo+5wmUHQA6h3L5yIqEImSRnAAURDirLu/BgiXGPAhg==, - } - - tapable@2.2.1: - resolution: - { - integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==, + integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==, } engines: { node: ">=6" } @@ -10085,10 +9897,10 @@ packages: uglify-js: optional: true - terser@5.39.0: + terser@5.43.1: resolution: { - integrity: sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==, + integrity: sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==, } engines: { node: ">=10" } hasBin: true @@ -10118,19 +9930,46 @@ packages: integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==, } + tinybench@2.9.0: + resolution: + { + integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==, + } + tinyexec@0.3.2: resolution: { integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==, } - tinyglobby@0.2.13: + tinyglobby@0.2.14: resolution: { - integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==, + integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==, } engines: { node: ">=12.0.0" } + tinypool@1.1.1: + resolution: + { + integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==, + } + engines: { node: ^18.0.0 || >=20.0.0 } + + tinyrainbow@2.0.0: + resolution: + { + integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==, + } + engines: { node: ">=14.0.0" } + + tinyspy@4.0.3: + resolution: + { + integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==, + } + engines: { node: ">=14.0.0" } + to-regex-range@5.0.1: resolution: { @@ -10138,32 +9977,12 @@ packages: } engines: { node: ">=8.0" } - toidentifier@1.0.1: - resolution: - { - integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==, - } - engines: { node: ">=0.6" } - tr46@0.0.3: resolution: { integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==, } - tr46@1.0.1: - resolution: - { - integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==, - } - - tree-kill@1.2.2: - resolution: - { - integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==, - } - hasBin: true - trim-lines@3.0.1: resolution: { @@ -10203,28 +10022,6 @@ packages: integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==, } - tsup@8.5.0: - resolution: - { - integrity: sha512-VmBp77lWNQq6PfuMqCHD3xWl22vEoWsKajkF8t+yMBawlUS8JzEI+vOVMeuNZIuMML8qXRizFKi9oD5glKQVcQ==, - } - engines: { node: ">=18" } - hasBin: true - peerDependencies: - "@microsoft/api-extractor": ^7.36.0 - "@swc/core": ^1 - postcss: ^8.4.12 - typescript: ">=4.5.0" - peerDependenciesMeta: - "@microsoft/api-extractor": - optional: true - "@swc/core": - optional: true - postcss: - optional: true - typescript: - optional: true - tsx@4.19.4: resolution: { @@ -10260,13 +10057,6 @@ packages: } engines: { node: ">=8" } - type-is@2.0.1: - resolution: - { - integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==, - } - engines: { node: ">= 0.6" } - typed-array-buffer@1.0.3: resolution: { @@ -10295,20 +10085,20 @@ packages: } engines: { node: ">= 0.4" } - typescript-eslint@8.32.0: + typescript-eslint@8.40.0: resolution: { - integrity: sha512-UMq2kxdXCzinFFPsXc9o2ozIpYCCOiEC46MG3yEh5Vipq6BO27otTtEBZA1fQ66DulEUgE97ucQ/3YY66CPg0A==, + integrity: sha512-Xvd2l+ZmFDPEt4oj1QEXzA4A2uUK6opvKu3eGN9aGjB8au02lIVcLyi375w94hHyejTOmzIU77L8ol2sRg9n7Q==, } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" + typescript: ">=4.8.4 <6.0.0" - typescript@5.8.3: + typescript@5.9.2: resolution: { - integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==, + integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==, } engines: { node: ">=14.17" } hasBin: true @@ -10326,12 +10116,6 @@ packages: } hasBin: true - ufo@1.6.1: - resolution: - { - integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==, - } - unbox-primitive@1.1.0: resolution: { @@ -10399,13 +10183,6 @@ packages: integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==, } - unpipe@1.0.0: - resolution: - { - integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==, - } - engines: { node: ">= 0.8" } - unplugin@1.0.1: resolution: { @@ -10419,6 +10196,12 @@ packages: } engines: { node: ">=18.12.0" } + unrs-resolver@1.11.1: + resolution: + { + integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==, + } + update-browserslist-db@1.1.3: resolution: { @@ -10453,6 +10236,18 @@ packages: "@types/react": optional: true + use-composed-ref@1.4.0: + resolution: + { + integrity: sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + use-debounce@10.0.4: resolution: { @@ -10470,6 +10265,30 @@ packages: peerDependencies: react: ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0 + use-isomorphic-layout-effect@1.2.1: + resolution: + { + integrity: sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + + use-latest@1.3.0: + resolution: + { + integrity: sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + use-sidecar@1.1.3: resolution: { @@ -10530,13 +10349,13 @@ packages: } engines: { node: ">= 6" } - vite-plugin-css-injected-by-js@3.5.2: + vite-node@3.2.4: resolution: { - integrity: sha512-2MpU/Y+SCZyWUB6ua3HbJCrgnF0KACAsmzOQt1UvRVJCGF6S8xdA3ZUhWcWdM9ivG4I5az8PnQmwwrkC2CAQrQ==, + integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==, } - peerDependencies: - vite: ">2.0.0-0" + engines: { node: ^18.0.0 || ^20.0.0 || >=22.0.0 } + hasBin: true vite@6.3.5: resolution: @@ -10581,11 +10400,43 @@ packages: yaml: optional: true - walk-up-path@3.0.1: + vitest@3.2.4: resolution: { - integrity: sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==, + integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==, } + engines: { node: ^18.0.0 || ^20.0.0 || >=22.0.0 } + hasBin: true + peerDependencies: + "@edge-runtime/vm": "*" + "@types/debug": ^4.1.12 + "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 + "@vitest/browser": 3.2.4 + "@vitest/ui": 3.2.4 + happy-dom: "*" + jsdom: "*" + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@types/debug": + optional: true + "@types/node": + optional: true + "@vitest/browser": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + walk-up-path@4.0.0: + resolution: + { + integrity: sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==, + } + engines: { node: 20 || >=22 } warning@4.0.3: resolution: @@ -10593,10 +10444,10 @@ packages: integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==, } - watchpack@2.4.2: + watchpack@2.4.4: resolution: { - integrity: sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==, + integrity: sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==, } engines: { node: ">=10.13.0" } @@ -10612,16 +10463,10 @@ packages: integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==, } - webidl-conversions@4.0.2: + webpack-sources@3.3.3: resolution: { - integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==, - } - - webpack-sources@3.2.3: - resolution: - { - integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==, + integrity: sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==, } engines: { node: ">=10.13.0" } @@ -10656,12 +10501,6 @@ packages: integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==, } - whatwg-url@7.1.0: - resolution: - { - integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==, - } - which-boxed-primitive@1.1.1: resolution: { @@ -10698,6 +10537,14 @@ packages: engines: { node: ">= 8" } hasBin: true + why-is-node-running@2.3.0: + resolution: + { + integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==, + } + engines: { node: ">=8" } + hasBin: true + word-wrap@1.2.5: resolution: { @@ -10781,22 +10628,14 @@ packages: integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==, } - zod-to-json-schema@3.24.5: + zod-validation-error@3.5.3: resolution: { - integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==, - } - peerDependencies: - zod: ^3.24.1 - - zod-validation-error@3.4.1: - resolution: - { - integrity: sha512-1KP64yqDPQ3rupxNv7oXhf7KdhHHgaqbKuspVoiN93TT0xrBjql+Svjkdjq/Qh/7GSMmgQs3AfvBT0heE35thw==, + integrity: sha512-OT5Y8lbUadqVZCsnyFaTQ4/O2mys4tj7PqhdbBCp7McPwvIEKfPtdA6QfPeFQK2/Rz5LgwmAXRJTugBNBi0btw==, } engines: { node: ">=18.0.0" } peerDependencies: - zod: ^3.24.4 + zod: ^3.25.0 || ^4.0.0 zod@3.24.4: resolution: @@ -10804,27 +10643,6 @@ packages: integrity: sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==, } - zustand@5.0.5: - resolution: - { - integrity: sha512-mILtRfKW9xM47hqxGIxCv12gXusoY/xTSHBYApXozR0HmQv299whhBeeAcRy+KrPPybzosvJBCOmVjq6x12fCg==, - } - engines: { node: ">=12.20.0" } - peerDependencies: - "@types/react": ">=18.0.0" - immer: ">=9.0.6" - react: ">=18.0.0" - use-sync-external-store: ">=1.2.0" - peerDependenciesMeta: - "@types/react": - optional: true - immer: - optional: true - react: - optional: true - use-sync-external-store: - optional: true - zwitch@2.0.4: resolution: { @@ -10836,8 +10654,8 @@ snapshots: "@ampproject/remapping@2.3.0": dependencies: - "@jridgewell/gen-mapping": 0.3.8 - "@jridgewell/trace-mapping": 0.3.25 + "@jridgewell/gen-mapping": 0.3.12 + "@jridgewell/trace-mapping": 0.3.29 "@ark/schema@0.46.0": dependencies: @@ -10866,7 +10684,7 @@ snapshots: "@babel/traverse": 7.26.9 "@babel/types": 7.26.9 convert-source-map: 2.0.0 - debug: 4.4.0 + debug: 4.4.1 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -10877,15 +10695,15 @@ snapshots: dependencies: "@babel/parser": 7.26.9 "@babel/types": 7.26.9 - "@jridgewell/gen-mapping": 0.3.8 - "@jridgewell/trace-mapping": 0.3.25 + "@jridgewell/gen-mapping": 0.3.12 + "@jridgewell/trace-mapping": 0.3.29 jsesc: 3.1.0 "@babel/helper-compilation-targets@7.26.5": dependencies: "@babel/compat-data": 7.26.8 "@babel/helper-validator-option": 7.25.9 - browserslist: 4.24.4 + browserslist: 4.25.2 lru-cache: 5.1.1 semver: 6.3.1 @@ -10941,7 +10759,7 @@ snapshots: "@babel/parser": 7.26.9 "@babel/template": 7.26.9 "@babel/types": 7.26.9 - debug: 4.4.0 + debug: 4.4.1 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -10953,7 +10771,7 @@ snapshots: "@babel/parser": 7.26.9 "@babel/template": 7.26.9 "@babel/types": 7.26.9 - debug: 4.4.0 + debug: 4.4.1 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -10974,18 +10792,18 @@ snapshots: picocolors: 1.1.1 sisteransi: 1.0.5 - "@emnapi/core@1.3.1": + "@emnapi/core@1.4.5": dependencies: - "@emnapi/wasi-threads": 1.0.1 + "@emnapi/wasi-threads": 1.0.4 tslib: 2.8.1 optional: true - "@emnapi/runtime@1.3.1": + "@emnapi/runtime@1.4.5": dependencies: tslib: 2.8.1 optional: true - "@emnapi/wasi-threads@1.0.1": + "@emnapi/wasi-threads@1.0.4": dependencies: tslib: 2.8.1 optional: true @@ -11065,32 +10883,32 @@ snapshots: "@esbuild/win32-x64@0.25.0": optional: true - "@eslint-community/eslint-utils@4.7.0(eslint@9.26.0(jiti@2.4.2))": + "@eslint-community/eslint-utils@4.7.0(eslint@9.33.0(jiti@2.4.2))": dependencies: - eslint: 9.26.0(jiti@2.4.2) + eslint: 9.33.0(jiti@2.4.2) eslint-visitor-keys: 3.4.3 "@eslint-community/regexpp@4.12.1": {} - "@eslint/config-array@0.20.0": + "@eslint/config-array@0.21.0": dependencies: "@eslint/object-schema": 2.1.6 - debug: 4.4.0 + debug: 4.4.1 minimatch: 3.1.2 transitivePeerDependencies: - supports-color - "@eslint/config-helpers@0.2.2": {} + "@eslint/config-helpers@0.3.1": {} - "@eslint/core@0.13.0": + "@eslint/core@0.15.2": dependencies: "@types/json-schema": 7.0.15 "@eslint/eslintrc@3.3.1": dependencies: ajv: 6.12.6 - debug: 4.4.0 - espree: 10.3.0 + debug: 4.4.1 + espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.0 @@ -11100,13 +10918,13 @@ snapshots: transitivePeerDependencies: - supports-color - "@eslint/js@9.26.0": {} + "@eslint/js@9.33.0": {} "@eslint/object-schema@2.1.6": {} - "@eslint/plugin-kit@0.2.8": + "@eslint/plugin-kit@0.3.5": dependencies: - "@eslint/core": 0.13.0 + "@eslint/core": 0.15.2 levn: 0.4.1 "@floating-ui/core@1.6.9": @@ -11124,14 +10942,6 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - "@floating-ui/react@0.26.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": - dependencies: - "@floating-ui/react-dom": 2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - "@floating-ui/utils": 0.2.9 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - tabbable: 6.2.0 - "@floating-ui/utils@0.2.9": {} "@formatjs/ecma402-abstract@2.3.4": @@ -11164,16 +10974,6 @@ snapshots: dependencies: tslib: 2.8.1 - "@headlessui/react@2.2.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": - dependencies: - "@floating-ui/react": 0.26.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - "@react-aria/focus": 3.20.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - "@react-aria/interactions": 3.25.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - "@tanstack/react-virtual": 3.13.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - use-sync-external-store: 1.5.0(react@18.3.1) - "@hookform/resolvers@3.9.0(react-hook-form@7.53.1(react@18.3.1))": dependencies: react-hook-form: 7.53.1(react@18.3.1) @@ -11257,7 +11057,7 @@ snapshots: "@img/sharp-wasm32@0.33.5": dependencies: - "@emnapi/runtime": 1.3.1 + "@emnapi/runtime": 1.4.5 optional: true "@img/sharp-win32-ia32@0.33.5": @@ -11292,41 +11092,38 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 - "@jridgewell/gen-mapping@0.3.8": + "@jridgewell/gen-mapping@0.3.12": dependencies: - "@jridgewell/set-array": 1.2.1 - "@jridgewell/sourcemap-codec": 1.5.0 - "@jridgewell/trace-mapping": 0.3.25 + "@jridgewell/sourcemap-codec": 1.5.4 + "@jridgewell/trace-mapping": 0.3.29 "@jridgewell/resolve-uri@3.1.2": {} - "@jridgewell/set-array@1.2.1": {} - - "@jridgewell/source-map@0.3.6": + "@jridgewell/source-map@0.3.10": dependencies: - "@jridgewell/gen-mapping": 0.3.8 - "@jridgewell/trace-mapping": 0.3.25 + "@jridgewell/gen-mapping": 0.3.12 + "@jridgewell/trace-mapping": 0.3.29 - "@jridgewell/sourcemap-codec@1.5.0": {} + "@jridgewell/sourcemap-codec@1.5.4": {} - "@jridgewell/trace-mapping@0.3.25": + "@jridgewell/trace-mapping@0.3.29": dependencies: "@jridgewell/resolve-uri": 3.1.2 - "@jridgewell/sourcemap-codec": 1.5.0 + "@jridgewell/sourcemap-codec": 1.5.4 - "@mdx-js/loader@3.1.0(acorn@8.14.1)(webpack@5.98.0(esbuild@0.25.0))": + "@mdx-js/loader@3.1.0(acorn@8.15.0)(webpack@5.98.0)": dependencies: - "@mdx-js/mdx": 3.1.0(acorn@8.14.1) + "@mdx-js/mdx": 3.1.0(acorn@8.15.0) source-map: 0.7.4 optionalDependencies: - webpack: 5.98.0(esbuild@0.25.0) + webpack: 5.98.0 transitivePeerDependencies: - acorn - supports-color - "@mdx-js/mdx@3.1.0(acorn@8.14.1)": + "@mdx-js/mdx@3.1.0(acorn@8.15.0)": dependencies: - "@types/estree": 1.0.7 + "@types/estree": 1.0.8 "@types/estree-jsx": 1.0.5 "@types/hast": 3.0.4 "@types/mdx": 2.0.13 @@ -11338,7 +11135,7 @@ snapshots: hast-util-to-jsx-runtime: 2.3.6 markdown-extensions: 2.0.0 recma-build-jsx: 1.0.0 - recma-jsx: 1.0.0(acorn@8.14.1) + recma-jsx: 1.0.0(acorn@8.15.0) recma-stringify: 1.0.0 rehype-recma: 1.0.0 remark-mdx: 3.1.0 @@ -11360,26 +11157,21 @@ snapshots: "@types/react": 18.3.12 react: 18.3.1 - "@modelcontextprotocol/sdk@1.11.1": + "@microlink/react-json-view@1.27.0(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": dependencies: - content-type: 1.0.5 - cors: 2.8.5 - cross-spawn: 7.0.6 - eventsource: 3.0.7 - express: 5.1.0 - express-rate-limit: 7.5.0(express@5.1.0) - pkce-challenge: 5.0.0 - raw-body: 3.0.0 - zod: 3.24.4 - zod-to-json-schema: 3.24.5(zod@3.24.4) + react: 18.3.1 + react-base16-styling: 0.9.1 + react-dom: 18.3.1(react@18.3.1) + react-lifecycles-compat: 3.0.4 + react-textarea-autosize: 8.5.9(@types/react@18.3.12)(react@18.3.1) transitivePeerDependencies: - - supports-color + - "@types/react" - "@napi-rs/wasm-runtime@0.2.7": + "@napi-rs/wasm-runtime@0.2.12": dependencies: - "@emnapi/core": 1.3.1 - "@emnapi/runtime": 1.3.1 - "@tybys/wasm-util": 0.9.0 + "@emnapi/core": 1.4.5 + "@emnapi/runtime": 1.4.5 + "@tybys/wasm-util": 0.10.0 optional: true "@next/env@14.2.15": {} @@ -11390,11 +11182,11 @@ snapshots: dependencies: fast-glob: 3.3.1 - "@next/mdx@15.3.3(@mdx-js/loader@3.1.0(acorn@8.14.1)(webpack@5.98.0(esbuild@0.25.0)))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))": + "@next/mdx@15.3.3(@mdx-js/loader@3.1.0(acorn@8.15.0)(webpack@5.98.0))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))": dependencies: source-map: 0.7.4 optionalDependencies: - "@mdx-js/loader": 3.1.0(acorn@8.14.1)(webpack@5.98.0(esbuild@0.25.0)) + "@mdx-js/loader": 3.1.0(acorn@8.15.0)(webpack@5.98.0) "@mdx-js/react": 3.1.0(@types/react@18.3.12)(react@18.3.1) "@next/swc-darwin-arm64@14.2.15": @@ -11559,7 +11351,7 @@ snapshots: "@opentelemetry/instrumentation": 0.57.2(@opentelemetry/api@1.9.0) "@opentelemetry/semantic-conventions": 1.28.0 forwarded-parse: 2.1.2 - semver: 7.7.1 + semver: 7.7.2 transitivePeerDependencies: - supports-color @@ -11684,7 +11476,7 @@ snapshots: "@types/shimmer": 1.2.0 import-in-the-middle: 1.13.1 require-in-the-middle: 7.5.2 - semver: 7.7.1 + semver: 7.7.2 shimmer: 1.2.1 transitivePeerDependencies: - supports-color @@ -11725,10 +11517,6 @@ snapshots: dependencies: playwright: 1.52.0 - "@preact/compat@18.3.1(preact@10.26.6)": - dependencies: - preact: 10.26.6 - "@preact/signals-core@1.8.0": {} "@preact/signals@1.3.2(preact@10.26.6)": @@ -13570,18 +13358,18 @@ snapshots: "@rollup/pluginutils": 5.1.4(rollup@3.29.5) commondir: 1.0.1 estree-walker: 2.0.2 - fdir: 6.4.4(picomatch@4.0.2) + fdir: 6.4.4(picomatch@4.0.3) is-reference: 1.2.1 magic-string: 0.30.17 - picomatch: 4.0.2 + picomatch: 4.0.3 optionalDependencies: rollup: 3.29.5 "@rollup/pluginutils@5.1.4(rollup@3.29.5)": dependencies: - "@types/estree": 1.0.7 + "@types/estree": 1.0.8 estree-walker: 2.0.2 - picomatch: 4.0.2 + picomatch: 4.0.3 optionalDependencies: rollup: 3.29.5 @@ -13647,7 +13435,7 @@ snapshots: "@rtsao/scc@1.1.0": {} - "@rushstack/eslint-patch@1.11.0": {} + "@rushstack/eslint-patch@1.12.0": {} "@schummar/icu-type-parser@1.21.5": {} @@ -13740,7 +13528,7 @@ snapshots: "@sentry/core@9.3.0": {} - "@sentry/nextjs@9.3.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(next@14.2.15(@babel/core@7.26.9)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(webpack@5.98.0(esbuild@0.25.0))": + "@sentry/nextjs@9.3.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(next@14.2.15(@babel/core@7.26.9)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(webpack@5.98.0)": dependencies: "@opentelemetry/api": 1.9.0 "@opentelemetry/semantic-conventions": 1.30.0 @@ -13751,7 +13539,7 @@ snapshots: "@sentry/opentelemetry": 9.3.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.30.0) "@sentry/react": 9.3.0(react@18.3.1) "@sentry/vercel-edge": 9.3.0 - "@sentry/webpack-plugin": 3.1.2(webpack@5.98.0(esbuild@0.25.0)) + "@sentry/webpack-plugin": 3.1.2(webpack@5.98.0) chalk: 3.0.0 next: 14.2.15(@babel/core@7.26.9)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) resolve: 1.22.8 @@ -13828,94 +13616,18 @@ snapshots: "@opentelemetry/api": 1.9.0 "@sentry/core": 9.3.0 - "@sentry/webpack-plugin@3.1.2(webpack@5.98.0(esbuild@0.25.0))": + "@sentry/webpack-plugin@3.1.2(webpack@5.98.0)": dependencies: "@sentry/bundler-plugin-core": 3.1.2 unplugin: 1.0.1 uuid: 9.0.1 - webpack: 5.98.0(esbuild@0.25.0) + webpack: 5.98.0 transitivePeerDependencies: - encoding - supports-color "@socket.io/component-emitter@3.1.2": {} - "@stagewise/toolbar-next@0.1.2(@types/react@18.3.12)(jiti@2.4.2)(next@14.2.15(@babel/core@7.26.9)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(postcss@8.5.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tsx@4.19.4)(typescript@5.8.3)(use-sync-external-store@1.5.0(react@18.3.1))(vite@6.3.5(@types/node@22.8.1)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.4)(yaml@2.7.0))(yaml@2.7.0)": - dependencies: - "@stagewise/toolbar-react": 0.1.2(@types/react@18.3.12)(jiti@2.4.2)(postcss@8.5.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tsx@4.19.4)(typescript@5.8.3)(use-sync-external-store@1.5.0(react@18.3.1))(vite@6.3.5(@types/node@22.8.1)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.4)(yaml@2.7.0))(yaml@2.7.0) - "@types/react": 18.3.12 - next: 14.2.15(@babel/core@7.26.9)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - transitivePeerDependencies: - - "@microsoft/api-extractor" - - "@swc/core" - - encoding - - immer - - jiti - - postcss - - react-dom - - supports-color - - tsx - - typescript - - use-sync-external-store - - vite - - yaml - - "@stagewise/toolbar-react@0.1.2(@types/react@18.3.12)(jiti@2.4.2)(postcss@8.5.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tsx@4.19.4)(typescript@5.8.3)(use-sync-external-store@1.5.0(react@18.3.1))(vite@6.3.5(@types/node@22.8.1)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.4)(yaml@2.7.0))(yaml@2.7.0)": - dependencies: - "@stagewise/toolbar": 0.2.1(@types/react@18.3.12)(jiti@2.4.2)(postcss@8.5.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tsx@4.19.4)(typescript@5.8.3)(use-sync-external-store@1.5.0(react@18.3.1))(vite@6.3.5(@types/node@22.8.1)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.4)(yaml@2.7.0))(yaml@2.7.0) - "@types/react": 18.3.12 - react: 18.3.1 - transitivePeerDependencies: - - "@microsoft/api-extractor" - - "@swc/core" - - encoding - - immer - - jiti - - postcss - - react-dom - - supports-color - - tsx - - typescript - - use-sync-external-store - - vite - - yaml - - "@stagewise/toolbar@0.2.1(@types/react@18.3.12)(jiti@2.4.2)(postcss@8.5.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tsx@4.19.4)(typescript@5.8.3)(use-sync-external-store@1.5.0(react@18.3.1))(vite@6.3.5(@types/node@22.8.1)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.4)(yaml@2.7.0))(yaml@2.7.0)": - dependencies: - "@headlessui/react": 2.2.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - "@preact/compat": 18.3.1(preact@10.26.6) - clsx: 2.1.1 - javascript-time-ago: 2.5.11 - lucide-react: 0.503.0(react@18.3.1) - postcss-prefix-selector: 2.1.1(postcss@8.5.3) - preact: 10.26.6 - react-remove-scroll: 2.6.3(@types/react@18.3.12)(react@18.3.1) - superjson: 2.2.2 - tailwind-merge: 3.3.0 - tailwindcss: 4.1.7 - tsup: 8.5.0(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.4)(typescript@5.8.3)(yaml@2.7.0) - ua-parser-js: 2.0.3 - vite-plugin-css-injected-by-js: 3.5.2(vite@6.3.5(@types/node@22.8.1)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.4)(yaml@2.7.0)) - zod: 3.24.4 - zustand: 5.0.5(@types/react@18.3.12)(react@18.3.1)(use-sync-external-store@1.5.0(react@18.3.1)) - transitivePeerDependencies: - - "@microsoft/api-extractor" - - "@swc/core" - - "@types/react" - - encoding - - immer - - jiti - - postcss - - react - - react-dom - - supports-color - - tsx - - typescript - - use-sync-external-store - - vite - - yaml - "@swc/counter@0.1.3": {} "@swc/helpers@0.5.15": @@ -13927,18 +13639,18 @@ snapshots: "@swc/counter": 0.1.3 tslib: 2.8.1 - "@t3-oss/env-core@0.13.4(arktype@2.1.20)(typescript@5.8.3)(zod@3.24.4)": + "@t3-oss/env-core@0.13.4(arktype@2.1.20)(typescript@5.9.2)(zod@3.24.4)": dependencies: arktype: 2.1.20 optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.2 zod: 3.24.4 - "@t3-oss/env-nextjs@0.13.4(arktype@2.1.20)(typescript@5.8.3)(zod@3.24.4)": + "@t3-oss/env-nextjs@0.13.4(arktype@2.1.20)(typescript@5.9.2)(zod@3.24.4)": dependencies: - "@t3-oss/env-core": 0.13.4(arktype@2.1.20)(typescript@5.8.3)(zod@3.24.4) + "@t3-oss/env-core": 0.13.4(arktype@2.1.20)(typescript@5.9.2)(zod@3.24.4) optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.2 zod: 3.24.4 transitivePeerDependencies: - arktype @@ -13951,19 +13663,17 @@ snapshots: postcss-selector-parser: 6.0.10 tailwindcss: 3.4.14 - "@tanstack/react-virtual@3.13.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": - dependencies: - "@tanstack/virtual-core": 3.13.9 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - "@tanstack/virtual-core@3.13.9": {} - - "@tybys/wasm-util@0.9.0": + "@tybys/wasm-util@0.10.0": dependencies: tslib: 2.8.1 optional: true + "@types/base16@1.0.5": {} + + "@types/chai@5.2.2": + dependencies: + "@types/deep-eql": 4.0.2 + "@types/connect@3.4.38": dependencies: "@types/node": 22.8.1 @@ -13976,22 +13686,26 @@ snapshots: dependencies: "@types/ms": 2.1.0 + "@types/deep-eql@4.0.2": {} + "@types/eslint-scope@3.7.7": dependencies: "@types/eslint": 9.6.1 - "@types/estree": 1.0.7 + "@types/estree": 1.0.8 "@types/eslint@9.6.1": dependencies: - "@types/estree": 1.0.7 + "@types/estree": 1.0.8 "@types/json-schema": 7.0.15 "@types/estree-jsx@1.0.5": dependencies: - "@types/estree": 1.0.7 + "@types/estree": 1.0.8 "@types/estree@1.0.7": {} + "@types/estree@1.0.8": {} + "@types/file-saver@2.0.7": {} "@types/hast@3.0.4": @@ -14002,6 +13716,8 @@ snapshots: "@types/json5@0.0.29": {} + "@types/lodash@4.17.20": {} + "@types/mdast@4.0.4": dependencies: "@types/unist": 3.0.3 @@ -14068,149 +13784,189 @@ snapshots: "@types/unist@3.0.3": {} - "@typescript-eslint/eslint-plugin@8.32.0(@typescript-eslint/parser@8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)": + "@typescript-eslint/eslint-plugin@8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.33.0(jiti@2.4.2))(typescript@5.9.2)": dependencies: "@eslint-community/regexpp": 4.12.1 - "@typescript-eslint/parser": 8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) - "@typescript-eslint/scope-manager": 8.32.0 - "@typescript-eslint/type-utils": 8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) - "@typescript-eslint/utils": 8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) - "@typescript-eslint/visitor-keys": 8.32.0 - eslint: 9.26.0(jiti@2.4.2) + "@typescript-eslint/parser": 8.40.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.9.2) + "@typescript-eslint/scope-manager": 8.40.0 + "@typescript-eslint/type-utils": 8.40.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.9.2) + "@typescript-eslint/utils": 8.40.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.9.2) + "@typescript-eslint/visitor-keys": 8.40.0 + eslint: 9.33.0(jiti@2.4.2) graphemer: 1.4.0 - ignore: 5.3.2 + ignore: 7.0.5 natural-compare: 1.4.0 - ts-api-utils: 2.1.0(typescript@5.8.3) - typescript: 5.8.3 + ts-api-utils: 2.1.0(typescript@5.9.2) + typescript: 5.9.2 transitivePeerDependencies: - supports-color - "@typescript-eslint/parser@8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)": + "@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.9.2)": dependencies: - "@typescript-eslint/scope-manager": 8.32.0 - "@typescript-eslint/types": 8.32.0 - "@typescript-eslint/typescript-estree": 8.32.0(typescript@5.8.3) - "@typescript-eslint/visitor-keys": 8.32.0 - debug: 4.4.0 - eslint: 9.26.0(jiti@2.4.2) - typescript: 5.8.3 + "@typescript-eslint/scope-manager": 8.40.0 + "@typescript-eslint/types": 8.40.0 + "@typescript-eslint/typescript-estree": 8.40.0(typescript@5.9.2) + "@typescript-eslint/visitor-keys": 8.40.0 + debug: 4.4.1 + eslint: 9.33.0(jiti@2.4.2) + typescript: 5.9.2 transitivePeerDependencies: - supports-color - "@typescript-eslint/scope-manager@8.32.0": + "@typescript-eslint/project-service@8.40.0(typescript@5.9.2)": dependencies: - "@typescript-eslint/types": 8.32.0 - "@typescript-eslint/visitor-keys": 8.32.0 - - "@typescript-eslint/type-utils@8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)": - dependencies: - "@typescript-eslint/typescript-estree": 8.32.0(typescript@5.8.3) - "@typescript-eslint/utils": 8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) - debug: 4.4.0 - eslint: 9.26.0(jiti@2.4.2) - ts-api-utils: 2.1.0(typescript@5.8.3) - typescript: 5.8.3 + "@typescript-eslint/tsconfig-utils": 8.40.0(typescript@5.9.2) + "@typescript-eslint/types": 8.40.0 + debug: 4.4.1 + typescript: 5.9.2 transitivePeerDependencies: - supports-color - "@typescript-eslint/types@8.32.0": {} - - "@typescript-eslint/typescript-estree@8.32.0(typescript@5.8.3)": + "@typescript-eslint/scope-manager@8.40.0": dependencies: - "@typescript-eslint/types": 8.32.0 - "@typescript-eslint/visitor-keys": 8.32.0 - debug: 4.4.0 + "@typescript-eslint/types": 8.40.0 + "@typescript-eslint/visitor-keys": 8.40.0 + + "@typescript-eslint/tsconfig-utils@8.40.0(typescript@5.9.2)": + dependencies: + typescript: 5.9.2 + + "@typescript-eslint/type-utils@8.40.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.9.2)": + dependencies: + "@typescript-eslint/types": 8.40.0 + "@typescript-eslint/typescript-estree": 8.40.0(typescript@5.9.2) + "@typescript-eslint/utils": 8.40.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.9.2) + debug: 4.4.1 + eslint: 9.33.0(jiti@2.4.2) + ts-api-utils: 2.1.0(typescript@5.9.2) + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/types@8.40.0": {} + + "@typescript-eslint/typescript-estree@8.40.0(typescript@5.9.2)": + dependencies: + "@typescript-eslint/project-service": 8.40.0(typescript@5.9.2) + "@typescript-eslint/tsconfig-utils": 8.40.0(typescript@5.9.2) + "@typescript-eslint/types": 8.40.0 + "@typescript-eslint/visitor-keys": 8.40.0 + debug: 4.4.1 fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 - semver: 7.7.1 - ts-api-utils: 2.1.0(typescript@5.8.3) - typescript: 5.8.3 + semver: 7.7.2 + ts-api-utils: 2.1.0(typescript@5.9.2) + typescript: 5.9.2 transitivePeerDependencies: - supports-color - "@typescript-eslint/utils@8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)": + "@typescript-eslint/utils@8.40.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.9.2)": dependencies: - "@eslint-community/eslint-utils": 4.7.0(eslint@9.26.0(jiti@2.4.2)) - "@typescript-eslint/scope-manager": 8.32.0 - "@typescript-eslint/types": 8.32.0 - "@typescript-eslint/typescript-estree": 8.32.0(typescript@5.8.3) - eslint: 9.26.0(jiti@2.4.2) - typescript: 5.8.3 + "@eslint-community/eslint-utils": 4.7.0(eslint@9.33.0(jiti@2.4.2)) + "@typescript-eslint/scope-manager": 8.40.0 + "@typescript-eslint/types": 8.40.0 + "@typescript-eslint/typescript-estree": 8.40.0(typescript@5.9.2) + eslint: 9.33.0(jiti@2.4.2) + typescript: 5.9.2 transitivePeerDependencies: - supports-color - "@typescript-eslint/visitor-keys@8.32.0": + "@typescript-eslint/visitor-keys@8.40.0": dependencies: - "@typescript-eslint/types": 8.32.0 - eslint-visitor-keys: 4.2.0 + "@typescript-eslint/types": 8.40.0 + eslint-visitor-keys: 4.2.1 - "@typescript/native-preview-darwin-arm64@7.0.0-dev.20250525.1": + "@typescript/native-preview-darwin-arm64@7.0.0-dev.20250819.1": optional: true - "@typescript/native-preview-darwin-x64@7.0.0-dev.20250525.1": + "@typescript/native-preview-darwin-x64@7.0.0-dev.20250819.1": optional: true - "@typescript/native-preview-linux-arm64@7.0.0-dev.20250525.1": + "@typescript/native-preview-linux-arm64@7.0.0-dev.20250819.1": optional: true - "@typescript/native-preview-linux-arm@7.0.0-dev.20250525.1": + "@typescript/native-preview-linux-arm@7.0.0-dev.20250819.1": optional: true - "@typescript/native-preview-linux-x64@7.0.0-dev.20250525.1": + "@typescript/native-preview-linux-x64@7.0.0-dev.20250819.1": optional: true - "@typescript/native-preview-win32-arm64@7.0.0-dev.20250525.1": + "@typescript/native-preview-win32-arm64@7.0.0-dev.20250819.1": optional: true - "@typescript/native-preview-win32-x64@7.0.0-dev.20250525.1": + "@typescript/native-preview-win32-x64@7.0.0-dev.20250819.1": optional: true - "@typescript/native-preview@7.0.0-dev.20250525.1": + "@typescript/native-preview@7.0.0-dev.20250819.1": optionalDependencies: - "@typescript/native-preview-darwin-arm64": 7.0.0-dev.20250525.1 - "@typescript/native-preview-darwin-x64": 7.0.0-dev.20250525.1 - "@typescript/native-preview-linux-arm": 7.0.0-dev.20250525.1 - "@typescript/native-preview-linux-arm64": 7.0.0-dev.20250525.1 - "@typescript/native-preview-linux-x64": 7.0.0-dev.20250525.1 - "@typescript/native-preview-win32-arm64": 7.0.0-dev.20250525.1 - "@typescript/native-preview-win32-x64": 7.0.0-dev.20250525.1 + "@typescript/native-preview-darwin-arm64": 7.0.0-dev.20250819.1 + "@typescript/native-preview-darwin-x64": 7.0.0-dev.20250819.1 + "@typescript/native-preview-linux-arm": 7.0.0-dev.20250819.1 + "@typescript/native-preview-linux-arm64": 7.0.0-dev.20250819.1 + "@typescript/native-preview-linux-x64": 7.0.0-dev.20250819.1 + "@typescript/native-preview-win32-arm64": 7.0.0-dev.20250819.1 + "@typescript/native-preview-win32-x64": 7.0.0-dev.20250819.1 "@ungap/structured-clone@1.3.0": {} - "@unrs/rspack-resolver-binding-darwin-arm64@1.2.2": + "@unrs/resolver-binding-android-arm-eabi@1.11.1": optional: true - "@unrs/rspack-resolver-binding-darwin-x64@1.2.2": + "@unrs/resolver-binding-android-arm64@1.11.1": optional: true - "@unrs/rspack-resolver-binding-freebsd-x64@1.2.2": + "@unrs/resolver-binding-darwin-arm64@1.11.1": optional: true - "@unrs/rspack-resolver-binding-linux-arm-gnueabihf@1.2.2": + "@unrs/resolver-binding-darwin-x64@1.11.1": optional: true - "@unrs/rspack-resolver-binding-linux-arm64-gnu@1.2.2": + "@unrs/resolver-binding-freebsd-x64@1.11.1": optional: true - "@unrs/rspack-resolver-binding-linux-arm64-musl@1.2.2": + "@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1": optional: true - "@unrs/rspack-resolver-binding-linux-x64-gnu@1.2.2": + "@unrs/resolver-binding-linux-arm-musleabihf@1.11.1": optional: true - "@unrs/rspack-resolver-binding-linux-x64-musl@1.2.2": + "@unrs/resolver-binding-linux-arm64-gnu@1.11.1": optional: true - "@unrs/rspack-resolver-binding-wasm32-wasi@1.2.2": + "@unrs/resolver-binding-linux-arm64-musl@1.11.1": + optional: true + + "@unrs/resolver-binding-linux-ppc64-gnu@1.11.1": + optional: true + + "@unrs/resolver-binding-linux-riscv64-gnu@1.11.1": + optional: true + + "@unrs/resolver-binding-linux-riscv64-musl@1.11.1": + optional: true + + "@unrs/resolver-binding-linux-s390x-gnu@1.11.1": + optional: true + + "@unrs/resolver-binding-linux-x64-gnu@1.11.1": + optional: true + + "@unrs/resolver-binding-linux-x64-musl@1.11.1": + optional: true + + "@unrs/resolver-binding-wasm32-wasi@1.11.1": dependencies: - "@napi-rs/wasm-runtime": 0.2.7 + "@napi-rs/wasm-runtime": 0.2.12 optional: true - "@unrs/rspack-resolver-binding-win32-arm64-msvc@1.2.2": + "@unrs/resolver-binding-win32-arm64-msvc@1.11.1": optional: true - "@unrs/rspack-resolver-binding-win32-x64-msvc@1.2.2": + "@unrs/resolver-binding-win32-ia32-msvc@1.11.1": + optional: true + + "@unrs/resolver-binding-win32-x64-msvc@1.11.1": optional: true "@upstash/core-analytics@0.0.10": @@ -14231,6 +13987,48 @@ snapshots: next: 14.2.15(@babel/core@7.26.9)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 + "@vitest/expect@3.2.4": + dependencies: + "@types/chai": 5.2.2 + "@vitest/spy": 3.2.4 + "@vitest/utils": 3.2.4 + chai: 5.2.1 + tinyrainbow: 2.0.0 + + "@vitest/mocker@3.2.4(vite@6.3.5(@types/node@22.8.1)(jiti@2.4.2)(terser@5.43.1)(tsx@4.19.4)(yaml@2.7.0))": + dependencies: + "@vitest/spy": 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 6.3.5(@types/node@22.8.1)(jiti@2.4.2)(terser@5.43.1)(tsx@4.19.4)(yaml@2.7.0) + + "@vitest/pretty-format@3.2.4": + dependencies: + tinyrainbow: 2.0.0 + + "@vitest/runner@3.2.4": + dependencies: + "@vitest/utils": 3.2.4 + pathe: 2.0.3 + strip-literal: 3.0.0 + + "@vitest/snapshot@3.2.4": + dependencies: + "@vitest/pretty-format": 3.2.4 + magic-string: 0.30.17 + pathe: 2.0.3 + + "@vitest/spy@3.2.4": + dependencies: + tinyspy: 4.0.3 + + "@vitest/utils@3.2.4": + dependencies: + "@vitest/pretty-format": 3.2.4 + loupe: 3.2.0 + tinyrainbow: 2.0.0 + "@webassemblyjs/ast@1.14.1": dependencies: "@webassemblyjs/helper-numbers": 1.13.2 @@ -14318,24 +14116,19 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 - accepts@2.0.0: + acorn-import-attributes@1.9.5(acorn@8.15.0): dependencies: - mime-types: 3.0.1 - negotiator: 1.0.0 + acorn: 8.15.0 - acorn-import-attributes@1.9.5(acorn@8.14.1): + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: - acorn: 8.14.1 + acorn: 8.15.0 - acorn-jsx@5.3.2(acorn@8.14.1): - dependencies: - acorn: 8.14.1 - - acorn@8.14.1: {} + acorn@8.15.0: {} agent-base@6.0.2: dependencies: - debug: 4.4.0 + debug: 4.4.1 transitivePeerDependencies: - supports-color @@ -14405,20 +14198,22 @@ snapshots: call-bound: 1.0.4 is-array-buffer: 3.0.5 - array-includes@3.1.8: + array-includes@3.1.9: dependencies: call-bind: 1.0.8 + call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.0 es-object-atoms: 1.1.1 get-intrinsic: 1.3.0 is-string: 1.1.1 + math-intrinsics: 1.1.0 array.prototype.findlast@1.2.5: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.0 es-errors: 1.3.0 es-object-atoms: 1.1.1 es-shim-unscopables: 1.1.0 @@ -14428,7 +14223,7 @@ snapshots: call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.0 es-errors: 1.3.0 es-object-atoms: 1.1.1 es-shim-unscopables: 1.1.0 @@ -14437,21 +14232,21 @@ snapshots: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.0 es-shim-unscopables: 1.1.0 array.prototype.flatmap@1.3.3: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.0 es-shim-unscopables: 1.1.0 array.prototype.tosorted@1.1.4: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.0 es-errors: 1.3.0 es-shim-unscopables: 1.1.0 @@ -14460,11 +14255,13 @@ snapshots: array-buffer-byte-length: 1.0.2 call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.0 es-errors: 1.3.0 get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 + assertion-error@2.0.1: {} + ast-types-flow@0.0.8: {} astring@1.9.0: {} @@ -14475,8 +14272,8 @@ snapshots: autoprefixer@10.4.21(postcss@8.5.3): dependencies: - browserslist: 4.24.4 - caniuse-lite: 1.0.30001707 + browserslist: 4.25.2 + caniuse-lite: 1.0.30001733 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.1 @@ -14495,6 +14292,8 @@ snapshots: balanced-match@1.0.2: {} + base16@1.0.0: {} + base64-js@0.0.8: {} base64-js@1.5.1: {} @@ -14522,20 +14321,6 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - body-parser@2.2.0: - dependencies: - bytes: 3.1.2 - content-type: 1.0.5 - debug: 4.4.0 - http-errors: 2.0.0 - iconv-lite: 0.6.3 - on-finished: 2.4.1 - qs: 6.14.0 - raw-body: 3.0.0 - type-is: 2.0.1 - transitivePeerDependencies: - - supports-color - brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 @@ -14557,12 +14342,12 @@ snapshots: dependencies: pako: 1.0.11 - browserslist@4.24.4: + browserslist@4.25.2: dependencies: - caniuse-lite: 1.0.30001707 - electron-to-chromium: 1.5.109 + caniuse-lite: 1.0.30001733 + electron-to-chromium: 1.5.199 node-releases: 2.0.19 - update-browserslist-db: 1.1.3(browserslist@4.24.4) + update-browserslist-db: 1.1.3(browserslist@4.25.2) buffer-equal-constant-time@1.0.1: {} @@ -14573,17 +14358,10 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 - bundle-require@5.1.0(esbuild@0.25.0): - dependencies: - esbuild: 0.25.0 - load-tsconfig: 0.2.5 - busboy@1.6.0: dependencies: streamsearch: 1.1.0 - bytes@3.1.2: {} - cac@6.7.14: {} call-bind-apply-helpers@1.0.2: @@ -14607,7 +14385,7 @@ snapshots: camelcase-css@2.0.1: {} - caniuse-lite@1.0.30001707: {} + caniuse-lite@1.0.30001733: {} canvas@3.1.0: dependencies: @@ -14617,6 +14395,14 @@ snapshots: ccount@2.0.1: {} + chai@5.2.1: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.2.0 + pathval: 2.0.1 + chalk@3.0.0: dependencies: ansi-styles: 4.3.0 @@ -14637,6 +14423,8 @@ snapshots: character-reference-invalid@2.0.1: {} + check-error@2.1.1: {} + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -14699,10 +14487,16 @@ snapshots: collapse-white-space@2.1.0: {} + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + color-convert@2.0.1: dependencies: color-name: 1.1.4 + color-name@1.1.3: {} + color-name@1.1.4: {} color-string@1.9.1: @@ -14710,6 +14504,11 @@ snapshots: color-name: 1.1.4 simple-swizzle: 0.2.2 + color@3.2.1: + dependencies: + color-convert: 1.9.3 + color-string: 1.9.1 + color@4.2.3: dependencies: color-convert: 2.0.1 @@ -14736,26 +14535,10 @@ snapshots: concat-map@0.0.1: {} - confbox@0.1.8: {} - - consola@3.4.2: {} - - content-disposition@1.0.0: - dependencies: - safe-buffer: 5.2.1 - - content-type@1.0.5: {} - convert-source-map@2.0.0: {} - cookie-signature@1.2.2: {} - cookie@0.7.2: {} - copy-anything@3.0.5: - dependencies: - is-what: 4.1.16 - core-util-is@1.0.3: {} cors@2.8.5: @@ -14807,7 +14590,7 @@ snapshots: dependencies: ms: 2.1.3 - debug@4.4.0: + debug@4.4.1: dependencies: ms: 2.1.3 @@ -14822,6 +14605,8 @@ snapshots: mimic-response: 3.1.0 optional: true + deep-eql@5.0.2: {} + deep-extend@0.6.0: optional: true @@ -14847,8 +14632,6 @@ snapshots: delayed-stream@1.0.0: {} - depd@2.0.0: {} - dequal@2.0.3: {} detect-europe-js@0.1.2: {} @@ -14904,9 +14687,7 @@ snapshots: dependencies: safe-buffer: 5.2.1 - ee-first@1.1.1: {} - - electron-to-chromium@1.5.109: {} + electron-to-chromium@1.5.199: {} emoji-regex@10.4.0: {} @@ -14914,8 +14695,6 @@ snapshots: emoji-regex@9.2.2: {} - encodeurl@2.0.0: {} - end-of-stream@1.4.4: dependencies: once: 1.4.0 @@ -14939,16 +14718,16 @@ snapshots: - supports-color - utf-8-validate - enhanced-resolve@5.18.1: + enhanced-resolve@5.18.3: dependencies: graceful-fs: 4.2.11 - tapable: 2.2.1 + tapable: 2.2.2 entities@4.5.0: {} environment@1.1.0: {} - es-abstract@1.23.9: + es-abstract@1.24.0: dependencies: array-buffer-byte-length: 1.0.2 arraybuffer.prototype.slice: 1.0.4 @@ -14977,7 +14756,9 @@ snapshots: is-array-buffer: 3.0.5 is-callable: 1.2.7 is-data-view: 1.0.2 + is-negative-zero: 2.0.3 is-regex: 1.2.1 + is-set: 2.0.3 is-shared-array-buffer: 1.0.4 is-string: 1.1.1 is-typed-array: 1.1.15 @@ -14992,6 +14773,7 @@ snapshots: safe-push-apply: 1.0.0 safe-regex-test: 1.1.0 set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 string.prototype.trim: 1.2.10 string.prototype.trimend: 1.0.9 string.prototype.trimstart: 1.0.8 @@ -15011,7 +14793,7 @@ snapshots: call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.0 es-errors: 1.3.0 es-set-tostringtag: 2.1.0 function-bind: 1.1.2 @@ -15058,7 +14840,7 @@ snapshots: esast-util-from-js@2.0.1: dependencies: "@types/estree-jsx": 1.0.5 - acorn: 8.14.1 + acorn: 8.15.0 esast-util-from-estree: 2.0.0 vfile-message: 4.0.2 @@ -15092,27 +14874,25 @@ snapshots: escalade@3.2.0: {} - escape-html@1.0.3: {} - escape-string-regexp@4.0.0: {} escape-string-regexp@5.0.0: {} - eslint-config-next@15.2.3(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3): + eslint-config-next@15.2.3(eslint@9.33.0(jiti@2.4.2))(typescript@5.9.2): dependencies: "@next/eslint-plugin-next": 15.2.3 - "@rushstack/eslint-patch": 1.11.0 - "@typescript-eslint/eslint-plugin": 8.32.0(@typescript-eslint/parser@8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) - "@typescript-eslint/parser": 8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) - eslint: 9.26.0(jiti@2.4.2) + "@rushstack/eslint-patch": 1.12.0 + "@typescript-eslint/eslint-plugin": 8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.33.0(jiti@2.4.2))(typescript@5.9.2) + "@typescript-eslint/parser": 8.40.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.9.2) + eslint: 9.33.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.9.1(eslint-plugin-import@2.31.0)(eslint@9.26.0(jiti@2.4.2)) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.9.1)(eslint@9.26.0(jiti@2.4.2)) - eslint-plugin-jsx-a11y: 6.10.2(eslint@9.26.0(jiti@2.4.2)) - eslint-plugin-react: 7.37.4(eslint@9.26.0(jiti@2.4.2)) - eslint-plugin-react-hooks: 5.2.0(eslint@9.26.0(jiti@2.4.2)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.33.0(jiti@2.4.2)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.4.2)) + eslint-plugin-jsx-a11y: 6.10.2(eslint@9.33.0(jiti@2.4.2)) + eslint-plugin-react: 7.37.5(eslint@9.33.0(jiti@2.4.2)) + eslint-plugin-react-hooks: 5.2.0(eslint@9.33.0(jiti@2.4.2)) optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.2 transitivePeerDependencies: - eslint-import-resolver-webpack - eslint-plugin-import-x @@ -15126,44 +14906,44 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.9.1(eslint-plugin-import@2.31.0)(eslint@9.26.0(jiti@2.4.2)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.33.0(jiti@2.4.2)): dependencies: "@nolyfill/is-core-module": 1.0.39 - debug: 4.4.0 - eslint: 9.26.0(jiti@2.4.2) - get-tsconfig: 4.10.0 - is-bun-module: 1.3.0 - rspack-resolver: 1.2.2 + debug: 4.4.1 + eslint: 9.33.0(jiti@2.4.2) + get-tsconfig: 4.10.1 + is-bun-module: 2.0.0 stable-hash: 0.0.5 - tinyglobby: 0.2.13 + tinyglobby: 0.2.14 + unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.9.1)(eslint@9.26.0(jiti@2.4.2)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.4.2)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.9.1)(eslint@9.26.0(jiti@2.4.2)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.4.2)): dependencies: debug: 3.2.7 optionalDependencies: - "@typescript-eslint/parser": 8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) - eslint: 9.26.0(jiti@2.4.2) + "@typescript-eslint/parser": 8.40.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.9.2) + eslint: 9.33.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.9.1(eslint-plugin-import@2.31.0)(eslint@9.26.0(jiti@2.4.2)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.33.0(jiti@2.4.2)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.9.1)(eslint@9.26.0(jiti@2.4.2)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.4.2)): dependencies: "@rtsao/scc": 1.1.0 - array-includes: 3.1.8 + array-includes: 3.1.9 array.prototype.findlastindex: 1.2.6 array.prototype.flat: 1.3.3 array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 - eslint: 9.26.0(jiti@2.4.2) + eslint: 9.33.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.9.1)(eslint@9.26.0(jiti@2.4.2)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.4.2)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -15175,23 +14955,23 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - "@typescript-eslint/parser": 8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) + "@typescript-eslint/parser": 8.40.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.9.2) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-jsx-a11y@6.10.2(eslint@9.26.0(jiti@2.4.2)): + eslint-plugin-jsx-a11y@6.10.2(eslint@9.33.0(jiti@2.4.2)): dependencies: aria-query: 5.3.2 - array-includes: 3.1.8 + array-includes: 3.1.9 array.prototype.flatmap: 1.3.3 ast-types-flow: 0.0.8 axe-core: 4.10.3 axobject-query: 4.1.0 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 - eslint: 9.26.0(jiti@2.4.2) + eslint: 9.33.0(jiti@2.4.2) hasown: 2.0.2 jsx-ast-utils: 3.3.5 language-tags: 1.0.9 @@ -15200,29 +14980,29 @@ snapshots: safe-regex-test: 1.1.0 string.prototype.includes: 2.0.1 - eslint-plugin-playwright@2.2.0(eslint@9.26.0(jiti@2.4.2)): + eslint-plugin-playwright@2.2.0(eslint@9.33.0(jiti@2.4.2)): dependencies: - eslint: 9.26.0(jiti@2.4.2) + eslint: 9.33.0(jiti@2.4.2) globals: 13.24.0 - eslint-plugin-react-hooks@5.2.0(eslint@9.26.0(jiti@2.4.2)): + eslint-plugin-react-hooks@5.2.0(eslint@9.33.0(jiti@2.4.2)): dependencies: - eslint: 9.26.0(jiti@2.4.2) + eslint: 9.33.0(jiti@2.4.2) - eslint-plugin-react-you-might-not-need-an-effect@0.0.39(eslint@9.26.0(jiti@2.4.2)): + eslint-plugin-react-you-might-not-need-an-effect@0.0.39(eslint@9.33.0(jiti@2.4.2)): dependencies: - eslint: 9.26.0(jiti@2.4.2) - eslint-utils: 3.0.0(eslint@9.26.0(jiti@2.4.2)) + eslint: 9.33.0(jiti@2.4.2) + eslint-utils: 3.0.0(eslint@9.33.0(jiti@2.4.2)) - eslint-plugin-react@7.37.4(eslint@9.26.0(jiti@2.4.2)): + eslint-plugin-react@7.37.5(eslint@9.33.0(jiti@2.4.2)): dependencies: - array-includes: 3.1.8 + array-includes: 3.1.9 array.prototype.findlast: 1.2.5 array.prototype.flatmap: 1.3.3 array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 es-iterator-helpers: 1.2.1 - eslint: 9.26.0(jiti@2.4.2) + eslint: 9.33.0(jiti@2.4.2) estraverse: 5.3.0 hasown: 2.0.2 jsx-ast-utils: 3.3.5 @@ -15241,46 +15021,45 @@ snapshots: esrecurse: 4.3.0 estraverse: 4.3.0 - eslint-scope@8.3.0: + eslint-scope@8.4.0: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 - eslint-utils@3.0.0(eslint@9.26.0(jiti@2.4.2)): + eslint-utils@3.0.0(eslint@9.33.0(jiti@2.4.2)): dependencies: - eslint: 9.26.0(jiti@2.4.2) + eslint: 9.33.0(jiti@2.4.2) eslint-visitor-keys: 2.1.0 eslint-visitor-keys@2.1.0: {} eslint-visitor-keys@3.4.3: {} - eslint-visitor-keys@4.2.0: {} + eslint-visitor-keys@4.2.1: {} - eslint@9.26.0(jiti@2.4.2): + eslint@9.33.0(jiti@2.4.2): dependencies: - "@eslint-community/eslint-utils": 4.7.0(eslint@9.26.0(jiti@2.4.2)) + "@eslint-community/eslint-utils": 4.7.0(eslint@9.33.0(jiti@2.4.2)) "@eslint-community/regexpp": 4.12.1 - "@eslint/config-array": 0.20.0 - "@eslint/config-helpers": 0.2.2 - "@eslint/core": 0.13.0 + "@eslint/config-array": 0.21.0 + "@eslint/config-helpers": 0.3.1 + "@eslint/core": 0.15.2 "@eslint/eslintrc": 3.3.1 - "@eslint/js": 9.26.0 - "@eslint/plugin-kit": 0.2.8 + "@eslint/js": 9.33.0 + "@eslint/plugin-kit": 0.3.5 "@humanfs/node": 0.16.6 "@humanwhocodes/module-importer": 1.0.1 "@humanwhocodes/retry": 0.4.3 - "@modelcontextprotocol/sdk": 1.11.1 - "@types/estree": 1.0.7 + "@types/estree": 1.0.8 "@types/json-schema": 7.0.15 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.0 + debug: 4.4.1 escape-string-regexp: 4.0.0 - eslint-scope: 8.3.0 - eslint-visitor-keys: 4.2.0 - espree: 10.3.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 esquery: 1.6.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 @@ -15295,17 +15074,16 @@ snapshots: minimatch: 3.1.2 natural-compare: 1.4.0 optionator: 0.9.4 - zod: 3.24.4 optionalDependencies: jiti: 2.4.2 transitivePeerDependencies: - supports-color - espree@10.3.0: + espree@10.4.0: dependencies: - acorn: 8.14.1 - acorn-jsx: 5.3.2(acorn@8.14.1) - eslint-visitor-keys: 4.2.0 + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 esquery@1.6.0: dependencies: @@ -15321,7 +15099,7 @@ snapshots: estree-util-attach-comments@3.0.0: dependencies: - "@types/estree": 1.0.7 + "@types/estree": 1.0.8 estree-util-build-jsx@3.0.1: dependencies: @@ -15334,7 +15112,7 @@ snapshots: estree-util-scope@1.0.0: dependencies: - "@types/estree": 1.0.7 + "@types/estree": 1.0.8 devlop: 1.1.0 estree-util-to-js@2.0.0: @@ -15352,22 +15130,14 @@ snapshots: estree-walker@3.0.3: dependencies: - "@types/estree": 1.0.7 + "@types/estree": 1.0.8 esutils@2.0.3: {} - etag@1.8.1: {} - eventemitter3@5.0.1: {} events@3.3.0: {} - eventsource-parser@3.0.1: {} - - eventsource@3.0.7: - dependencies: - eventsource-parser: 3.0.1 - execa@8.0.1: dependencies: cross-spawn: 7.0.6 @@ -15383,41 +15153,7 @@ snapshots: expand-template@2.0.3: optional: true - express-rate-limit@7.5.0(express@5.1.0): - dependencies: - express: 5.1.0 - - express@5.1.0: - dependencies: - accepts: 2.0.0 - body-parser: 2.2.0 - content-disposition: 1.0.0 - content-type: 1.0.5 - cookie: 0.7.2 - cookie-signature: 1.2.2 - debug: 4.4.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 2.1.0 - fresh: 2.0.0 - http-errors: 2.0.0 - merge-descriptors: 2.0.0 - mime-types: 3.0.1 - on-finished: 2.4.1 - once: 1.4.0 - parseurl: 1.3.3 - proxy-addr: 2.0.7 - qs: 6.14.0 - range-parser: 1.2.1 - router: 2.2.0 - send: 1.2.0 - serve-static: 2.2.0 - statuses: 2.0.1 - type-is: 2.0.1 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color + expect-type@1.2.2: {} extend@3.0.2: {} @@ -15451,13 +15187,13 @@ snapshots: dependencies: reusify: 1.0.4 - fd-package-json@1.2.0: + fd-package-json@2.0.0: dependencies: - walk-up-path: 3.0.1 + walk-up-path: 4.0.0 - fdir@6.4.4(picomatch@4.0.2): + fdir@6.4.4(picomatch@4.0.3): optionalDependencies: - picomatch: 4.0.2 + picomatch: 4.0.3 file-entry-cache@8.0.0: dependencies: @@ -15469,28 +15205,11 @@ snapshots: dependencies: to-regex-range: 5.0.1 - finalhandler@2.1.0: - dependencies: - debug: 4.4.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - on-finished: 2.4.1 - parseurl: 1.3.3 - statuses: 2.0.1 - transitivePeerDependencies: - - supports-color - find-up@5.0.0: dependencies: locate-path: 6.0.0 path-exists: 4.0.0 - fix-dts-default-cjs-exports@1.0.1: - dependencies: - magic-string: 0.30.17 - mlly: 1.7.4 - rollup: 4.41.0 - flat-cache@4.0.1: dependencies: flatted: 3.3.3 @@ -15526,18 +15245,14 @@ snapshots: es-set-tostringtag: 2.1.0 mime-types: 2.1.35 - formatly@0.2.3: + formatly@0.2.4: dependencies: - fd-package-json: 1.2.0 + fd-package-json: 2.0.0 forwarded-parse@2.1.2: {} - forwarded@0.2.0: {} - fraction.js@4.3.7: {} - fresh@2.0.0: {} - fs-constants@1.0.0: optional: true @@ -15614,7 +15329,7 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 - get-tsconfig@4.10.0: + get-tsconfig@4.10.1: dependencies: resolve-pkg-maps: 1.0.0 @@ -15740,7 +15455,7 @@ snapshots: hast-util-to-estree@3.1.3: dependencies: - "@types/estree": 1.0.7 + "@types/estree": 1.0.8 "@types/estree-jsx": 1.0.5 "@types/hast": 3.0.4 comma-separated-tokens: 2.0.3 @@ -15761,7 +15476,7 @@ snapshots: hast-util-to-jsx-runtime@2.3.6: dependencies: - "@types/estree": 1.0.7 + "@types/estree": 1.0.8 "@types/hast": 3.0.4 "@types/unist": 3.0.3 comma-separated-tokens: 2.0.3 @@ -15808,25 +15523,17 @@ snapshots: domutils: 3.2.2 entities: 4.5.0 - http-errors@2.0.0: - dependencies: - depd: 2.0.0 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 2.0.1 - toidentifier: 1.0.1 - https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.4.0 + debug: 4.4.1 transitivePeerDependencies: - supports-color https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.3 - debug: 4.4.0 + debug: 4.4.1 transitivePeerDependencies: - supports-color @@ -15836,14 +15543,12 @@ snapshots: hyphen@1.10.6: {} - iconv-lite@0.6.3: - dependencies: - safer-buffer: 2.1.2 - ieee754@1.2.1: {} ignore@5.3.2: {} + ignore@7.0.5: {} + immediate@3.0.6: {} import-fresh@3.3.0: @@ -15853,8 +15558,8 @@ snapshots: import-in-the-middle@1.13.1: dependencies: - acorn: 8.14.1 - acorn-import-attributes: 1.9.5(acorn@8.14.1) + acorn: 8.15.0 + acorn-import-attributes: 1.9.5(acorn@8.15.0) cjs-module-lexer: 1.4.3 module-details-from-path: 1.0.3 @@ -15880,8 +15585,6 @@ snapshots: "@formatjs/icu-messageformat-parser": 2.11.2 tslib: 2.8.1 - ipaddr.js@1.9.1: {} - is-alphabetical@2.0.1: {} is-alphanumerical@2.0.1: @@ -15918,9 +15621,9 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 - is-bun-module@1.3.0: + is-bun-module@2.0.0: dependencies: - semver: 7.7.1 + semver: 7.7.2 is-callable@1.2.7: {} @@ -15972,6 +15675,8 @@ snapshots: is-map@2.0.3: {} + is-negative-zero@2.0.3: {} + is-number-object@1.1.1: dependencies: call-bound: 1.0.4 @@ -15981,11 +15686,9 @@ snapshots: is-plain-obj@4.1.0: {} - is-promise@4.0.0: {} - is-reference@1.2.1: dependencies: - "@types/estree": 1.0.7 + "@types/estree": 1.0.8 is-regex@1.2.1: dependencies: @@ -16036,8 +15739,6 @@ snapshots: call-bound: 1.0.4 get-intrinsic: 1.3.0 - is-what@4.1.16: {} - isarray@1.0.0: {} isarray@2.0.5: {} @@ -16065,10 +15766,6 @@ snapshots: optionalDependencies: "@pkgjs/parseargs": 0.11.0 - javascript-time-ago@2.5.11: - dependencies: - relative-time-format: 1.1.6 - jay-peg@1.1.1: dependencies: restructure: 3.0.2 @@ -16083,10 +15780,10 @@ snapshots: jiti@2.4.2: {} - joycon@3.1.1: {} - js-tokens@4.0.0: {} + js-tokens@9.0.1: {} + js-yaml@4.1.0: dependencies: argparse: 2.0.1 @@ -16115,7 +15812,7 @@ snapshots: jsx-ast-utils@3.3.5: dependencies: - array-includes: 3.1.8 + array-includes: 3.1.9 array.prototype.flat: 1.3.3 object.assign: 4.1.7 object.values: 1.2.1 @@ -16144,23 +15841,23 @@ snapshots: kleur@4.1.5: {} - knip@5.55.1(@types/node@22.8.1)(typescript@5.8.3): + knip@5.55.1(@types/node@22.8.1)(typescript@5.9.2): dependencies: "@nodelib/fs.walk": 1.2.8 "@types/node": 22.8.1 - enhanced-resolve: 5.18.1 + enhanced-resolve: 5.18.3 fast-glob: 3.3.3 - formatly: 0.2.3 + formatly: 0.2.4 jiti: 2.4.2 js-yaml: 4.1.0 minimist: 1.2.8 picocolors: 1.1.1 - picomatch: 4.0.2 - smol-toml: 1.3.4 + picomatch: 4.0.3 + smol-toml: 1.4.2 strip-json-comments: 5.0.1 - typescript: 5.8.3 + typescript: 5.9.2 zod: 3.24.4 - zod-validation-error: 3.4.1(zod@3.24.4) + zod-validation-error: 3.5.3(zod@3.24.4) language-subtag-registry@0.3.23: {} @@ -16194,7 +15891,7 @@ snapshots: dependencies: chalk: 5.4.1 commander: 13.1.0 - debug: 4.4.0 + debug: 4.4.1 execa: 8.0.1 lilconfig: 3.1.3 listr2: 8.2.5 @@ -16214,8 +15911,6 @@ snapshots: rfdc: 1.4.1 wrap-ansi: 9.0.0 - load-tsconfig@0.2.5: {} - loader-runner@4.3.0: {} locate-path@6.0.0: @@ -16224,12 +15919,12 @@ snapshots: lodash.castarray@4.4.0: {} + lodash.curry@4.1.1: {} + lodash.isplainobject@4.0.6: {} lodash.merge@4.6.2: {} - lodash.sortby@4.7.0: {} - log-symbols@4.1.0: dependencies: chalk: 4.1.2 @@ -16249,6 +15944,8 @@ snapshots: dependencies: js-tokens: 4.0.0 + loupe@3.2.0: {} + lru-cache@10.4.3: {} lru-cache@5.1.1: @@ -16259,19 +15956,15 @@ snapshots: dependencies: react: 18.3.1 - lucide-react@0.503.0(react@18.3.1): - dependencies: - react: 18.3.1 - lz-string@1.5.0: {} magic-string@0.30.17: dependencies: - "@jridgewell/sourcemap-codec": 1.5.0 + "@jridgewell/sourcemap-codec": 1.5.4 magic-string@0.30.8: dependencies: - "@jridgewell/sourcemap-codec": 1.5.0 + "@jridgewell/sourcemap-codec": 1.5.4 make-cancellable-promise@1.3.2: {} @@ -16455,10 +16148,6 @@ snapshots: media-engine@1.0.3: {} - media-typer@1.1.0: {} - - merge-descriptors@2.0.0: {} - merge-refs@1.3.0(@types/react@18.3.12): optionalDependencies: "@types/react": 18.3.12 @@ -16546,7 +16235,7 @@ snapshots: micromark-extension-mdx-expression@3.0.1: dependencies: - "@types/estree": 1.0.7 + "@types/estree": 1.0.8 devlop: 1.1.0 micromark-factory-mdx-expression: 2.0.3 micromark-factory-space: 2.0.1 @@ -16557,7 +16246,7 @@ snapshots: micromark-extension-mdx-jsx@3.0.2: dependencies: - "@types/estree": 1.0.7 + "@types/estree": 1.0.8 devlop: 1.1.0 estree-util-is-identifier-name: 3.0.0 micromark-factory-mdx-expression: 2.0.3 @@ -16574,7 +16263,7 @@ snapshots: micromark-extension-mdxjs-esm@3.0.0: dependencies: - "@types/estree": 1.0.7 + "@types/estree": 1.0.8 devlop: 1.1.0 micromark-core-commonmark: 2.0.3 micromark-util-character: 2.1.1 @@ -16586,8 +16275,8 @@ snapshots: micromark-extension-mdxjs@3.0.0: dependencies: - acorn: 8.14.1 - acorn-jsx: 5.3.2(acorn@8.14.1) + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) micromark-extension-mdx-expression: 3.0.1 micromark-extension-mdx-jsx: 3.0.2 micromark-extension-mdx-md: 2.0.0 @@ -16610,7 +16299,7 @@ snapshots: micromark-factory-mdx-expression@2.0.3: dependencies: - "@types/estree": 1.0.7 + "@types/estree": 1.0.8 devlop: 1.1.0 micromark-factory-space: 2.0.1 micromark-util-character: 2.1.1 @@ -16674,7 +16363,7 @@ snapshots: micromark-util-events-to-acorn@2.0.3: dependencies: - "@types/estree": 1.0.7 + "@types/estree": 1.0.8 "@types/unist": 3.0.3 devlop: 1.1.0 estree-util-visit: 2.0.0 @@ -16712,7 +16401,7 @@ snapshots: micromark@4.0.2: dependencies: "@types/debug": 4.1.12 - debug: 4.4.0 + debug: 4.4.1 decode-named-character-reference: 1.2.0 devlop: 1.1.0 micromark-core-commonmark: 2.0.3 @@ -16738,16 +16427,10 @@ snapshots: mime-db@1.52.0: {} - mime-db@1.54.0: {} - mime-types@2.1.35: dependencies: mime-db: 1.52.0 - mime-types@3.0.1: - dependencies: - mime-db: 1.54.0 - mimic-fn@2.1.0: {} mimic-fn@4.0.0: {} @@ -16778,13 +16461,6 @@ snapshots: mkdirp-classic@0.5.3: optional: true - mlly@1.7.4: - dependencies: - acorn: 8.14.1 - pathe: 2.0.3 - pkg-types: 1.3.1 - ufo: 1.6.1 - module-details-from-path@1.0.3: {} mri@1.2.0: {} @@ -16804,6 +16480,8 @@ snapshots: napi-build-utils@2.0.0: optional: true + napi-postinstall@0.3.3: {} + natural-compare@1.4.0: {} negotiator@0.6.3: {} @@ -16812,7 +16490,7 @@ snapshots: neo-async@2.6.2: {} - next-intl@4.0.2(next@14.2.15(@babel/core@7.26.9)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(typescript@5.8.3): + next-intl@4.0.2(next@14.2.15(@babel/core@7.26.9)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(typescript@5.9.2): dependencies: "@formatjs/intl-localematcher": 0.5.10 negotiator: 1.0.0 @@ -16820,14 +16498,14 @@ snapshots: react: 18.3.1 use-intl: 4.0.2(react@18.3.1) optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.2 next@14.2.15(@babel/core@7.26.9)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: "@next/env": 14.2.15 "@swc/helpers": 0.5.5 busboy: 1.6.0 - caniuse-lite: 1.0.30001707 + caniuse-lite: 1.0.30001733 graceful-fs: 4.2.11 postcss: 8.4.31 react: 18.3.1 @@ -16855,7 +16533,7 @@ snapshots: "@swc/counter": 0.1.3 "@swc/helpers": 0.5.15 busboy: 1.6.0 - caniuse-lite: 1.0.30001707 + caniuse-lite: 1.0.30001733 postcss: 8.4.31 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -16878,7 +16556,7 @@ snapshots: node-abi@3.75.0: dependencies: - semver: 7.7.1 + semver: 7.7.2 optional: true node-addon-api@7.1.1: @@ -16932,14 +16610,14 @@ snapshots: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.0 es-object-atoms: 1.1.1 object.groupby@1.0.3: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.0 object.values@1.2.1: dependencies: @@ -16948,13 +16626,10 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 - on-finished@2.4.1: - dependencies: - ee-first: 1.1.1 - once@1.4.0: dependencies: wrappy: 1.0.2 + optional: true onetime@5.1.2: dependencies: @@ -17030,8 +16705,6 @@ snapshots: leac: 0.6.0 peberminta: 0.9.0 - parseurl@1.3.3: {} - path-exists@4.0.0: {} path-key@3.1.1: {} @@ -17045,13 +16718,13 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 - path-to-regexp@8.2.0: {} - path2d@0.2.2: optional: true pathe@2.0.3: {} + pathval@2.0.1: {} + pdf-parse@1.1.1: dependencies: debug: 3.2.7 @@ -17082,7 +16755,7 @@ snapshots: picomatch@2.3.1: {} - picomatch@4.0.2: {} + picomatch@4.0.3: {} pidtree@0.6.0: {} @@ -17090,14 +16763,6 @@ snapshots: pirates@4.0.6: {} - pkce-challenge@5.0.0: {} - - pkg-types@1.3.1: - dependencies: - confbox: 0.1.8 - mlly: 1.7.4 - pathe: 2.0.3 - playwright-core@1.52.0: {} playwright@1.52.0: @@ -17127,24 +16792,11 @@ snapshots: optionalDependencies: postcss: 8.5.3 - postcss-load-config@6.0.1(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.4)(yaml@2.7.0): - dependencies: - lilconfig: 3.1.3 - optionalDependencies: - jiti: 2.4.2 - postcss: 8.5.3 - tsx: 4.19.4 - yaml: 2.7.0 - postcss-nested@6.2.0(postcss@8.5.3): dependencies: postcss: 8.5.3 postcss-selector-parser: 6.1.2 - postcss-prefix-selector@2.1.1(postcss@8.5.3): - dependencies: - postcss: 8.5.3 - postcss-selector-parser@6.0.10: dependencies: cssesc: 3.0.0 @@ -17221,11 +16873,6 @@ snapshots: property-information@7.1.0: {} - proxy-addr@2.0.7: - dependencies: - forwarded: 0.2.0 - ipaddr.js: 1.9.1 - proxy-from-env@1.1.0: {} pump@3.0.2: @@ -17250,15 +16897,6 @@ snapshots: dependencies: safe-buffer: 5.2.1 - range-parser@1.2.1: {} - - raw-body@3.0.0: - dependencies: - bytes: 3.1.2 - http-errors: 2.0.0 - iconv-lite: 0.6.3 - unpipe: 1.0.0 - rc@1.2.8: dependencies: deep-extend: 0.6.0 @@ -17347,6 +16985,16 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + react-base16-styling@0.9.1: + dependencies: + "@babel/runtime": 7.26.9 + "@types/base16": 1.0.5 + "@types/lodash": 4.17.20 + base16: 1.0.0 + color: 3.2.1 + csstype: 3.1.3 + lodash.curry: 4.1.1 + react-dom@18.3.1(react@18.3.1): dependencies: loose-envify: 1.4.0 @@ -17394,6 +17042,8 @@ snapshots: react-is@16.13.1: {} + react-lifecycles-compat@3.0.4: {} + react-pdf@9.2.1(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: clsx: 2.1.1 @@ -17510,6 +17160,15 @@ snapshots: optionalDependencies: "@types/react": 18.3.12 + react-textarea-autosize@8.5.9(@types/react@18.3.12)(react@18.3.1): + dependencies: + "@babel/runtime": 7.26.9 + react: 18.3.1 + use-composed-ref: 1.4.0(@types/react@18.3.12)(react@18.3.1) + use-latest: 1.3.0(@types/react@18.3.12)(react@18.3.1) + transitivePeerDependencies: + - "@types/react" + react@18.3.1: dependencies: loose-envify: 1.4.0 @@ -17542,13 +17201,13 @@ snapshots: recma-build-jsx@1.0.0: dependencies: - "@types/estree": 1.0.7 + "@types/estree": 1.0.8 estree-util-build-jsx: 3.0.1 vfile: 6.0.3 - recma-jsx@1.0.0(acorn@8.14.1): + recma-jsx@1.0.0(acorn@8.15.0): dependencies: - acorn-jsx: 5.3.2(acorn@8.14.1) + acorn-jsx: 5.3.2(acorn@8.15.0) estree-util-to-js: 2.0.0 recma-parse: 1.0.0 recma-stringify: 1.0.0 @@ -17558,14 +17217,14 @@ snapshots: recma-parse@1.0.0: dependencies: - "@types/estree": 1.0.7 + "@types/estree": 1.0.8 esast-util-from-js: 2.0.1 unified: 11.0.5 vfile: 6.0.3 recma-stringify@1.0.0: dependencies: - "@types/estree": 1.0.7 + "@types/estree": 1.0.8 estree-util-to-js: 2.0.0 unified: 11.0.5 vfile: 6.0.3 @@ -17574,7 +17233,7 @@ snapshots: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.0 es-errors: 1.3.0 es-object-atoms: 1.1.1 get-intrinsic: 1.3.0 @@ -17594,14 +17253,12 @@ snapshots: rehype-recma@1.0.0: dependencies: - "@types/estree": 1.0.7 + "@types/estree": 1.0.8 "@types/hast": 3.0.4 hast-util-to-estree: 3.1.3 transitivePeerDependencies: - supports-color - relative-time-format@1.1.6: {} - remark-gfm@4.0.1: dependencies: "@types/mdast": 4.0.4 @@ -17647,7 +17304,7 @@ snapshots: require-in-the-middle@7.5.2: dependencies: - debug: 4.4.0 + debug: 4.4.1 module-details-from-path: 1.0.3 resolve: 1.22.10 transitivePeerDependencies: @@ -17662,8 +17319,6 @@ snapshots: resolve-from@4.0.0: {} - resolve-from@5.0.0: {} - resolve-pkg-maps@1.0.0: {} resolve@1.22.10: @@ -17730,30 +17385,6 @@ snapshots: "@rollup/rollup-win32-x64-msvc": 4.41.0 fsevents: 2.3.3 - router@2.2.0: - dependencies: - debug: 4.4.0 - depd: 2.0.0 - is-promise: 4.0.0 - parseurl: 1.3.3 - path-to-regexp: 8.2.0 - transitivePeerDependencies: - - supports-color - - rspack-resolver@1.2.2: - optionalDependencies: - "@unrs/rspack-resolver-binding-darwin-arm64": 1.2.2 - "@unrs/rspack-resolver-binding-darwin-x64": 1.2.2 - "@unrs/rspack-resolver-binding-freebsd-x64": 1.2.2 - "@unrs/rspack-resolver-binding-linux-arm-gnueabihf": 1.2.2 - "@unrs/rspack-resolver-binding-linux-arm64-gnu": 1.2.2 - "@unrs/rspack-resolver-binding-linux-arm64-musl": 1.2.2 - "@unrs/rspack-resolver-binding-linux-x64-gnu": 1.2.2 - "@unrs/rspack-resolver-binding-linux-x64-musl": 1.2.2 - "@unrs/rspack-resolver-binding-wasm32-wasi": 1.2.2 - "@unrs/rspack-resolver-binding-win32-arm64-msvc": 1.2.2 - "@unrs/rspack-resolver-binding-win32-x64-msvc": 1.2.2 - run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -17781,8 +17412,6 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 - safer-buffer@2.1.2: {} - scheduler@0.23.2: dependencies: loose-envify: 1.4.0 @@ -17804,37 +17433,12 @@ snapshots: semver@6.3.1: {} - semver@7.7.1: {} - - send@1.2.0: - dependencies: - debug: 4.4.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - fresh: 2.0.0 - http-errors: 2.0.0 - mime-types: 3.0.1 - ms: 2.1.3 - on-finished: 2.4.1 - range-parser: 1.2.1 - statuses: 2.0.1 - transitivePeerDependencies: - - supports-color + semver@7.7.2: {} serialize-javascript@6.0.2: dependencies: randombytes: 2.1.0 - serve-static@2.2.0: - dependencies: - encodeurl: 2.0.0 - escape-html: 1.0.3 - parseurl: 1.3.3 - send: 1.2.0 - transitivePeerDependencies: - - supports-color - set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -17859,13 +17463,11 @@ snapshots: setimmediate@1.0.5: {} - setprototypeof@1.2.0: {} - sharp@0.33.5: dependencies: color: 4.2.3 detect-libc: 2.0.3 - semver: 7.7.1 + semver: 7.7.2 optionalDependencies: "@img/sharp-darwin-arm64": 0.33.5 "@img/sharp-darwin-x64": 0.33.5 @@ -17924,6 +17526,8 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} + signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -17954,7 +17558,7 @@ snapshots: ansi-styles: 6.2.1 is-fullwidth-code-point: 5.0.0 - smol-toml@1.3.4: {} + smol-toml@1.4.2: {} socket.io-adapter@2.5.5: dependencies: @@ -18002,19 +17606,22 @@ snapshots: source-map@0.7.4: {} - source-map@0.8.0-beta.0: - dependencies: - whatwg-url: 7.1.0 - space-separated-tokens@2.0.2: {} stable-hash@0.0.5: {} + stackback@0.0.2: {} + stacktrace-parser@0.1.11: dependencies: type-fest: 0.7.1 - statuses@2.0.1: {} + std-env@3.9.0: {} + + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 streamsearch@1.1.0: {} @@ -18042,14 +17649,14 @@ snapshots: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.0 string.prototype.matchall@4.0.12: dependencies: call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.0 es-errors: 1.3.0 es-object-atoms: 1.1.1 get-intrinsic: 1.3.0 @@ -18063,7 +17670,7 @@ snapshots: string.prototype.repeat@1.0.0: dependencies: define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.0 string.prototype.trim@1.2.10: dependencies: @@ -18071,7 +17678,7 @@ snapshots: call-bound: 1.0.4 define-data-property: 1.1.4 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.0 es-object-atoms: 1.1.1 has-property-descriptors: 1.0.2 @@ -18120,6 +17727,10 @@ snapshots: strip-json-comments@5.0.1: {} + strip-literal@3.0.0: + dependencies: + js-tokens: 9.0.1 + style-to-js@1.1.17: dependencies: style-to-object: 1.0.9 @@ -18144,7 +17755,7 @@ snapshots: sucrase@3.35.0: dependencies: - "@jridgewell/gen-mapping": 0.3.8 + "@jridgewell/gen-mapping": 0.3.12 commander: 4.1.1 glob: 10.4.5 lines-and-columns: 1.2.4 @@ -18152,10 +17763,6 @@ snapshots: pirates: 4.0.6 ts-interface-checker: 0.1.13 - superjson@2.2.2: - dependencies: - copy-anything: 3.0.5 - supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -18168,12 +17775,8 @@ snapshots: svg-arc-to-cubic-bezier@3.2.0: {} - tabbable@6.2.0: {} - tailwind-merge@2.6.0: {} - tailwind-merge@3.3.0: {} - tailwindcss-animate@1.0.7(tailwindcss@3.4.14): dependencies: tailwindcss: 3.4.14 @@ -18205,9 +17808,7 @@ snapshots: transitivePeerDependencies: - ts-node - tailwindcss@4.1.7: {} - - tapable@2.2.1: {} + tapable@2.2.2: {} tar-fs@2.1.2: dependencies: @@ -18226,21 +17827,19 @@ snapshots: readable-stream: 3.6.2 optional: true - terser-webpack-plugin@5.3.14(esbuild@0.25.0)(webpack@5.98.0(esbuild@0.25.0)): + terser-webpack-plugin@5.3.14(webpack@5.98.0): dependencies: - "@jridgewell/trace-mapping": 0.3.25 + "@jridgewell/trace-mapping": 0.3.29 jest-worker: 27.5.1 schema-utils: 4.3.2 serialize-javascript: 6.0.2 - terser: 5.39.0 - webpack: 5.98.0(esbuild@0.25.0) - optionalDependencies: - esbuild: 0.25.0 + terser: 5.43.1 + webpack: 5.98.0 - terser@5.39.0: + terser@5.43.1: dependencies: - "@jridgewell/source-map": 0.3.6 - acorn: 8.14.1 + "@jridgewell/source-map": 0.3.10 + acorn: 8.15.0 commander: 2.20.3 source-map-support: 0.5.21 @@ -18256,34 +17855,34 @@ snapshots: tiny-invariant@1.3.3: {} + tinybench@2.9.0: {} + tinyexec@0.3.2: {} - tinyglobby@0.2.13: + tinyglobby@0.2.14: dependencies: - fdir: 6.4.4(picomatch@4.0.2) - picomatch: 4.0.2 + fdir: 6.4.4(picomatch@4.0.3) + picomatch: 4.0.3 + + tinypool@1.1.1: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.3: {} to-regex-range@5.0.1: dependencies: is-number: 7.0.0 - toidentifier@1.0.1: {} - tr46@0.0.3: {} - tr46@1.0.1: - dependencies: - punycode: 2.3.1 - - tree-kill@1.2.2: {} - trim-lines@3.0.1: {} trough@2.2.0: {} - ts-api-utils@2.1.0(typescript@5.8.3): + ts-api-utils@2.1.0(typescript@5.9.2): dependencies: - typescript: 5.8.3 + typescript: 5.9.2 ts-interface-checker@0.1.13: {} @@ -18296,38 +17895,10 @@ snapshots: tslib@2.8.1: {} - tsup@8.5.0(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.4)(typescript@5.8.3)(yaml@2.7.0): - dependencies: - bundle-require: 5.1.0(esbuild@0.25.0) - cac: 6.7.14 - chokidar: 4.0.3 - consola: 3.4.2 - debug: 4.4.0 - esbuild: 0.25.0 - fix-dts-default-cjs-exports: 1.0.1 - joycon: 3.1.1 - picocolors: 1.1.1 - postcss-load-config: 6.0.1(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.4)(yaml@2.7.0) - resolve-from: 5.0.0 - rollup: 4.41.0 - source-map: 0.8.0-beta.0 - sucrase: 3.35.0 - tinyexec: 0.3.2 - tinyglobby: 0.2.13 - tree-kill: 1.2.2 - optionalDependencies: - postcss: 8.5.3 - typescript: 5.8.3 - transitivePeerDependencies: - - jiti - - supports-color - - tsx - - yaml - tsx@4.19.4: dependencies: esbuild: 0.25.0 - get-tsconfig: 4.10.0 + get-tsconfig: 4.10.1 optionalDependencies: fsevents: 2.3.3 @@ -18344,12 +17915,6 @@ snapshots: type-fest@0.7.1: {} - type-is@2.0.1: - dependencies: - content-type: 1.0.5 - media-typer: 1.1.0 - mime-types: 3.0.1 - typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 @@ -18383,17 +17948,18 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript-eslint@8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3): + typescript-eslint@8.40.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.9.2): dependencies: - "@typescript-eslint/eslint-plugin": 8.32.0(@typescript-eslint/parser@8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) - "@typescript-eslint/parser": 8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) - "@typescript-eslint/utils": 8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) - eslint: 9.26.0(jiti@2.4.2) - typescript: 5.8.3 + "@typescript-eslint/eslint-plugin": 8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.33.0(jiti@2.4.2))(typescript@5.9.2) + "@typescript-eslint/parser": 8.40.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.9.2) + "@typescript-eslint/typescript-estree": 8.40.0(typescript@5.9.2) + "@typescript-eslint/utils": 8.40.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.9.2) + eslint: 9.33.0(jiti@2.4.2) + typescript: 5.9.2 transitivePeerDependencies: - supports-color - typescript@5.8.3: {} + typescript@5.9.2: {} ua-is-frozen@0.1.2: {} @@ -18407,8 +17973,6 @@ snapshots: transitivePeerDependencies: - encoding - ufo@1.6.1: {} - unbox-primitive@1.1.0: dependencies: call-bound: 1.0.4 @@ -18465,24 +18029,46 @@ snapshots: unist-util-is: 6.0.0 unist-util-visit-parents: 6.0.1 - unpipe@1.0.0: {} - unplugin@1.0.1: dependencies: - acorn: 8.14.1 + acorn: 8.15.0 chokidar: 3.6.0 - webpack-sources: 3.2.3 + webpack-sources: 3.3.3 webpack-virtual-modules: 0.5.0 unplugin@2.1.0: dependencies: - acorn: 8.14.1 + acorn: 8.15.0 webpack-virtual-modules: 0.6.2 optional: true - update-browserslist-db@1.1.3(browserslist@4.24.4): + unrs-resolver@1.11.1: dependencies: - browserslist: 4.24.4 + napi-postinstall: 0.3.3 + optionalDependencies: + "@unrs/resolver-binding-android-arm-eabi": 1.11.1 + "@unrs/resolver-binding-android-arm64": 1.11.1 + "@unrs/resolver-binding-darwin-arm64": 1.11.1 + "@unrs/resolver-binding-darwin-x64": 1.11.1 + "@unrs/resolver-binding-freebsd-x64": 1.11.1 + "@unrs/resolver-binding-linux-arm-gnueabihf": 1.11.1 + "@unrs/resolver-binding-linux-arm-musleabihf": 1.11.1 + "@unrs/resolver-binding-linux-arm64-gnu": 1.11.1 + "@unrs/resolver-binding-linux-arm64-musl": 1.11.1 + "@unrs/resolver-binding-linux-ppc64-gnu": 1.11.1 + "@unrs/resolver-binding-linux-riscv64-gnu": 1.11.1 + "@unrs/resolver-binding-linux-riscv64-musl": 1.11.1 + "@unrs/resolver-binding-linux-s390x-gnu": 1.11.1 + "@unrs/resolver-binding-linux-x64-gnu": 1.11.1 + "@unrs/resolver-binding-linux-x64-musl": 1.11.1 + "@unrs/resolver-binding-wasm32-wasi": 1.11.1 + "@unrs/resolver-binding-win32-arm64-msvc": 1.11.1 + "@unrs/resolver-binding-win32-ia32-msvc": 1.11.1 + "@unrs/resolver-binding-win32-x64-msvc": 1.11.1 + + update-browserslist-db@1.1.3(browserslist@4.25.2): + dependencies: + browserslist: 4.25.2 escalade: 3.2.0 picocolors: 1.1.1 @@ -18499,6 +18085,12 @@ snapshots: optionalDependencies: "@types/react": 18.3.12 + use-composed-ref@1.4.0(@types/react@18.3.12)(react@18.3.1): + dependencies: + react: 18.3.1 + optionalDependencies: + "@types/react": 18.3.12 + use-debounce@10.0.4(react@18.3.1): dependencies: react: 18.3.1 @@ -18510,6 +18102,19 @@ snapshots: intl-messageformat: 10.7.16 react: 18.3.1 + use-isomorphic-layout-effect@1.2.1(@types/react@18.3.12)(react@18.3.1): + dependencies: + react: 18.3.1 + optionalDependencies: + "@types/react": 18.3.12 + + use-latest@1.3.0(@types/react@18.3.12)(react@18.3.1): + dependencies: + react: 18.3.1 + use-isomorphic-layout-effect: 1.2.1(@types/react@18.3.12)(react@18.3.1) + optionalDependencies: + "@types/react": 18.3.12 + use-sidecar@1.1.3(@types/react@18.3.12)(react@18.3.1): dependencies: detect-node-es: 1.1.0 @@ -18544,33 +18149,92 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 - vite-plugin-css-injected-by-js@3.5.2(vite@6.3.5(@types/node@22.8.1)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.4)(yaml@2.7.0)): + vite-node@3.2.4(@types/node@22.8.1)(jiti@2.4.2)(terser@5.43.1)(tsx@4.19.4)(yaml@2.7.0): dependencies: - vite: 6.3.5(@types/node@22.8.1)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.4)(yaml@2.7.0) + cac: 6.7.14 + debug: 4.4.1 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 6.3.5(@types/node@22.8.1)(jiti@2.4.2)(terser@5.43.1)(tsx@4.19.4)(yaml@2.7.0) + transitivePeerDependencies: + - "@types/node" + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml - vite@6.3.5(@types/node@22.8.1)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.4)(yaml@2.7.0): + vite@6.3.5(@types/node@22.8.1)(jiti@2.4.2)(terser@5.43.1)(tsx@4.19.4)(yaml@2.7.0): dependencies: esbuild: 0.25.0 - fdir: 6.4.4(picomatch@4.0.2) - picomatch: 4.0.2 + fdir: 6.4.4(picomatch@4.0.3) + picomatch: 4.0.3 postcss: 8.5.3 rollup: 4.41.0 - tinyglobby: 0.2.13 + tinyglobby: 0.2.14 optionalDependencies: "@types/node": 22.8.1 fsevents: 2.3.3 jiti: 2.4.2 - terser: 5.39.0 + terser: 5.43.1 tsx: 4.19.4 yaml: 2.7.0 - walk-up-path@3.0.1: {} + vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.8.1)(jiti@2.4.2)(terser@5.43.1)(tsx@4.19.4)(yaml@2.7.0): + dependencies: + "@types/chai": 5.2.2 + "@vitest/expect": 3.2.4 + "@vitest/mocker": 3.2.4(vite@6.3.5(@types/node@22.8.1)(jiti@2.4.2)(terser@5.43.1)(tsx@4.19.4)(yaml@2.7.0)) + "@vitest/pretty-format": 3.2.4 + "@vitest/runner": 3.2.4 + "@vitest/snapshot": 3.2.4 + "@vitest/spy": 3.2.4 + "@vitest/utils": 3.2.4 + chai: 5.2.1 + debug: 4.4.1 + expect-type: 1.2.2 + magic-string: 0.30.17 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.9.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.14 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 6.3.5(@types/node@22.8.1)(jiti@2.4.2)(terser@5.43.1)(tsx@4.19.4)(yaml@2.7.0) + vite-node: 3.2.4(@types/node@22.8.1)(jiti@2.4.2)(terser@5.43.1)(tsx@4.19.4)(yaml@2.7.0) + why-is-node-running: 2.3.0 + optionalDependencies: + "@types/debug": 4.1.12 + "@types/node": 22.8.1 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + walk-up-path@4.0.0: {} warning@4.0.3: dependencies: loose-envify: 1.4.0 - watchpack@2.4.2: + watchpack@2.4.4: dependencies: glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 @@ -18581,26 +18245,24 @@ snapshots: webidl-conversions@3.0.1: {} - webidl-conversions@4.0.2: {} - - webpack-sources@3.2.3: {} + webpack-sources@3.3.3: {} webpack-virtual-modules@0.5.0: {} webpack-virtual-modules@0.6.2: optional: true - webpack@5.98.0(esbuild@0.25.0): + webpack@5.98.0: dependencies: "@types/eslint-scope": 3.7.7 - "@types/estree": 1.0.7 + "@types/estree": 1.0.8 "@webassemblyjs/ast": 1.14.1 "@webassemblyjs/wasm-edit": 1.14.1 "@webassemblyjs/wasm-parser": 1.14.1 - acorn: 8.14.1 - browserslist: 4.24.4 + acorn: 8.15.0 + browserslist: 4.25.2 chrome-trace-event: 1.0.4 - enhanced-resolve: 5.18.1 + enhanced-resolve: 5.18.3 es-module-lexer: 1.7.0 eslint-scope: 5.1.1 events: 3.3.0 @@ -18611,10 +18273,10 @@ snapshots: mime-types: 2.1.35 neo-async: 2.6.2 schema-utils: 4.3.2 - tapable: 2.2.1 - terser-webpack-plugin: 5.3.14(esbuild@0.25.0)(webpack@5.98.0(esbuild@0.25.0)) - watchpack: 2.4.2 - webpack-sources: 3.2.3 + tapable: 2.2.2 + terser-webpack-plugin: 5.3.14(webpack@5.98.0) + watchpack: 2.4.4 + webpack-sources: 3.3.3 transitivePeerDependencies: - "@swc/core" - esbuild @@ -18625,12 +18287,6 @@ snapshots: tr46: 0.0.3 webidl-conversions: 3.0.1 - whatwg-url@7.1.0: - dependencies: - lodash.sortby: 4.7.0 - tr46: 1.0.1 - webidl-conversions: 4.0.2 - which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 @@ -18676,6 +18332,11 @@ snapshots: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + word-wrap@1.2.5: {} wrap-ansi@7.0.0: @@ -18696,7 +18357,8 @@ snapshots: string-width: 7.2.0 strip-ansi: 7.1.0 - wrappy@1.0.2: {} + wrappy@1.0.2: + optional: true ws@8.17.1: {} @@ -18710,20 +18372,10 @@ snapshots: yoga-layout@3.2.1: {} - zod-to-json-schema@3.24.5(zod@3.24.4): - dependencies: - zod: 3.24.4 - - zod-validation-error@3.4.1(zod@3.24.4): + zod-validation-error@3.5.3(zod@3.24.4): dependencies: zod: 3.24.4 zod@3.24.4: {} - zustand@5.0.5(@types/react@18.3.12)(react@18.3.1)(use-sync-external-store@1.5.0(react@18.3.1)): - optionalDependencies: - "@types/react": 18.3.12 - react: 18.3.1 - use-sync-external-store: 1.5.0(react@18.3.1) - zwitch@2.0.4: {} diff --git a/src/actions/subscribe-action.ts b/src/actions/subscribe-action.ts index 1e5d049..d2b5414 100644 --- a/src/actions/subscribe-action.ts +++ b/src/actions/subscribe-action.ts @@ -39,7 +39,7 @@ export async function subscribeAction(formData: FormData) { // Check email rate limit const emailLimit = await checkRateLimit( validatedFields.email.toLowerCase(), - emailLimiter + emailLimiter, ); if (!emailLimit.success) { return { error: emailLimit.error }; @@ -53,7 +53,7 @@ export async function subscribeAction(formData: FormData) { if ( existingContacts?.data?.some( (contact) => - contact.email.toLowerCase() === validatedFields.email.toLowerCase() + contact.email.toLowerCase() === validatedFields.email.toLowerCase(), ) ) { return { error: "This email is already subscribed." }; diff --git a/src/app/(app)/components/cta-toasts/index.tsx b/src/app/(app)/components/cta-toasts/index.tsx index d76ef6c..2cffcb8 100644 --- a/src/app/(app)/components/cta-toasts/index.tsx +++ b/src/app/(app)/components/cta-toasts/index.tsx @@ -49,7 +49,7 @@ export function customPremiumToast(toast: Omit) { ), { duration: Infinity, - } + }, ); } @@ -71,12 +71,12 @@ export function customDefaultToast(toast: Omit) { ), { duration: Infinity, - } + }, ); } const SonnerCloseButton = ( - props: React.ButtonHTMLAttributes + props: React.ButtonHTMLAttributes, ) => { return ( + } + content="View localStorage data" + /> + + {/* Draggable Window */} + {isOpen ? ( +
+ {/* Header with drag handle */} +
+
+ +
+ + localStorage Viewer + + + {Object.keys(localStorageData).length} items + {lastUpdated && ` • Last updated: ${lastUpdated}`} + +
+
+
+ + + + } + content="Refresh data" + /> + setIsExpanded(!isExpanded)} + _size="sm" + _variant="ghost" + className="h-6 w-6 p-0" + > + {isExpanded ? ( + + ) : ( + + )} + + } + content={isExpanded ? "Collapse" : "Expand"} + /> + setIsOpen(false)} + _size="sm" + _variant="ghost" + className="h-6 w-6 p-0" + > + + + } + content="Close" + /> +
+
+ + {/* Content */} +
+
+ +
+
+
+ ) : null} + + ); +} diff --git a/src/app/(app)/components/index.tsx b/src/app/(app)/components/index.tsx index 388163d..953a8e9 100644 --- a/src/app/(app)/components/index.tsx +++ b/src/app/(app)/components/index.tsx @@ -35,7 +35,7 @@ const InvoicePDFViewer = dynamic( { ssr: false, loading: () => , - } + }, ); const AndroidPDFViewer = dynamic( @@ -43,7 +43,7 @@ const AndroidPDFViewer = dynamic( { ssr: false, loading: () => , - } + }, ); const PdfViewer = ({ @@ -131,7 +131,7 @@ export function InvoiceClientPage({
@@ -210,7 +210,7 @@ export function InvoiceClientPage({
diff --git a/src/app/(app)/components/invoice-form/index.tsx b/src/app/(app)/components/invoice-form/index.tsx index 83bd947..0c7b649 100644 --- a/src/app/(app)/components/invoice-form/index.tsx +++ b/src/app/(app)/components/invoice-form/index.tsx @@ -63,13 +63,13 @@ type AccordionKeys = Array<(typeof DEFAULT_ACCORDION_VALUES)[number]>; interface InvoiceFormProps { invoiceData: InvoiceData; - onInvoiceDataChange: (updatedData: InvoiceData) => void; + handleInvoiceDataChange: (updatedData: InvoiceData) => void; setCanShareInvoice: (canShareInvoice: boolean) => void; } export const InvoiceForm = memo(function InvoiceForm({ invoiceData, - onInvoiceDataChange, + handleInvoiceDataChange, setCanShareInvoice, }: InvoiceFormProps) { const form = useForm({ @@ -96,7 +96,7 @@ export const InvoiceForm = memo(function InvoiceForm({ const selectedDateFormat = useWatch({ control, name: "dateFormat" }); const isPaymentDueBeforeDateOfIssue = dayjs(paymentDue).isBefore( - dayjs(dateOfIssue) + dayjs(dateOfIssue), ); // payment due date is 14 days after the date of issue or the same day @@ -124,7 +124,7 @@ export const InvoiceForm = memo(function InvoiceForm({ ? Number( invoiceItems .reduce((sum, item) => sum + (item?.preTaxAmount || 0), 0) - .toFixed(2) + .toFixed(2), ) : 0; @@ -177,7 +177,7 @@ export const InvoiceForm = memo(function InvoiceForm({ } }, // debounce delay in ms - DEBOUNCE_TIMEOUT + DEBOUNCE_TIMEOUT, ); // subscribe to form changes to regenerate pdf on every input change @@ -211,12 +211,12 @@ export const InvoiceForm = memo(function InvoiceForm({ const currentFormData = watch(); debouncedRegeneratePdfOnFormChange(currentFormData); }, - [remove, watch, debouncedRegeneratePdfOnFormChange] + [remove, watch, debouncedRegeneratePdfOnFormChange], ); // TODO: refactor this and debouncedRegeneratePdfOnFormChange(), so data is saved to local storage, basically copy everything from debouncedRegeneratePdfOnFormChange() and use this onSubmit function in two places const onSubmit = (data: InvoiceData) => { - onInvoiceDataChange(data); + handleInvoiceDataChange(data); }; /** @@ -230,7 +230,7 @@ export const InvoiceForm = memo(function InvoiceForm({ // Try to load from localStorage try { const savedState = localStorage.getItem( - ACCORDION_STATE_LOCAL_STORAGE_KEY + ACCORDION_STATE_LOCAL_STORAGE_KEY, ); if (savedState) { @@ -273,7 +273,7 @@ export const InvoiceForm = memo(function InvoiceForm({ localStorage.setItem( ACCORDION_STATE_LOCAL_STORAGE_KEY, - JSON.stringify(stateToSave) + JSON.stringify(stateToSave), ); } catch (error) { console.error("Error saving accordion state:", error); @@ -310,7 +310,7 @@ export const InvoiceForm = memo(function InvoiceForm({ if (Array.isArray(error)) { return error.map((item, index) => Object.entries( - item as { [key: string]: { message?: string } } + item as { [key: string]: { message?: string } }, ).map(([fieldName, fieldError]) => (
  • {fieldError?.message || "Unknown error"}
  • - )) + )), ); } // Handle nested object errors if (error && typeof error === "object") { return Object.entries( - error as { [key: string]: { message?: string } } + error as { [key: string]: { message?: string } }, ).map(([nestedKey, nestedError]) => { return (
  • @@ -342,7 +342,7 @@ export const InvoiceForm = memo(function InvoiceForm({ , { closeButton: true, - } + }, ); })} > @@ -636,53 +636,58 @@ export const InvoiceForm = memo(function InvoiceForm({ )} -
    -
    - {/* Show/hide Person Authorized to Receive field in PDF switch */} -
    - + {/* + Stripe template doesn't have these fields + */} + {invoiceData.template === "default" && ( +
    +
    + {/* Show/hide Person Authorized to Receive field in PDF switch */} +
    + - ( - - )} - /> -
    + ( + + )} + /> +
    - {/* Show/hide Person Authorized to Issue field in PDF switch */} -
    - + {/* Show/hide Person Authorized to Issue field in PDF switch */} +
    + - ( - - )} - /> + ( + + )} + /> +
    -
    + )}
    ); @@ -703,7 +708,7 @@ const calculateItemTotals = (item: InvoiceItemData | null) => { const formattedVatAmount = Number(vatAmount.toFixed(2)); const formattedPreTaxAmount = Number( - (formattedNetAmount + formattedVatAmount).toFixed(2) + (formattedNetAmount + formattedVatAmount).toFixed(2), ); return { diff --git a/src/app/(app)/components/invoice-form/sections/general-information.tsx b/src/app/(app)/components/invoice-form/sections/general-information.tsx index 723d164..f33cf4e 100644 --- a/src/app/(app)/components/invoice-form/sections/general-information.tsx +++ b/src/app/(app)/components/invoice-form/sections/general-information.tsx @@ -101,7 +101,7 @@ export const GeneralInformation = memo(function GeneralInformation({ const isDateOfServiceEqualsEndOfCurrentMonth = dayjs(dateOfService).isSame( dayjs().endOf("month"), - "day" + "day", ); const isDefaultInvoiceNumberLabel = @@ -109,7 +109,7 @@ export const GeneralInformation = memo(function GeneralInformation({ // extract the month and year from the invoice number (i.e. 1/04-2025 -> 04-2025) const extractInvoiceMonthAndYear = /(\d{2}-\d{4})/.exec( - invoiceNumberValue ?? "" + invoiceNumberValue ?? "", )?.[1]; const isInvoiceNumberInCurrentMonth = @@ -144,7 +144,7 @@ export const GeneralInformation = memo(function GeneralInformation({ toast.error("Error uploading image. Please try again."); } }, - [setValue] + [setValue], ); const handleLogoRemove = useCallback(() => { @@ -325,7 +325,7 @@ export const GeneralInformation = memo(function GeneralInformation({ // we need to keep the invoice number suffix (e.g. 1/MM-YYYY) for better user experience, when switching language setValue( "invoiceNumberObject.label", - `${newInvoiceNumberLabel}:` + `${newInvoiceNumberLabel}:`, ); setValue("invoiceNumberObject.value", invoiceNumberValue); }} @@ -460,7 +460,7 @@ export const GeneralInformation = memo(function GeneralInformation({ onClick={() => { setValue( "invoiceNumberObject.label", - defaultInvoiceNumber + defaultInvoiceNumber, ); }} > @@ -504,7 +504,7 @@ export const GeneralInformation = memo(function GeneralInformation({ onClick={() => { setValue( "invoiceNumberObject.value", - `1/${CURRENT_MONTH_AND_YEAR}` + `1/${CURRENT_MONTH_AND_YEAR}`, ); }} > diff --git a/src/app/(app)/components/invoice-form/sections/invoice-items.tsx b/src/app/(app)/components/invoice-form/sections/invoice-items.tsx index 90a9fe8..38354d3 100644 --- a/src/app/(app)/components/invoice-form/sections/invoice-items.tsx +++ b/src/app/(app)/components/invoice-form/sections/invoice-items.tsx @@ -114,14 +114,14 @@ export const InvoiceItems = memo(function InvoiceItems({ type="button" onClick={() => { const canDelete = window.confirm( - `Are you sure you want to delete invoice item #${index + 1}?` + `Are you sure you want to delete invoice item #${index + 1}?`, ); if (canDelete) { handleRemoveItem(index); } }} - className="flex items-center justify-center rounded-full bg-red-600 p-2 transition-colors hover:bg-red-700" + className="flex items-center justify-center rounded-full bg-red-600 p-2 transition-colors hover:bg-red-700 active:scale-[98%] active:transition-transform" > Delete Invoice Item {index + 1} diff --git a/src/app/(app)/components/invoice-pdf-download-link.tsx b/src/app/(app)/components/invoice-pdf-download-link.tsx index fac34be..393db28 100644 --- a/src/app/(app)/components/invoice-pdf-download-link.tsx +++ b/src/app/(app)/components/invoice-pdf-download-link.tsx @@ -123,7 +123,7 @@ export function InvoicePDFDownloadLink({ if (!pdfLoading) { const timer = setTimeout( () => setIsLoading(false), - LOADING_BUTTON_TIMEOUT + LOADING_BUTTON_TIMEOUT, ); return () => clearTimeout(timer); } @@ -155,13 +155,13 @@ export function InvoicePDFDownloadLink({ onClick={handleClick} className={cn( "h-[36px] w-full rounded-lg bg-slate-900 px-4 py-2 text-center text-sm font-medium text-slate-50", - "shadow-sm shadow-black/5 outline-offset-2 hover:bg-slate-900/90", + "shadow-sm shadow-black/5 outline-offset-2 hover:bg-slate-900/90 active:scale-[98%] active:transition-transform", "focus-visible:border-indigo-500 focus-visible:ring focus-visible:ring-indigo-200 focus-visible:ring-opacity-50", "dark:bg-slate-50 dark:text-slate-900 dark:hover:bg-slate-50/90 lg:mb-0 lg:w-[210px]", { "pointer-events-none opacity-70": isLoading, "lg:w-[240px]": invoiceData.language === "pt", - } + }, )} > diff --git a/src/app/(app)/components/invoice-pdf-download-multiple-languages.tsx b/src/app/(app)/components/invoice-pdf-download-multiple-languages.tsx index 42409d5..727f6e6 100644 --- a/src/app/(app)/components/invoice-pdf-download-multiple-languages.tsx +++ b/src/app/(app)/components/invoice-pdf-download-multiple-languages.tsx @@ -46,7 +46,7 @@ export function InvoicePDFDownloadMultipleLanguages({ }, [language]); const generateAndZipPDFs = async ( - selectedLanguages: SupportedLanguages[] + selectedLanguages: SupportedLanguages[], ) => { try { // Generate PDF documents for each selected language @@ -83,7 +83,7 @@ export function InvoicePDFDownloadMultipleLanguages({ selectedLanguages.forEach((lang, index) => { zip.file( `invoice-${lang}-${invoiceNumberFormatted}.pdf`, - pdfBlobs[index] + pdfBlobs[index], ); }); diff --git a/src/app/(app)/components/invoice-pdf-stripe-template/stripe-due-amount.tsx b/src/app/(app)/components/invoice-pdf-stripe-template/stripe-due-amount.tsx index 30634c6..4ffc5b3 100644 --- a/src/app/(app)/components/invoice-pdf-stripe-template/stripe-due-amount.tsx +++ b/src/app/(app)/components/invoice-pdf-stripe-template/stripe-due-amount.tsx @@ -30,7 +30,7 @@ export function StripeDueAmount({ dayjs.locale(language); const paymentDueDate = dayjs(invoiceData.paymentDue).format( - invoiceData.dateFormat + invoiceData.dateFormat, ); // Check if payOnlineUrl is provided and valid diff --git a/src/app/(app)/components/invoice-pdf-stripe-template/stripe-footer.tsx b/src/app/(app)/components/invoice-pdf-stripe-template/stripe-footer.tsx index dad9c46..0b4c627 100644 --- a/src/app/(app)/components/invoice-pdf-stripe-template/stripe-footer.tsx +++ b/src/app/(app)/components/invoice-pdf-stripe-template/stripe-footer.tsx @@ -22,7 +22,7 @@ export function StripeFooter({ const invoiceNumber = `${invoiceNumberValue}`; const paymentDueDate = dayjs(invoiceData.paymentDue).format( - invoiceData.dateFormat + invoiceData.dateFormat, ); return ( diff --git a/src/app/(app)/components/invoice-pdf-stripe-template/stripe-invoice-info.tsx b/src/app/(app)/components/invoice-pdf-stripe-template/stripe-invoice-info.tsx index b2b0d14..74cf488 100644 --- a/src/app/(app)/components/invoice-pdf-stripe-template/stripe-invoice-info.tsx +++ b/src/app/(app)/components/invoice-pdf-stripe-template/stripe-invoice-info.tsx @@ -16,13 +16,13 @@ export function StripeInvoiceInfo({ const t = TRANSLATIONS[language]; const dateOfIssue = dayjs(invoiceData.dateOfIssue).format( - invoiceData.dateFormat + invoiceData.dateFormat, ); const invoiceNumberValue = invoiceData?.invoiceNumberObject?.value; const paymentDueDate = dayjs(invoiceData.paymentDue).format( - invoiceData.dateFormat + invoiceData.dateFormat, ); // for better readability, we need to adjust the column width based on the language diff --git a/src/app/(app)/components/invoice-pdf-stripe-template/stripe-items-table.tsx b/src/app/(app)/components/invoice-pdf-stripe-template/stripe-items-table.tsx index c025a2e..09bd809 100644 --- a/src/app/(app)/components/invoice-pdf-stripe-template/stripe-items-table.tsx +++ b/src/app/(app)/components/invoice-pdf-stripe-template/stripe-items-table.tsx @@ -31,7 +31,7 @@ export function StripeItemsTable({ // Check if any items have numeric VAT values (not "NP" or "OO") const hasNumericVat = invoiceData.items.some( - (item) => typeof item.vat === "number" + (item) => typeof item.vat === "number", ); // Calculate service period (example: Jan 01 2025 - Jan 31 2025) @@ -40,7 +40,7 @@ export function StripeItemsTable({ .format(invoiceData.dateFormat); const servicePeriodEnd = dayjs(invoiceData.dateOfService).format( - invoiceData.dateFormat + invoiceData.dateFormat, ); const vatAmountFieldIsVisible = invoiceData.items[0].vatFieldIsVisible; diff --git a/src/app/(app)/components/invoice-pdf-stripe-template/stripe-totals.tsx b/src/app/(app)/components/invoice-pdf-stripe-template/stripe-totals.tsx index 088745f..e0df6f2 100644 --- a/src/app/(app)/components/invoice-pdf-stripe-template/stripe-totals.tsx +++ b/src/app/(app)/components/invoice-pdf-stripe-template/stripe-totals.tsx @@ -19,7 +19,7 @@ export function StripeTotals({ // Calculate subtotal (sum of all items) const subtotal = invoiceData.items.reduce( (sum, item) => sum + item.netAmount, - 0 + 0, ); const formattedSubtotal = formatCurrency({ amount: subtotal, @@ -35,7 +35,7 @@ export function StripeTotals({ // Check if any items have numeric VAT values (not "NP" or "OO") const hasNumericVat = invoiceData.items.some( - (item) => typeof item.vat === "number" + (item) => typeof item.vat === "number", ); return ( diff --git a/src/app/(app)/components/invoice-pdf-template/invoice-footer.tsx b/src/app/(app)/components/invoice-pdf-template/invoice-footer.tsx index d44dc22..85f13bf 100644 --- a/src/app/(app)/components/invoice-pdf-template/invoice-footer.tsx +++ b/src/app/(app)/components/invoice-pdf-template/invoice-footer.tsx @@ -19,7 +19,7 @@ export function InvoiceFooter({ const invoiceNumberValue = invoiceData?.invoiceNumberObject?.value; const paymentDueDate = dayjs(invoiceData.paymentDue).format( - invoiceData.dateFormat + invoiceData.dateFormat, ); const invoiceTotal = invoiceData?.total; diff --git a/src/app/(app)/components/invoice-pdf-template/invoice-header.tsx b/src/app/(app)/components/invoice-pdf-template/invoice-header.tsx index 74432bc..4230734 100644 --- a/src/app/(app)/components/invoice-pdf-template/invoice-header.tsx +++ b/src/app/(app)/components/invoice-pdf-template/invoice-header.tsx @@ -16,10 +16,10 @@ export function InvoiceHeader({ const t = TRANSLATIONS[language]; const dateOfIssue = dayjs(invoiceData.dateOfIssue).format( - invoiceData.dateFormat + invoiceData.dateFormat, ); const dateOfService = dayjs(invoiceData.dateOfService).format( - invoiceData.dateFormat + invoiceData.dateFormat, ); const invoiceNumberLabel = invoiceData?.invoiceNumberObject?.label; diff --git a/src/app/(app)/components/invoice-pdf-template/invoice-payment-info.tsx b/src/app/(app)/components/invoice-pdf-template/invoice-payment-info.tsx index a7b920d..7d60e66 100644 --- a/src/app/(app)/components/invoice-pdf-template/invoice-payment-info.tsx +++ b/src/app/(app)/components/invoice-pdf-template/invoice-payment-info.tsx @@ -16,7 +16,7 @@ export function InvoicePaymentInfo({ const t = TRANSLATIONS[language]; const paymentDate = dayjs(invoiceData.paymentDue).format( - invoiceData.dateFormat + invoiceData.dateFormat, ); const paymentMethodIsVisible = invoiceData.paymentMethodFieldIsVisible; diff --git a/src/app/(app)/components/invoice-pdf-template/invoice-vat-summary-table.tsx b/src/app/(app)/components/invoice-pdf-template/invoice-vat-summary-table.tsx index 8bf7e5b..aba7136 100644 --- a/src/app/(app)/components/invoice-pdf-template/invoice-vat-summary-table.tsx +++ b/src/app/(app)/components/invoice-pdf-template/invoice-vat-summary-table.tsx @@ -35,7 +35,7 @@ export function InvoiceVATSummaryTable({ const totalNetAmount = sortedItems.reduce( (acc, item) => acc + item.netAmount, - 0 + 0, ); const formattedTotalNetAmount = totalNetAmount .toLocaleString("en-US", { @@ -46,7 +46,7 @@ export function InvoiceVATSummaryTable({ const totalVATAmount = sortedItems.reduce( (acc, item) => acc + item.vatAmount, - 0 + 0, ); const formattedTotalVATAmount = totalVATAmount .toLocaleString("en-US", { diff --git a/src/app/(app)/error.tsx b/src/app/(app)/error.tsx index 1d87675..aedb888 100644 --- a/src/app/(app)/error.tsx +++ b/src/app/(app)/error.tsx @@ -24,7 +24,7 @@ export default function Error({ { closeButton: true, richColors: true, - } + }, ); }, [error]); @@ -60,7 +60,7 @@ export default function Error({ // Clear the invoice data and start from scratch localStorage.setItem( PDF_DATA_LOCAL_STORAGE_KEY, - JSON.stringify(INITIAL_INVOICE_DATA) + JSON.stringify(INITIAL_INVOICE_DATA), ); // Attempt to recover by trying to re-render the segment diff --git a/src/app/(app)/page.client.tsx b/src/app/(app)/page.client.tsx index 96b8938..a27a59b 100644 --- a/src/app/(app)/page.client.tsx +++ b/src/app/(app)/page.client.tsx @@ -4,11 +4,9 @@ import { INITIAL_INVOICE_DATA } from "@/app/constants"; import { invoiceSchema, PDF_DATA_LOCAL_STORAGE_KEY, - SUPPORTED_LANGUAGES, + SUPPORTED_TEMPLATES, type InvoiceData, } from "@/app/schema"; -import { TRANSLATIONS } from "@/app/schema/translations"; -import { GithubIcon } from "@/components/etc/github-logo"; import { ProjectLogo } from "@/components/etc/project-logo"; import { Button } from "@/components/ui/button"; import { @@ -24,6 +22,7 @@ import Link from "next/link"; import { useRouter } from "next/navigation"; import { Footer } from "@/components/footer"; +import { GitHubStarCTA } from "@/components/github-star-cta"; import { ProjectLogoDescription } from "@/components/project-logo-description"; import { GITHUB_URL, VIDEO_DEMO_URL } from "@/config"; import { isLocalStorageAvailable } from "@/lib/check-local-storage"; @@ -35,8 +34,13 @@ import { compressToEncodedURIComponent, decompressFromEncodedURIComponent, } from "lz-string"; +import { + compressInvoiceData, + decompressInvoiceData, +} from "@/utils/url-compression"; +import dynamic from "next/dynamic"; import { useSearchParams } from "next/navigation"; -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { toast } from "sonner"; import { z } from "zod"; import { InvoiceClientPage } from "./components"; @@ -45,73 +49,35 @@ import { customPremiumToast, } from "./components/cta-toasts"; import { InvoicePDFDownloadLink } from "./components/invoice-pdf-download-link"; +import { handleInvoiceNumberBreakingChange } from "./utils/invoice-number-breaking-change"; +// import { DevLocalStorageView } from "./components/dev/dev-local-storage-view"; // import { InvoicePDFDownloadMultipleLanguages } from "./components/invoice-pdf-download-multiple-languages"; -/** - * This function handles the breaking change of the invoice number field. - * It removes the old "invoiceNumber" field and adds the new "invoiceNumberObject" field with label and value. - * @param json - The JSON object to handle the breaking change. - * @returns The updated JSON object. - */ -function handleInvoiceNumberBreakingChange(json: unknown) { - // check if the invoice number is in the json - if ( - typeof json === "object" && - json !== null && - "invoiceNumber" in json && - typeof json.invoiceNumber === "string" && - "language" in json - ) { - umamiTrackEvent("breaking_change_detected"); - - let lang: keyof typeof TRANSLATIONS; - - const invoiceLanguage = z - .enum(SUPPORTED_LANGUAGES) - .safeParse(json.language); - - if (!invoiceLanguage.success) { - console.error("Invalid invoice language:", invoiceLanguage.error); - - // fallback to default language - lang = SUPPORTED_LANGUAGES[0]; - } else { - lang = invoiceLanguage.data; - } - - const invoiceNumberLabel = TRANSLATIONS[lang].invoiceNumber; - - // Create new object without invoiceNumber and with invoiceNumberObject - const newJson = { - ...json, - // assign invoiceNumber to invoiceNumberObject.value - invoiceNumberObject: { - label: `${invoiceNumberLabel}:`, - value: json.invoiceNumber, - }, - }; - - // remove deprecated invoiceNumber from json - delete (newJson as Record).invoiceNumber; - - // update json - json = newJson; - - return json; - } - - return json; -} +const DevLocalStorageView = dynamic( + () => + import("./components/dev/dev-local-storage-view").then( + (mod) => mod.DevLocalStorageView, + ), + { ssr: false }, +); export function AppPageClient() { const router = useRouter(); const searchParams = useSearchParams(); + const urlTemplateSearchParam = searchParams.get("template"); + + // Validate template parameter with zod + const templateValidation = z + .enum(SUPPORTED_TEMPLATES) + .default("default") + .safeParse(urlTemplateSearchParam); + const { isDesktop } = useDeviceContext(); const isMobile = !isDesktop; const [invoiceDataState, setInvoiceDataState] = useState( - null + null, ); const [errorWhileGeneratingPdfIsShown, setErrorWhileGeneratingPdfIsShown] = @@ -119,6 +85,55 @@ export function AppPageClient() { const [canShareInvoice, setCanShareInvoice] = useState(true); + // Helper function to load from localStorage + const loadFromLocalStorage = useCallback(() => { + try { + const savedData = localStorage.getItem(PDF_DATA_LOCAL_STORAGE_KEY); + + if (savedData) { + const json: unknown = JSON.parse(savedData); + + // this should happen before parsing the data with zod + const updatedJson = handleInvoiceNumberBreakingChange(json); + + const parsedData = invoiceSchema.parse(updatedJson); + + // if template is in url, use it + if (templateValidation.success) { + parsedData.template = templateValidation.data; + } + + setInvoiceDataState(parsedData); + } else { + if (templateValidation.success) { + // if no data in local storage and template is in url, set initial data with template from url + setInvoiceDataState({ + ...INITIAL_INVOICE_DATA, + template: templateValidation.data, + }); + } else { + // if no data in local storage, set initial data + setInvoiceDataState(INITIAL_INVOICE_DATA); + } + } + } catch (error) { + console.error("Failed to load saved invoice data:", error); + + setInvoiceDataState(INITIAL_INVOICE_DATA); + + toast.error( + "Unable to load your saved invoice data. For your convenience, we've reset the form to default values. Please try creating a new invoice.", + { + duration: 20000, + closeButton: true, + richColors: true, + }, + ); + + Sentry.captureException(error); + } + }, [templateValidation.data, templateValidation.success]); + // Scroll to top on mount useEffect(() => { window.scrollTo({ top: 0, behavior: "smooth" }); @@ -127,20 +142,37 @@ export function AppPageClient() { // Initialize data from URL or localStorage on mount useEffect(() => { const compressedInvoiceDataInUrl = searchParams.get("data"); + const urlTemplateSearchParam = searchParams.get("template"); + + // Validate template parameter with zod + const templateValidation = z + .enum(SUPPORTED_TEMPLATES) + .default("default") + .safeParse(urlTemplateSearchParam); // first try to load from url if (compressedInvoiceDataInUrl) { try { const decompressed = decompressFromEncodedURIComponent( - compressedInvoiceDataInUrl + compressedInvoiceDataInUrl, ); + const parsedJSON: unknown = JSON.parse(decompressed); + // Restore original keys from compressed format, we store keys in compressed format to reduce URL size i.e. {name: "John Doe"} -> {n: "John Doe"} + const decompressedKeys = decompressInvoiceData( + parsedJSON as Record, + ); + // this should happen before parsing the data with zod - const updatedJson = handleInvoiceNumberBreakingChange(parsedJSON); + const updatedJson = handleInvoiceNumberBreakingChange(decompressedKeys); const validated = invoiceSchema.parse(updatedJson); + if (templateValidation.success) { + validated.template = templateValidation.data; + } + setInvoiceDataState(validated); } catch (error) { // fallback to local storage @@ -153,42 +185,7 @@ export function AppPageClient() { // if no data in url, load from local storage loadFromLocalStorage(); } - }, [searchParams]); - - // Helper function to load from localStorage - const loadFromLocalStorage = () => { - try { - const savedData = localStorage.getItem(PDF_DATA_LOCAL_STORAGE_KEY); - if (savedData) { - const json: unknown = JSON.parse(savedData); - - // this should happen before parsing the data with zod - const updatedJson = handleInvoiceNumberBreakingChange(json); - - const parsedData = invoiceSchema.parse(updatedJson); - - setInvoiceDataState(parsedData); - } else { - // if no data in local storage, set initial data - setInvoiceDataState(INITIAL_INVOICE_DATA); - } - } catch (error) { - console.error("Failed to load saved invoice data:", error); - - setInvoiceDataState(INITIAL_INVOICE_DATA); - - toast.error( - "Unable to load your saved invoice data. For your convenience, we've reset the form to default values. Please try creating a new invoice.", - { - duration: 20000, - closeButton: true, - richColors: true, - } - ); - - Sentry.captureException(error); - } - }; + }, [loadFromLocalStorage, searchParams]); // Save to localStorage whenever data changes on form update useEffect(() => { @@ -199,18 +196,30 @@ export function AppPageClient() { localStorage.setItem( PDF_DATA_LOCAL_STORAGE_KEY, - JSON.stringify(newInvoiceDataValidated) + JSON.stringify(newInvoiceDataValidated), ); - // Check if URL has data and current data is different + // Update template in search params if it exists + + const newSearchParams = new URLSearchParams(searchParams); + newSearchParams.set("template", newInvoiceDataValidated.template); + router.replace(`/?${newSearchParams.toString()}`); + + // Check if URL has data i.e. if user has shared invoice link const urlData = searchParams.get("data"); if (urlData) { try { const decompressed = decompressFromEncodedURIComponent(urlData); + const urlParsed: unknown = JSON.parse(decompressed); - const urlValidated = invoiceSchema.parse(urlParsed); + // Restore original keys from compressed format + const decompressedKeys = decompressInvoiceData( + urlParsed as Record, + ); + + const urlValidated = invoiceSchema.parse(decompressedKeys); if ( JSON.stringify(urlValidated) !== @@ -232,7 +241,7 @@ export function AppPageClient() { duration: 10000, closeButton: true, richColors: true, - } + }, ); // Clean URL if data differs @@ -241,7 +250,9 @@ export function AppPageClient() { } catch (error) { console.error("Failed to compare with URL data:", error); + // TODO: move to 'Initialize data from URL or localStorage on mount' useEffect? toast.error("The shared invoice URL appears to be incorrect", { + id: "invalid-invoice-url-error-toast", // prevent duplicate toasts description: (

    @@ -252,7 +263,7 @@ export function AppPageClient() { _variant="outline" _size="sm" onClick={() => { - router.replace("/"); + router.replace("/?template=default"); toast.dismiss(); }} > @@ -302,8 +313,8 @@ export function AppPageClient() { } }; - // Show cta toast after 40 seconds on the app page - const initialTimer = setTimeout(showCTAToast, 40_000); + // Show cta toast after 50 seconds on the app page + const initialTimer = setTimeout(showCTAToast, 50_000); return () => { clearTimeout(initialTimer); @@ -335,11 +346,16 @@ export function AppPageClient() { if (invoiceDataState) { try { const newInvoiceDataValidated = invoiceSchema.parse(invoiceDataState); - const stringified = JSON.stringify(newInvoiceDataValidated); - const compressedData = compressToEncodedURIComponent(stringified); + + // Compress JSON keys before stringifying to reduce URL size + const compressedKeys = compressInvoiceData(newInvoiceDataValidated); + const compressedJson = JSON.stringify(compressedKeys); + + const compressedData = compressToEncodedURIComponent(compressedJson); // Check if the compressed data length exceeds browser URL limits // Most browsers have a limit around 2000 characters for URLs + // With key compression, we can fit much larger invoices within this limit const URL_LENGTH_LIMIT = 2000; const estimatedUrlLength = window.location.origin.length + 7 + compressedData.length; // 7 for "/?data=" @@ -351,14 +367,19 @@ export function AppPageClient() { return; } - router.push(`/?data=${compressedData}`); + router.push( + `/?template=${newInvoiceDataValidated.template}&data=${compressedData}`, + ); // Construct full URL with locale and compressed data - const newFullUrl = `${window.location.origin}/?data=${compressedData}`; + const newFullUrl = `${window.location.origin}/?template=${newInvoiceDataValidated.template}&data=${compressedData}`; // Copy to clipboard await navigator.clipboard.writeText(newFullUrl); + // Dismiss any existing toast before showing new one + toast.dismiss(); + toast.success("Invoice link copied to clipboard!", { description: "Share this link to let others view and edit this invoice", @@ -382,6 +403,10 @@ export function AppPageClient() { return ( + {process.env.NEXT_PUBLIC_DEBUG_LOCAL_STORAGE_UI === "true" && ( + + )} +

    @@ -398,7 +423,7 @@ export function AppPageClient() {
    {" | "} Share your feedback - {" | "} - diff --git a/src/app/(app)/page.tsx b/src/app/(app)/page.tsx index 6cd7436..4cc6904 100644 --- a/src/app/(app)/page.tsx +++ b/src/app/(app)/page.tsx @@ -1,5 +1,6 @@ import type { Metadata } from "next"; import { AppPageClient } from "./page.client"; +import { STATIC_ASSETS_URL } from "@/config"; // we generate metadata here, because we need access to searchParams (in layout we don't have it) export async function generateMetadata({ @@ -8,7 +9,12 @@ export async function generateMetadata({ searchParams: { [key: string]: string | string[] | undefined }; }): Promise { const hasShareableData = Boolean(searchParams?.data); - const isProd = process.env.VERCEL_ENV === "production"; + const isStripeTemplate = Boolean(searchParams?.template === "stripe"); + + const isProd = + process.env.VERCEL_ENV === "production" && + `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}` === + "https://easyinvoicepdf.com"; const defaultRobotsConfig = { index: false, @@ -36,6 +42,22 @@ export async function generateMetadata({ alternates: { canonical: "/", }, + ...(isStripeTemplate && { + openGraph: { + title: "Stripe Invoice Template | Free Invoice Generator", + description: + "Create and download professional invoices instantly with EasyInvoicePDF.com. Free and open-source. No signup required.", + siteName: "EasyInvoicePDF.com | Free Invoice Generator", + images: [ + { + url: `${STATIC_ASSETS_URL}/stripe-og.png`, + width: 1200, + height: 630, + alt: "Stripe Invoice Template", + }, + ], + }, + }), }; } diff --git a/src/app/(app)/utils/__tests__/invoice-number-breaking-change.test.ts b/src/app/(app)/utils/__tests__/invoice-number-breaking-change.test.ts new file mode 100644 index 0000000..4a60a35 --- /dev/null +++ b/src/app/(app)/utils/__tests__/invoice-number-breaking-change.test.ts @@ -0,0 +1,372 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { handleInvoiceNumberBreakingChange } from "../invoice-number-breaking-change"; +import { SUPPORTED_LANGUAGES, type InvoiceData } from "@/app/schema"; +import { TRANSLATIONS } from "@/app/schema/translations"; + +// Mock the umami tracking function +vi.mock("@/lib/umami-analytics-track-event", () => ({ + umamiTrackEvent: vi.fn(), +})); + +import { umamiTrackEvent } from "@/lib/umami-analytics-track-event"; + +describe("handleInvoiceNumberBreakingChange", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe("valid input scenarios", () => { + it("should transform invoiceNumber to invoiceNumberObject with English language", () => { + const input = { + invoiceNumber: "INV-2024-001", + language: "en", + otherField: "preserved", + }; + + const result = handleInvoiceNumberBreakingChange(input); + + expect(result).toEqual({ + language: "en", + otherField: "preserved", + invoiceNumberObject: { + label: `${TRANSLATIONS.en.invoiceNumber}:`, + value: "INV-2024-001", + }, + }); + + // Should not contain the old invoiceNumber field + expect(result).not.toHaveProperty("invoiceNumber"); + + // Should track the breaking change event + expect(umamiTrackEvent).toHaveBeenCalledWith("breaking_change_detected"); + expect(umamiTrackEvent).toHaveBeenCalledTimes(1); + }); + + it("should transform invoiceNumber with Polish language", () => { + const input = { + invoiceNumber: "FAKT-001", + language: "pl", + }; + + const result = handleInvoiceNumberBreakingChange(input); + + expect(result).toEqual({ + language: "pl", + invoiceNumberObject: { + label: `${TRANSLATIONS.pl.invoiceNumber}:`, + value: "FAKT-001", + }, + }); + + expect(umamiTrackEvent).toHaveBeenCalledWith("breaking_change_detected"); + }); + + it("should transform invoiceNumber with German language", () => { + const input = { + invoiceNumber: "RG-2024-001", + language: "de", + }; + + const result = handleInvoiceNumberBreakingChange(input); + + expect(result).toEqual({ + language: "de", + invoiceNumberObject: { + label: `${TRANSLATIONS.de.invoiceNumber}:`, + value: "RG-2024-001", + }, + }); + + expect(umamiTrackEvent).toHaveBeenCalledWith("breaking_change_detected"); + }); + + it("should preserve all other fields when transforming", () => { + const input = { + invoiceNumber: "123", + language: "en", + dateOfIssue: "2024-01-15", + seller: { name: "ACME Corp" }, + buyer: { name: "Client Ltd" }, + items: [{ name: "Product A", amount: 1 }], + total: 100, + }; + + const result = handleInvoiceNumberBreakingChange(input); + + expect(result).toEqual({ + language: "en", + dateOfIssue: "2024-01-15", + seller: { name: "ACME Corp" }, + buyer: { name: "Client Ltd" }, + items: [{ name: "Product A", amount: 1 }], + total: 100, + invoiceNumberObject: { + label: `${TRANSLATIONS.en.invoiceNumber}:`, + value: "123", + }, + }); + }); + }); + + describe("invalid language scenarios", () => { + it("should fallback to default language when language is invalid", () => { + const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => { + // do nothing + }); + + const input = { + invoiceNumber: "INV-001", + language: "invalid-lang", + }; + + const result = handleInvoiceNumberBreakingChange(input); + + const defaultLanguage = SUPPORTED_LANGUAGES[0]; + expect(result).toEqual({ + language: "invalid-lang", + invoiceNumberObject: { + label: `${TRANSLATIONS[defaultLanguage].invoiceNumber}:`, + value: "INV-001", + }, + }); + + // Should log error for invalid language + expect(consoleSpy).toHaveBeenCalledWith( + "Invalid invoice language:", + expect.any(Object), + ); + + // Should still track the breaking change event + expect(umamiTrackEvent).toHaveBeenCalledWith("breaking_change_detected"); + + consoleSpy.mockRestore(); + }); + + it("should fallback to default language when language is not a string", () => { + const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => { + // do nothing + }); + + const input = { + invoiceNumber: "INV-001", + language: 123, + }; + + const result = handleInvoiceNumberBreakingChange(input); + + const defaultLanguage = SUPPORTED_LANGUAGES[0]; + expect(result).toEqual({ + language: 123, + invoiceNumberObject: { + label: `${TRANSLATIONS[defaultLanguage].invoiceNumber}:`, + value: "INV-001", + }, + }); + + expect(consoleSpy).toHaveBeenCalled(); + expect(umamiTrackEvent).toHaveBeenCalledWith("breaking_change_detected"); + + consoleSpy.mockRestore(); + }); + }); + + describe("no transformation scenarios", () => { + it("should return unchanged when invoiceNumber field is missing", () => { + const input = { + language: "en", + dateOfIssue: "2024-01-15", + seller: { name: "ACME Corp" }, + }; + + const result = handleInvoiceNumberBreakingChange(input); + + expect(result).toBe(input); + expect(umamiTrackEvent).not.toHaveBeenCalled(); + }); + + it("should return unchanged when language field is missing", () => { + const input = { + invoiceNumber: "INV-001", + dateOfIssue: "2024-01-15", + }; + + const result = handleInvoiceNumberBreakingChange(input); + + expect(result).toBe(input); + expect(umamiTrackEvent).not.toHaveBeenCalled(); + }); + + it("should return unchanged when invoiceNumber is not a string", () => { + const input = { + invoiceNumber: 123, + language: "en", + }; + + const result = handleInvoiceNumberBreakingChange(input); + + expect(result).toBe(input); + expect(umamiTrackEvent).not.toHaveBeenCalled(); + }); + + it("should transform even when invoiceNumber is empty string", () => { + const input = { + invoiceNumber: "", + language: "en", + }; + + const result = handleInvoiceNumberBreakingChange(input); + + expect(result).toEqual({ + language: "en", + invoiceNumberObject: { + label: `${TRANSLATIONS.en.invoiceNumber}:`, + value: "", + }, + }); + + expect(umamiTrackEvent).toHaveBeenCalledWith("breaking_change_detected"); + }); + + it("should return unchanged when input is null", () => { + const result = handleInvoiceNumberBreakingChange(null); + + expect(result).toBe(null); + expect(umamiTrackEvent).not.toHaveBeenCalled(); + }); + + it("should return unchanged when input is undefined", () => { + const result = handleInvoiceNumberBreakingChange(undefined); + + expect(result).toBe(undefined); + expect(umamiTrackEvent).not.toHaveBeenCalled(); + }); + + it("should return unchanged when input is not an object", () => { + const stringInput = "test"; + const numberInput = 42; + const booleanInput = true; + + expect(handleInvoiceNumberBreakingChange(stringInput)).toBe(stringInput); + expect(handleInvoiceNumberBreakingChange(numberInput)).toBe(numberInput); + expect(handleInvoiceNumberBreakingChange(booleanInput)).toBe( + booleanInput, + ); + + expect(umamiTrackEvent).not.toHaveBeenCalled(); + }); + + it("should return unchanged when input is an array", () => { + const arrayInput = [1, 2, 3]; + const result = handleInvoiceNumberBreakingChange(arrayInput); + + expect(result).toBe(arrayInput); + expect(umamiTrackEvent).not.toHaveBeenCalled(); + }); + }); + + describe("edge cases", () => { + it("should handle input with existing invoiceNumberObject field", () => { + const input = { + invoiceNumber: "OLD-001", + language: "en", + invoiceNumberObject: { + label: "Existing Label:", + value: "Existing Value", + }, + } as unknown as InvoiceData; + + const result = handleInvoiceNumberBreakingChange(input); + + // Should overwrite the existing invoiceNumberObject + expect(result).toEqual({ + language: "en", + invoiceNumberObject: { + label: `${TRANSLATIONS.en.invoiceNumber}:`, + value: "OLD-001", + }, + }); + + expect(umamiTrackEvent).toHaveBeenCalledWith("breaking_change_detected"); + }); + + it("should handle all supported languages correctly", () => { + SUPPORTED_LANGUAGES.forEach((lang) => { + const input = { + invoiceNumber: `INV-${lang}`, + language: lang, + }; + + const result = handleInvoiceNumberBreakingChange(input); + + expect(result).toEqual({ + language: lang, + invoiceNumberObject: { + label: `${TRANSLATIONS[lang].invoiceNumber}:`, + value: `INV-${lang}`, + }, + }); + }); + + // Should track one event per language + expect(umamiTrackEvent).toHaveBeenCalledTimes(SUPPORTED_LANGUAGES.length); + }); + + it("should handle special characters in invoiceNumber", () => { + const input = { + invoiceNumber: "INV/2024\\001-#@!", + language: "en", + }; + + const result = handleInvoiceNumberBreakingChange(input); + + expect((result as InvoiceData).invoiceNumberObject?.value).toBe( + "INV/2024\\001-#@!", + ); + }); + + it("should handle very long invoiceNumber", () => { + const longInvoiceNumber = "A".repeat(1000); + const input = { + invoiceNumber: longInvoiceNumber, + language: "en", + }; + + const result = handleInvoiceNumberBreakingChange(input); + + expect((result as InvoiceData).invoiceNumberObject?.value).toBe( + longInvoiceNumber, + ); + }); + }); + + describe("type safety", () => { + it("should maintain proper typing after transformation", () => { + const input = { + invoiceNumber: "INV-001", + language: "en" as const, + numericField: 42, + booleanField: true, + arrayField: [1, 2, 3], + objectField: { nested: "value" }, + }; + + const result = handleInvoiceNumberBreakingChange(input); + + expect(typeof result).toBe("object"); + expect(result).not.toBe(null); + + if (typeof result === "object" && result !== null) { + expect("invoiceNumberObject" in result).toBe(true); + expect("invoiceNumber" in result).toBe(false); + + if ("invoiceNumberObject" in result) { + const invoiceNumberObject = (result as unknown as InvoiceData) + .invoiceNumberObject; + + expect(typeof invoiceNumberObject?.label).toBe("string"); + expect(typeof invoiceNumberObject?.value).toBe("string"); + } + } + }); + }); +}); diff --git a/src/app/(app)/utils/invoice-number-breaking-change.ts b/src/app/(app)/utils/invoice-number-breaking-change.ts new file mode 100644 index 0000000..f957b53 --- /dev/null +++ b/src/app/(app)/utils/invoice-number-breaking-change.ts @@ -0,0 +1,66 @@ +import { SUPPORTED_LANGUAGES } from "@/app/schema"; +import { TRANSLATIONS } from "@/app/schema/translations"; + +import { umamiTrackEvent } from "@/lib/umami-analytics-track-event"; +import { z } from "zod"; + +/** + * This function handles the breaking change of the invoice number field. + * It removes the old "invoiceNumber" field and adds the new "invoiceNumberObject" field with label and value. + * + * @example + * ```typescript + * const json = { invoiceNumber: "123", language: "en" }; + * const updatedJson = handleInvoiceNumberBreakingChange(json); + * // Returns: { invoiceNumberObject: { label: "Invoice Number:", value: "123" } + * ``` + */ +export function handleInvoiceNumberBreakingChange(json: unknown) { + // check if the invoice number is in the json + if ( + typeof json === "object" && + json !== null && + "invoiceNumber" in json && + typeof json.invoiceNumber === "string" && + "language" in json + ) { + umamiTrackEvent("breaking_change_detected"); + + let lang: keyof typeof TRANSLATIONS; + + const invoiceLanguage = z + .enum(SUPPORTED_LANGUAGES) + .safeParse(json.language); + + if (!invoiceLanguage.success) { + console.error("Invalid invoice language:", invoiceLanguage.error); + + // fallback to default language + lang = SUPPORTED_LANGUAGES[0]; + } else { + lang = invoiceLanguage.data; + } + + const invoiceNumberLabel = TRANSLATIONS[lang].invoiceNumber; + + // Create new object without invoiceNumber and with invoiceNumberObject + const newJson = { + ...json, + // assign invoiceNumber to invoiceNumberObject.value + invoiceNumberObject: { + label: `${invoiceNumberLabel}:`, + value: json.invoiceNumber, + }, + }; + + // remove deprecated invoiceNumber from json + delete (newJson as Record).invoiceNumber; + + // update json + json = newJson; + + return json; + } + + return json; +} diff --git a/src/app/[locale]/about/components/language-switcher.tsx b/src/app/[locale]/about/components/language-switcher.tsx index bca14bf..58ec8d2 100644 --- a/src/app/[locale]/about/components/language-switcher.tsx +++ b/src/app/[locale]/about/components/language-switcher.tsx @@ -85,7 +85,7 @@ export function LanguageSwitcher({ startTransition(() => { const pathnameWithoutLocale = pathname.replace( `/${locale}`, - "" + "", ); router.push(pathnameWithoutLocale || "/", { diff --git a/src/app/[locale]/about/page.tsx b/src/app/[locale]/about/page.tsx index 440d2b2..5b0229b 100644 --- a/src/app/[locale]/about/page.tsx +++ b/src/app/[locale]/about/page.tsx @@ -373,7 +373,7 @@ function FeaturesSection() { {FEATURES_CARDS.map((feature) => { const title = t(`features.items.${feature.translationKey}.title`); const description = t( - `features.items.${feature.translationKey}.description` + `features.items.${feature.translationKey}.description`, ); return ( diff --git a/src/app/[locale]/error.tsx b/src/app/[locale]/error.tsx index 3aa21e5..0812a6e 100644 --- a/src/app/[locale]/error.tsx +++ b/src/app/[locale]/error.tsx @@ -21,7 +21,7 @@ export default function Error({ error, reset }: Props) { { closeButton: true, richColors: true, - } + }, ); }, [error]); diff --git a/src/app/api/generate-invoice/route.tsx b/src/app/api/generate-invoice/route.tsx index e3ece41..3f1d7d4 100644 --- a/src/app/api/generate-invoice/route.tsx +++ b/src/app/api/generate-invoice/route.tsx @@ -23,6 +23,7 @@ import { import { env } from "@/env"; import { ipLimiter } from "@/lib/rate-limit"; +import { compressInvoiceData } from "@/utils/url-compression"; export const dynamic = "force-dynamic"; @@ -53,18 +54,18 @@ export async function GET(req: NextRequest) { headers: { "Content-Type": "application/json", }, - } + }, ); } const GENERATED_ENGLISH_INVOICE_PDF_DOCUMENT = renderToBuffer( + />, ).catch((err) => { console.error( "\n\n_________________________Error during `renderToBuffer` for English invoice:", - err + err, ); throw err; @@ -73,11 +74,11 @@ export async function GET(req: NextRequest) { const GENERATED_POLISH_INVOICE_PDF_DOCUMENT = renderToBuffer( + />, ).catch((err) => { console.error( "\n\n_________________________Error during `renderToBuffer` for Polish invoice:", - err + err, ); throw err; @@ -96,7 +97,7 @@ export async function GET(req: NextRequest) { ]).catch((err) => { console.error( "\n\n_________________________Error during `Promise.allSettled`:", - err + err, ); })) || []; @@ -111,7 +112,7 @@ export async function GET(req: NextRequest) { } else if (invoice.status === "rejected") { console.error( "\n\n_________________________Error in generate-invoice route:", - invoice?.reason || "Unknown error" + invoice?.reason || "Unknown error", ); } } @@ -134,17 +135,21 @@ export async function GET(req: NextRequest) { if (!ATTACHMENTS.length) { return NextResponse.json( { error: "No attachments found" }, - { status: 400 } + { status: 400 }, ); } const newInvoiceDataValidated = invoiceSchema.parse( - ENGLISH_INVOICE_REAL_DATA + ENGLISH_INVOICE_REAL_DATA, ); - const stringified = JSON.stringify(newInvoiceDataValidated); - const compressedData = compressToEncodedURIComponent(stringified); - const invoiceUrl = `https://easyinvoicepdf.com/?data=${compressedData}`; + // Compress JSON keys before stringifying to reduce URL size + const compressedKeys = compressInvoiceData(newInvoiceDataValidated); + const compressedJson = JSON.stringify(compressedKeys); + + const compressedData = compressToEncodedURIComponent(compressedJson); + + const invoiceUrl = `https://easyinvoicepdf.com/?template=${newInvoiceDataValidated.template}&data=${compressedData}`; const monthAndYear = dayjs().format("MMMM YYYY"); @@ -173,27 +178,31 @@ export async function GET(req: NextRequest) { fileName: attachment.filename, fileContent: Buffer.from(attachment.content), folderId: folderToUploadInvoices.id, - }) + }), ); const uploadResults = await Promise.allSettled(uploadPromises); const failedUploads = uploadResults.filter( - (result): result is PromiseRejectedResult => result.status === "rejected" + (result): result is PromiseRejectedResult => result.status === "rejected", ); if (failedUploads.length > 0) { console.error( "Some files failed to upload to Google Drive:", - failedUploads + failedUploads, ); return NextResponse.json( { error: "Failed to upload invoices to Google Drive" }, - { status: 500 } + { status: 500 }, ); } - const companyEmailLink = `https://outlook.office.com/mail/deeplink/compose?to=${env.INVOICE_EMAIL_COMPANY_TO}&subject=Invoice%20for%20${monthAndYear}&body=Hello%2C%0A%0AInvoice%20for%20${monthAndYear}%20in%20attachments%0A%0AHave%20a%20nice%20day`; + const companyEmailLink = + `https://outlook.office.com/mail/deeplink/compose` + + `?to=${encodeURIComponent(env.INVOICE_EMAIL_COMPANY_TO)}` + + `&subject=${encodeURIComponent(`Invoice for ${monthAndYear}`)}` + + `&body=${encodeURIComponent(`Hello,\nThe invoice for ${monthAndYear} is in the attachment.\n\nHave a nice day.`)}`; // we only need the value of the invoice number e.g. 1/05.2025 const invoiceNumberValue = @@ -258,7 +267,7 @@ EasyInvoicePDF.com`, ]); const failedNotifications = notificationResults.filter( - (result): result is PromiseRejectedResult => result.status === "rejected" + (result): result is PromiseRejectedResult => result.status === "rejected", ); if (failedNotifications.length > 0) { @@ -269,14 +278,14 @@ EasyInvoicePDF.com`, return NextResponse.json( { message: "Invoice sent successfully" }, - { status: 200 } + { status: 200 }, ); } catch (error) { console.error("Error in generate-invoice route:", error); return NextResponse.json( { error: "Failed to generate and send invoice" }, - { status: 500 } + { status: 500 }, ); } } diff --git a/src/app/changelog/[slug]/page.tsx b/src/app/changelog/[slug]/page.tsx index db5b5b5..e2c480f 100644 --- a/src/app/changelog/[slug]/page.tsx +++ b/src/app/changelog/[slug]/page.tsx @@ -173,7 +173,7 @@ export default async function ChangelogEntryPage({ rel="noopener noreferrer" className="transition-all hover:scale-110" href={`https://twitter.com/intent/tweet?text=${encodeURIComponent( - `EasyInvoicePDF: ${entry.metadata.title || `Update ${formattedDate}`}` + `EasyInvoicePDF: ${entry.metadata.title || `Update ${formattedDate}`}`, )}&url=${encodeURIComponent(`${APP_URL}/changelog/${slug}`)}`} > { "src", "app", "changelog", - "content" + "content", ); const files = await readdir(changelogDir); return files.filter((file) => file.endsWith(".mdx")); @@ -69,7 +69,7 @@ async function importChangelogFile(filename: string) { if (error instanceof z.ZodError) { console.error( `Invalid metadata in changelog file ${filename}:`, - error.errors + error.errors, ); return null; } @@ -119,7 +119,7 @@ export async function getChangelogEntries(): Promise { // Sort by date (newest first) return entries.sort( (a, b) => - new Date(b.metadata.date).getTime() - new Date(a.metadata.date).getTime() + new Date(b.metadata.date).getTime() - new Date(a.metadata.date).getTime(), ); } @@ -127,7 +127,7 @@ export async function getChangelogEntries(): Promise { * Gets a specific changelog entry by slug */ export async function getChangelogEntry( - slug: string + slug: string, ): Promise { const files = await getChangelogFiles(); const filename = files.find((file) => filenameToSlug(file) === slug); @@ -172,11 +172,11 @@ export function formatChangelogDate(date: string): string { * Gets the next changelog entry after the current one (based on date order) */ export async function getNextChangelogEntry( - currentSlug: string + currentSlug: string, ): Promise { const allEntries = await getChangelogEntries(); const currentIndex = allEntries.findIndex( - (entry) => entry.slug === currentSlug + (entry) => entry.slug === currentSlug, ); // If current entry is not found or is the first one (newest), return null @@ -192,11 +192,11 @@ export async function getNextChangelogEntry( * Gets the previous changelog entry before the current one (based on date order) */ export async function getPreviousChangelogEntry( - currentSlug: string + currentSlug: string, ): Promise { const allEntries = await getChangelogEntries(); const currentIndex = allEntries.findIndex( - (entry) => entry.slug === currentSlug + (entry) => entry.slug === currentSlug, ); // If current entry is not found or is the last one (oldest), return null diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 2a484f8..fb60f24 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -2,7 +2,6 @@ import { DeviceContextProvider } from "@/contexts/device-context"; import { checkDeviceUserAgent } from "@/lib/check-device.server"; import { NextIntlClientProvider } from "next-intl"; // import { ReactScan } from "@/components/dev/react-scan"; -// import { DevToolbar } from "@/components/dev/stagewise-toolbar"; import { SpeedInsights } from "@vercel/speed-insights/next"; import type { Metadata, Viewport } from "next"; @@ -153,9 +152,6 @@ export default async function RootLayout({ {children} - {/* Stagewise toolbar for development */} - {/* */} - {/* https://sonner.emilkowal.ski/ */} {/* should only be enabled in production */} diff --git a/src/app/robots.ts b/src/app/robots.ts index 04f7118..d4b96cc 100644 --- a/src/app/robots.ts +++ b/src/app/robots.ts @@ -12,10 +12,14 @@ export default function robots(): MetadataRoute.Robots { "/", // Allow about pages in all languages ...SUPPORTED_LANGUAGES.map((locale) => `/${locale}/about`), + // Allow template parameter URLs + "/?template=*", ], disallow: [ // Disallow shared invoice URLs, like /?data=* - "/?data=*", + "/?*data=*", + "/?template=*&data=*", + "/?data=*&template=*", // Disallow subscription confirmation pages with and without tokens "/confirm-subscription", "/confirm-subscription?*", diff --git a/src/app/schema/index.ts b/src/app/schema/index.ts index ef293a0..50b9fe9 100644 --- a/src/app/schema/index.ts +++ b/src/app/schema/index.ts @@ -395,7 +395,7 @@ export const invoiceSchema = z.object({ }, "Logo must be a valid image (JPEG, PNG or WebP) in base64 format") .optional() .describe( - "Stripe template specific field. Logo must be a valid image (JPEG, PNG or WebP) in base64 format" + "Stripe template specific field. Logo must be a valid image (JPEG, PNG or WebP) in base64 format", ), /** @@ -447,7 +447,7 @@ export const invoiceSchema = z.object({ return val; }) .describe( - "Invoice date of service. Default is the last day of the current month" + "Invoice date of service. Default is the last day of the current month", ), invoiceType: z @@ -506,9 +506,9 @@ export const invoiceSchema = z.object({ .url("Please enter a valid URL or leave empty") .refine( (url) => url.startsWith("https://"), - "URL must start with https://" + "URL must start with https://", ), // Validate HTTPS URL format - ]) + ]), ) .optional() .describe("Stripe template specific field. URL field for payment link"), @@ -557,7 +557,7 @@ const uniqueCurrencies = new Set(SUPPORTED_CURRENCIES); if (uniqueCurrencies.size !== SUPPORTED_CURRENCIES.length) { const duplicates = SUPPORTED_CURRENCIES.filter( - (currency, index) => SUPPORTED_CURRENCIES.indexOf(currency) !== index + (currency, index) => SUPPORTED_CURRENCIES.indexOf(currency) !== index, ); const currencyFullNames = duplicates.map((currency) => { @@ -567,8 +567,6 @@ if (uniqueCurrencies.size !== SUPPORTED_CURRENCIES.length) { }); throw new Error( - `SUPPORTED_CURRENCIES contains duplicate entries: ${currencyFullNames.join( - ", " - )}` + `SUPPORTED_CURRENCIES contains duplicate entries: ${currencyFullNames.join(", ")}`, ); } diff --git a/src/app/schema/translations.ts b/src/app/schema/translations.ts index b93a009..9a5f676 100644 --- a/src/app/schema/translations.ts +++ b/src/app/schema/translations.ts @@ -119,7 +119,7 @@ export const translationSchema = z // ...etc // } const languageToSchemaMap = Object.fromEntries( - SUPPORTED_LANGUAGES.map((lang) => [lang, translationSchema]) + SUPPORTED_LANGUAGES.map((lang) => [lang, translationSchema]), ); // Schema for all translations export const translationsSchema = z.object(languageToSchemaMap); diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts index 0fb5340..245a77d 100644 --- a/src/app/sitemap.ts +++ b/src/app/sitemap.ts @@ -6,7 +6,7 @@ export default function sitemap(): MetadataRoute.Sitemap { const lastModified = new Date(); const languages = Object.fromEntries( - SUPPORTED_LANGUAGES.map((lang) => [lang, `${APP_URL}/${lang}/about`]) + SUPPORTED_LANGUAGES.map((lang) => [lang, `${APP_URL}/${lang}/about`]), ); const sitemapEntries: MetadataRoute.Sitemap = [ diff --git a/src/components/buyer-dialog.tsx b/src/components/buyer-dialog.tsx index f5c1ea1..2cc6aac 100644 --- a/src/components/buyer-dialog.tsx +++ b/src/components/buyer-dialog.tsx @@ -37,7 +37,7 @@ interface BuyerDialogProps { onClose: React.Dispatch>; handleBuyerAdd?: ( buyer: BuyerData, - { shouldApplyNewBuyerToInvoice }: { shouldApplyNewBuyerToInvoice: boolean } + { shouldApplyNewBuyerToInvoice }: { shouldApplyNewBuyerToInvoice: boolean }, ) => void; handleBuyerEdit?: (buyer: BuyerData) => void; initialData: BuyerData | null; @@ -96,7 +96,7 @@ export function BuyerDialog({ vatNoFieldIsVisible: true, notes: "", notesFieldIsVisible: true, - } + }, ); } }, [shouldApplyFormValues, formValues, initialData, isEditMode, form]); @@ -117,7 +117,7 @@ export function BuyerDialog({ if (!existingBuyersValidationResult.success) { console.error( "Invalid existing buyers data:", - existingBuyersValidationResult.error + existingBuyersValidationResult.error, ); // Show error toast @@ -135,7 +135,7 @@ export function BuyerDialog({ // Validate buyer data against existing buyers const isDuplicateName = existingBuyersValidationResult.data.some( (buyer: BuyerData) => - buyer.name === formValues.name && buyer.id !== formValues.id + buyer.name === formValues.name && buyer.id !== formValues.id, ); if (isDuplicateName) { diff --git a/src/components/buyer-management.tsx b/src/components/buyer-management.tsx index 7b139a8..b403d6d 100644 --- a/src/components/buyer-management.tsx +++ b/src/components/buyer-management.tsx @@ -46,7 +46,7 @@ export function BuyerManagement({ const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); const [buyersSelectOptions, setBuyersSelectOptions] = useState( - [] + [], ); // const [selectedBuyerId, setSelectedBuyerId] = useState(""); const [editingBuyer, setEditingBuyer] = useState(null); @@ -86,7 +86,7 @@ export function BuyerManagement({ // Update buyers when a new one is added const handleBuyerAdd = ( newBuyer: BuyerData, - { shouldApplyNewBuyerToInvoice }: { shouldApplyNewBuyerToInvoice: boolean } + { shouldApplyNewBuyerToInvoice }: { shouldApplyNewBuyerToInvoice: boolean }, ) => { try { const newBuyerWithId = { @@ -130,12 +130,12 @@ export function BuyerManagement({ const handleBuyerEdit = (editedBuyer: BuyerData) => { try { const updatedBuyers = buyersSelectOptions.map((buyer) => - buyer.id === editedBuyer.id ? editedBuyer : buyer + buyer.id === editedBuyer.id ? editedBuyer : buyer, ); localStorage.setItem( BUYERS_LOCAL_STORAGE_KEY, - JSON.stringify(updatedBuyers) + JSON.stringify(updatedBuyers), ); setBuyersSelectOptions(updatedBuyers); @@ -167,7 +167,7 @@ export function BuyerManagement({ if (id) { setSelectedBuyerId(id); const selectedBuyer = buyersSelectOptions.find( - (buyer) => buyer.id === id + (buyer) => buyer.id === id, ); if (selectedBuyer) { @@ -187,12 +187,12 @@ export function BuyerManagement({ try { setBuyersSelectOptions((prevBuyers) => { const updatedBuyers = prevBuyers.filter( - (buyer) => buyer.id !== selectedBuyerId + (buyer) => buyer.id !== selectedBuyerId, ); localStorage.setItem( BUYERS_LOCAL_STORAGE_KEY, - JSON.stringify(updatedBuyers) + JSON.stringify(updatedBuyers), ); return updatedBuyers; }); @@ -222,7 +222,7 @@ export function BuyerManagement({ }; const activeBuyer = buyersSelectOptions.find( - (buyer) => buyer.id === selectedBuyerId + (buyer) => buyer.id === selectedBuyerId, ); return ( @@ -240,7 +240,7 @@ export function BuyerManagement({ id={buyerSelectId} className={cn( "block h-8 max-w-[200px] text-[12px]", - !selectedBuyerId && "italic text-gray-700" + !selectedBuyerId && "italic text-gray-700", )} onChange={handleBuyerChange} value={selectedBuyerId} diff --git a/src/components/dev/stagewise-toolbar.tsx b/src/components/dev/stagewise-toolbar.tsx deleted file mode 100644 index ab57629..0000000 --- a/src/components/dev/stagewise-toolbar.tsx +++ /dev/null @@ -1,15 +0,0 @@ -"use client"; - -import { StagewiseToolbar } from "@stagewise/toolbar-next"; - -const stagewiseConfig = { - plugins: [], -}; - -export function DevToolbar() { - if (process.env.NODE_ENV !== "development") { - return null; - } - - return ; -} diff --git a/src/components/etc/github-logo.tsx b/src/components/etc/github-logo.tsx index 1bef374..e8b7727 100644 --- a/src/components/etc/github-logo.tsx +++ b/src/components/etc/github-logo.tsx @@ -8,7 +8,7 @@ export function GithubIcon({ className }: { className?: string }) { xmlns="http://www.w3.org/2000/svg" className={cn( "h-5 w-5 transition-all group-hover:fill-blue-600", - className + className, )} > View on GitHub diff --git a/src/components/github-star-cta.tsx b/src/components/github-star-cta.tsx new file mode 100644 index 0000000..677d9c3 --- /dev/null +++ b/src/components/github-star-cta.tsx @@ -0,0 +1,40 @@ +"use client"; + +import { Button } from "@/components/ui/button"; +import { GITHUB_URL } from "@/config"; +import { umamiTrackEvent } from "@/lib/umami-analytics-track-event"; +import { cn } from "@/lib/utils"; +import { Star } from "lucide-react"; +import Link from "next/link"; + +export function GitHubStarCTA() { + const handleStarClick = () => { + umamiTrackEvent("github_star_cta_clicked"); + }; + + return ( +
    + +
    + ); +} diff --git a/src/components/go-to-app-button-cta.tsx b/src/components/go-to-app-button-cta.tsx index d722181..c26a88a 100644 --- a/src/components/go-to-app-button-cta.tsx +++ b/src/components/go-to-app-button-cta.tsx @@ -16,7 +16,7 @@ export function GoToAppButton({ _variant="outline" className={cn( "group relative overflow-hidden border-slate-200 px-8 shadow-sm transition-all duration-300 hover:border-slate-200/80 hover:shadow-lg", - className + className, )} asChild > @@ -39,7 +39,7 @@ export function BlackGoToAppButton({ {children} diff --git a/src/components/seller-dialog.tsx b/src/components/seller-dialog.tsx index cc396c5..c5386a8 100644 --- a/src/components/seller-dialog.tsx +++ b/src/components/seller-dialog.tsx @@ -39,7 +39,7 @@ interface SellerDialogProps { seller: SellerData, { shouldApplyNewSellerToInvoice, - }: { shouldApplyNewSellerToInvoice: boolean } + }: { shouldApplyNewSellerToInvoice: boolean }, ) => void; handleSellerEdit?: (seller: SellerData) => void; initialData: SellerData | null; @@ -108,7 +108,7 @@ export function SellerDialog({ swiftBicFieldIsVisible: true, notes: "", notesFieldIsVisible: true, - } + }, ); } }, [shouldApplyFormValues, formValues, initialData, isEditMode, form]); @@ -129,7 +129,7 @@ export function SellerDialog({ if (!existingSellersValidationResult.success) { console.error( "Invalid existing sellers data:", - existingSellersValidationResult.error + existingSellersValidationResult.error, ); // Show error toast @@ -149,7 +149,7 @@ export function SellerDialog({ // Validate seller data against existing sellers const isDuplicateName = existingSellersValidationResult.data.some( (seller: SellerData) => - seller.name === formValues.name && seller.id !== formValues.id + seller.name === formValues.name && seller.id !== formValues.id, ); if (isDuplicateName) { diff --git a/src/components/seller-management.tsx b/src/components/seller-management.tsx index 5e7c9d5..dcecc6a 100644 --- a/src/components/seller-management.tsx +++ b/src/components/seller-management.tsx @@ -82,7 +82,7 @@ export function SellerManagement({ const selectedSeller = validationResult.data.find( (seller: SellerData) => { return seller?.id === invoiceData?.seller?.id; - } + }, ); setSellersSelectOptions(validationResult.data); @@ -99,7 +99,7 @@ export function SellerManagement({ newSeller: SellerData, { shouldApplyNewSellerToInvoice, - }: { shouldApplyNewSellerToInvoice: boolean } + }: { shouldApplyNewSellerToInvoice: boolean }, ) => { try { const newSellerWithId = { @@ -113,7 +113,7 @@ export function SellerManagement({ // Save to localStorage localStorage.setItem( SELLERS_LOCAL_STORAGE_KEY, - JSON.stringify(newSellers) + JSON.stringify(newSellers), ); // Update the sellers state @@ -146,12 +146,12 @@ export function SellerManagement({ const handleSellerEdit = (editedSeller: SellerData) => { try { const updatedSellers = sellersSelectOptions.map((seller) => - seller.id === editedSeller.id ? editedSeller : seller + seller.id === editedSeller.id ? editedSeller : seller, ); localStorage.setItem( SELLERS_LOCAL_STORAGE_KEY, - JSON.stringify(updatedSellers) + JSON.stringify(updatedSellers), ); setSellersSelectOptions(updatedSellers); @@ -183,7 +183,7 @@ export function SellerManagement({ if (id) { setSelectedSellerId(id); const selectedSeller = sellersSelectOptions.find( - (seller) => seller.id === id + (seller) => seller.id === id, ); if (selectedSeller) { @@ -203,12 +203,12 @@ export function SellerManagement({ try { setSellersSelectOptions((prevSellers) => { const updatedSellers = prevSellers.filter( - (seller) => seller.id !== selectedSellerId + (seller) => seller.id !== selectedSellerId, ); localStorage.setItem( SELLERS_LOCAL_STORAGE_KEY, - JSON.stringify(updatedSellers) + JSON.stringify(updatedSellers), ); return updatedSellers; }); @@ -238,7 +238,7 @@ export function SellerManagement({ }; const activeSeller = sellersSelectOptions.find( - (seller) => seller.id === selectedSellerId + (seller) => seller.id === selectedSellerId, ); return ( @@ -256,7 +256,7 @@ export function SellerManagement({ id={sellerSelectId} className={cn( "block h-8 max-w-[200px] text-[12px]", - !selectedSellerId && "italic text-gray-700" + !selectedSellerId && "italic text-gray-700", )} onChange={handleSellerChange} value={selectedSellerId} diff --git a/src/components/subscribe-input.tsx b/src/components/subscribe-input.tsx index 3a52c27..2f3d4e1 100644 --- a/src/components/subscribe-input.tsx +++ b/src/components/subscribe-input.tsx @@ -26,7 +26,7 @@ function SubmitButton({ className={cn( "absolute right-2 top-1.5 transition-all duration-200", "hover:opacity-90 active:scale-95", - pending && "cursor-not-allowed opacity-80" + pending && "cursor-not-allowed opacity-80", )} disabled={pending} > @@ -64,7 +64,7 @@ export function SubscribeInput({ className={cn( "flex h-12 items-center justify-between", "rounded-lg border bg-emerald-50 px-4 py-2", - "duration-300 animate-in fade-in-0 slide-in-from-top-1" + "duration-300 animate-in fade-in-0 slide-in-from-top-1", )} >

    @@ -111,7 +111,7 @@ export function SubscribeInput({ "placeholder:text-muted-foreground/60", "transition-all duration-200", "focus:ring-primary/20 focus:ring-2 focus:ring-offset-0", - "hover:border-primary/50" + "hover:border-primary/50", )} required /> diff --git a/src/components/ui/accordion.tsx b/src/components/ui/accordion.tsx index cd5e887..44935d9 100644 --- a/src/components/ui/accordion.tsx +++ b/src/components/ui/accordion.tsx @@ -30,7 +30,7 @@ const AccordionTrigger = React.forwardRef< ref={ref} className={cn( "flex flex-1 items-center justify-between py-4 font-medium transition-all [&[data-state=open]>svg]:rotate-180", - className + className, )} {...props} > diff --git a/src/components/ui/alert-dialog.tsx b/src/components/ui/alert-dialog.tsx index 223c378..872dd3b 100644 --- a/src/components/ui/alert-dialog.tsx +++ b/src/components/ui/alert-dialog.tsx @@ -19,7 +19,7 @@ const AlertDialogOverlay = React.forwardRef< @@ -52,7 +52,7 @@ const AlertDialogHeader = ({

    @@ -66,7 +66,7 @@ const AlertDialogFooter = ({
    @@ -119,7 +119,7 @@ const AlertDialogCancel = React.forwardRef< className={cn( buttonVariants({ _variant: "outline" }), "mt-2 sm:mt-0", - className + className, )} {...props} /> diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx index 3bfbad9..3aa69f7 100644 --- a/src/components/ui/badge.tsx +++ b/src/components/ui/badge.tsx @@ -20,7 +20,7 @@ const badgeVariants = cva( defaultVariants: { variant: "default", }, - } + }, ); export interface BadgeProps diff --git a/src/components/ui/button-helper.tsx b/src/components/ui/button-helper.tsx index c280670..487f38f 100644 --- a/src/components/ui/button-helper.tsx +++ b/src/components/ui/button-helper.tsx @@ -14,7 +14,7 @@ export const ButtonHelper = ({ _size="sm" className={cn( "h-5 max-w-full whitespace-normal text-pretty p-0 text-left underline", - className + className, )} {...props} > diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index 04644fd..d29c2b7 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -5,7 +5,7 @@ import * as React from "react"; import { cn } from "@/lib/utils"; const buttonVariants = cva( - "inline-flex items-center justify-center whitespace-nowrap rounded-lg text-sm font-medium transition-colors outline-offset-2 focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring/70 disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed [&_svg]:pointer-events-none [&_svg]:shrink-0 [&[data-disabled=true]]:opacity-50", + "inline-flex items-center justify-center whitespace-nowrap rounded-lg text-sm font-medium transition-colors outline-offset-2 focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring/70 disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed [&_svg]:pointer-events-none [&_svg]:shrink-0 [&[data-disabled=true]]:opacity-50 active:scale-[98%] active:transition-transform", { variants: { _variant: { @@ -32,7 +32,7 @@ const buttonVariants = cva( _variant: "default", _size: "default", }, - } + }, ); export interface ButtonProps @@ -47,7 +47,7 @@ export interface ButtonProps const Button = React.forwardRef( ( { className, _variant, _size, asChild = false, type = "button", ...props }, - ref + ref, ) => { const Comp = asChild ? Slot : "button"; return ( @@ -58,7 +58,7 @@ const Button = React.forwardRef( {...props} /> ); - } + }, ); Button.displayName = "Button"; diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx index 6cac29a..d7955a1 100644 --- a/src/components/ui/command.tsx +++ b/src/components/ui/command.tsx @@ -16,7 +16,7 @@ const Command = React.forwardRef< ref={ref} className={cn( "flex h-full w-full flex-col overflow-hidden rounded-md bg-white text-slate-950 dark:bg-slate-950 dark:text-slate-50", - className + className, )} {...props} /> @@ -45,7 +45,7 @@ const CommandInput = React.forwardRef< ref={ref} className={cn( "flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-slate-500 disabled:cursor-not-allowed disabled:opacity-50 dark:placeholder:text-slate-400", - className + className, )} {...props} /> @@ -88,7 +88,7 @@ const CommandGroup = React.forwardRef< ref={ref} className={cn( "overflow-hidden p-1 text-slate-950 dark:text-slate-50 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-slate-500 dark:[&_[cmdk-group-heading]]:text-slate-400", - className + className, )} {...props} /> @@ -116,7 +116,7 @@ const CommandItem = React.forwardRef< ref={ref} className={cn( "relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-slate-100 data-[selected=true]:text-slate-900 data-[disabled=true]:opacity-50 dark:data-[selected='true']:bg-slate-800 dark:data-[selected=true]:text-slate-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", - className + className, )} {...props} /> @@ -132,7 +132,7 @@ const CommandShortcut = ({ diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx index 4205ad7..e5b9ccb 100644 --- a/src/components/ui/dialog.tsx +++ b/src/components/ui/dialog.tsx @@ -22,7 +22,7 @@ const DialogOverlay = React.forwardRef< ref={ref} className={cn( "fixed inset-0 z-[101] bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", - className + className, )} {...props} /> @@ -39,7 +39,7 @@ const DialogContent = React.forwardRef< ref={ref} className={cn( "fixed left-1/2 top-1/2 z-[101] grid max-h-[calc(100%-4rem)] w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 overflow-y-auto rounded-xl border border-slate-200 bg-white p-6 shadow-lg shadow-black/5 duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] dark:border-slate-800 dark:bg-slate-950 sm:max-w-[400px] sm:rounded-xl", - className + className, )} {...props} > @@ -64,7 +64,7 @@ const DialogHeader = ({
    @@ -78,7 +78,7 @@ const DialogFooter = ({
    diff --git a/src/components/ui/disclosure.tsx b/src/components/ui/disclosure.tsx index 6cd5a2c..0462281 100644 --- a/src/components/ui/disclosure.tsx +++ b/src/components/ui/disclosure.tsx @@ -35,7 +35,7 @@ const DisclosureGroup = React.forwardRef( {...props} className={composeTailwindRenderProps( className, - "peer cursor-pointer disabled:cursor-not-allowed disabled:opacity-75" + "peer cursor-pointer disabled:cursor-not-allowed disabled:opacity-75", )} > {(values) => ( @@ -45,7 +45,7 @@ const DisclosureGroup = React.forwardRef( )} ); - } + }, ); DisclosureGroup.displayName = "DisclosureGroup"; @@ -61,13 +61,13 @@ const Disclosure = React.forwardRef( {...props} className={composeTailwindRenderProps( className, - "w-full min-w-60 border-b disabled:opacity-60" + "w-full min-w-60 border-b disabled:opacity-60", )} > {children} ); - } + }, ); Disclosure.displayName = "Disclosure"; @@ -83,7 +83,7 @@ const DisclosureTrigger = React.forwardRef( slot="trigger" className={composeTailwindRenderProps( className, - "flex w-full items-center justify-between gap-x-2 py-3 text-left text-base font-medium disabled:cursor-default disabled:opacity-50 forced-colors:disabled:text-[GrayText] [&[aria-expanded=true]_[data-slot=disclosure-chevron]]:rotate-180" + "flex w-full items-center justify-between gap-x-2 py-3 text-left text-base font-medium disabled:cursor-default disabled:opacity-50 forced-colors:disabled:text-[GrayText] [&[aria-expanded=true]_[data-slot=disclosure-chevron]]:rotate-180", )} {...props} > @@ -104,7 +104,7 @@ const DisclosureTrigger = React.forwardRef( ); - } + }, ); DisclosureTrigger.displayName = "DisclosureTrigger"; @@ -121,7 +121,7 @@ const DisclosurePanel = React.forwardRef< data-slot="disclosure-panel" className={composeTailwindRenderProps( className, - "cursor-text overflow-hidden text-sm text-slate-600 transition-all duration-200 ease-in-out" + "cursor-text overflow-hidden text-sm text-slate-600 transition-all duration-200 ease-in-out", )} {...props} > @@ -138,10 +138,10 @@ DisclosurePanel.displayName = "DisclosurePanel"; function composeTailwindRenderProps( className: string | ((v: T) => string) | undefined, - tailwind: string + tailwind: string, ): string | ((v: T) => string) { return composeRenderProps(className, (className) => - twMerge(tailwind, className) + twMerge(tailwind, className), ); } diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx index a5ef08e..ea9f6da 100644 --- a/src/components/ui/dropdown-menu.tsx +++ b/src/components/ui/dropdown-menu.tsx @@ -55,7 +55,7 @@ function DropdownMenuContent({ isCloseFromMouse.current = true; onPointerDown?.(e); }, - [onPointerDown] + [onPointerDown], ); const handlePointerDownOutside = React.useCallback( @@ -63,7 +63,7 @@ function DropdownMenuContent({ isCloseFromMouse.current = true; onPointerDownOutside?.(e); }, - [onPointerDownOutside] + [onPointerDownOutside], ); const handleCloseAutoFocus = React.useCallback( @@ -79,7 +79,7 @@ function DropdownMenuContent({ e.preventDefault(); isCloseFromMouse.current = false; }, - [onCloseAutoFocus] + [onCloseAutoFocus], ); return ( @@ -89,7 +89,7 @@ function DropdownMenuContent({ sideOffset={sideOffset} className={cn( "z-50 min-w-40 overflow-hidden rounded-md border border-slate-200 bg-white p-1 text-slate-950 shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-slate-800 dark:bg-slate-950 dark:text-slate-50", - className + className, )} onPointerDown={handlePointerDown} onPointerDownOutside={handlePointerDownOutside} @@ -124,7 +124,7 @@ function DropdownMenuItem({ data-variant={variant} className={cn( "data-[variant=destructive]:*:[svg]:!text-destructive-foreground outline-hidden relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm hover:bg-slate-100 hover:text-slate-900 data-[disabled]:pointer-events-none data-[highlighted]:bg-slate-100 data-[variant=destructive]:data-[highlighted]:bg-red-500/10 data-[inset]:pl-8 data-[highlighted]:text-slate-900 data-[variant=destructive]:data-[highlighted]:text-slate-50 data-[variant=destructive]:text-slate-50 data-[disabled]:opacity-50 data-[highlighted]:outline-none data-[variant=destructive]:hover:bg-red-500/10 data-[variant=destructive]:hover:text-slate-50 dark:hover:bg-slate-800 dark:hover:text-slate-50 dark:data-[highlighted]:bg-slate-800 dark:data-[variant=destructive]:data-[highlighted]:bg-red-900/10 dark:data-[highlighted]:text-slate-50 dark:data-[variant=destructive]:data-[highlighted]:text-slate-50 dark:data-[variant=destructive]:text-slate-50 dark:data-[variant=destructive]:hover:bg-red-900/10 dark:data-[variant=destructive]:hover:text-slate-50 [&_svg]:pointer-events-none [&_svg]:shrink-0", - className + className, )} {...props} /> @@ -142,7 +142,7 @@ function DropdownMenuCheckboxItem({ data-slot="dropdown-menu-checkbox-item" className={cn( "outline-hidden relative flex cursor-default select-none items-center gap-2 rounded-sm py-1.5 pl-8 pr-2 text-sm focus:bg-slate-100 focus:text-slate-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-slate-800 dark:focus:text-slate-50 [&_svg]:pointer-events-none [&_svg]:shrink-0", - className + className, )} checked={checked} {...props} @@ -178,7 +178,7 @@ function DropdownMenuRadioItem({ data-slot="dropdown-menu-radio-item" className={cn( "outline-hidden relative flex cursor-default select-none items-center gap-2 rounded-sm py-1.5 pl-8 pr-2 text-sm focus:bg-slate-100 focus:text-slate-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-slate-800 dark:focus:text-slate-50 [&_svg]:pointer-events-none [&_svg]:shrink-0", - className + className, )} {...props} > @@ -205,7 +205,7 @@ function DropdownMenuLabel({ data-inset={inset} className={cn( "px-2 py-1.5 text-xs font-medium text-slate-500 data-[inset]:pl-8 dark:text-slate-400", - className + className, )} {...props} /> @@ -221,7 +221,7 @@ function DropdownMenuSeparator({ data-slot="dropdown-menu-separator" className={cn( "-mx-1 my-1 h-px bg-slate-200 dark:bg-slate-800", - className + className, )} {...props} /> @@ -237,7 +237,7 @@ function DropdownMenuShortcut({ data-slot="dropdown-menu-shortcut" className={cn( "-me-1 ms-auto inline-flex h-5 max-h-full items-center rounded border border-slate-200 bg-white px-1 font-[inherit] text-[0.625rem] font-medium text-slate-500/70 dark:border-slate-800 dark:bg-slate-950 dark:text-slate-400/70", - className + className, )} {...props} /> @@ -264,7 +264,7 @@ function DropdownMenuSubTrigger({ data-inset={inset} className={cn( "outline-hidden flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm focus:bg-slate-100 focus:text-slate-900 data-[state=open]:bg-slate-100 data-[inset]:pl-8 data-[state=open]:text-slate-900 dark:focus:bg-slate-800 dark:focus:text-slate-50 dark:data-[state=open]:bg-slate-800 dark:data-[state=open]:text-slate-50", - className + className, )} {...props} > @@ -286,7 +286,7 @@ function DropdownMenuSubContent({ data-slot="dropdown-menu-sub-content" className={cn( "z-50 min-w-40 overflow-hidden rounded-md border border-slate-200 bg-white p-1 text-slate-950 shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-slate-800 dark:bg-slate-950 dark:text-slate-50", - className + className, )} {...props} /> diff --git a/src/components/ui/form.tsx b/src/components/ui/form.tsx index 54b7f4c..6c68d77 100644 --- a/src/components/ui/form.tsx +++ b/src/components/ui/form.tsx @@ -25,7 +25,7 @@ type FormFieldContextValue< }; const FormFieldContext = React.createContext( - {} as FormFieldContextValue + {} as FormFieldContextValue, ); const FormField = < @@ -69,7 +69,7 @@ type FormItemContextValue = { }; const FormItemContext = React.createContext( - {} as FormItemContextValue + {} as FormItemContextValue, ); const FormItem = React.forwardRef< @@ -160,7 +160,7 @@ const FormMessage = React.forwardRef< id={formMessageId} className={cn( "text-[13px] font-medium text-red-500 dark:text-red-900", - className + className, )} {...props} > diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx index cce4637..39e27fa 100644 --- a/src/components/ui/input.tsx +++ b/src/components/ui/input.tsx @@ -15,14 +15,14 @@ const Input = React.memo( "[&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-decoration]:appearance-none [&::-webkit-search-results-button]:appearance-none [&::-webkit-search-results-decoration]:appearance-none", type === "file" && "p-0 pr-3 italic text-slate-500/70 file:me-3 file:h-full file:border-0 file:border-r file:border-solid file:border-slate-200 file:bg-transparent file:px-3 file:text-sm file:font-medium file:not-italic file:text-slate-950 dark:text-slate-400/70 dark:file:border-slate-800 dark:file:text-slate-50", - className + className, )} ref={ref} {...props} /> ); - } - ) + }, + ), ); Input.displayName = "Input"; diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx index fedbaaf..a984897 100644 --- a/src/components/ui/label.tsx +++ b/src/components/ui/label.tsx @@ -13,11 +13,11 @@ const Label = React.memo( ref={ref} className={cn( "block text-balance text-xs font-medium text-gray-900 peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:text-slate-50", - className + className, )} {...props} /> - )) + )), ); Label.displayName = "Label"; diff --git a/src/components/ui/money-input.tsx b/src/components/ui/money-input.tsx index 7e16641..9ff6b12 100644 --- a/src/components/ui/money-input.tsx +++ b/src/components/ui/money-input.tsx @@ -54,7 +54,7 @@ const MoneyInput = React.memo( className={cn( "-me-px rounded-e-none ps-6 shadow-none", getCurrencyPadding(currencySymbol), - props.className + props.className, )} placeholder="0.00" /> @@ -64,7 +64,7 @@ const MoneyInput = React.memo(
    ); - }) + }), ); MoneyInput.displayName = "MoneyInput"; @@ -97,7 +97,7 @@ const ReadOnlyMoneyInput = React.memo( "-me-px block w-full cursor-not-allowed rounded-md rounded-e-none border border-gray-300 bg-gray-100 px-3 py-2 ps-6", getCurrencyPadding(currencySymbol), "focus-visible:border-indigo-500 focus-visible:ring focus-visible:ring-indigo-200 focus-visible:ring-opacity-50", - props.className + props.className, )} placeholder="0.00" type="text" @@ -110,7 +110,7 @@ const ReadOnlyMoneyInput = React.memo(
    ); - }) + }), ); ReadOnlyMoneyInput.displayName = "ReadOnlyMoneyInput"; diff --git a/src/components/ui/multi-select.tsx b/src/components/ui/multi-select.tsx index e5f5024..ce26fa4 100644 --- a/src/components/ui/multi-select.tsx +++ b/src/components/ui/multi-select.tsx @@ -47,7 +47,7 @@ const multiSelectVariants = cva( defaultVariants: { variant: "default", }, - } + }, ); /** @@ -153,12 +153,12 @@ export const MultiSelect = React.forwardRef< handleDownload, ...props }, - ref + ref, ) => { const [isPopoverOpen, setIsPopoverOpen] = React.useState(false); const handleInputKeyDown = ( - event: React.KeyboardEvent + event: React.KeyboardEvent, ) => { if (event.key === "Enter") { setIsPopoverOpen(true); @@ -222,7 +222,7 @@ export const MultiSelect = React.forwardRef< selectedLanguages.length === 1 && "lg:w-[200px]", selectedLanguages.length === 2 && "lg:w-[240px]", selectedLanguages.length >= 3 && "lg:w-[280px]", - className + className, )} >
    @@ -249,7 +249,7 @@ export const MultiSelect = React.forwardRef< {`+ ${selectedLanguages.length - maxCount} more`} @@ -276,7 +276,7 @@ export const MultiSelect = React.forwardRef<
    ); - } + }, ); SelectNative.displayName = "SelectNative"; diff --git a/src/components/ui/separator.tsx b/src/components/ui/separator.tsx index ff5e25f..d52497a 100644 --- a/src/components/ui/separator.tsx +++ b/src/components/ui/separator.tsx @@ -11,7 +11,7 @@ const Separator = React.forwardRef< >( ( { className, orientation = "horizontal", decorative = true, ...props }, - ref + ref, ) => ( - ) + ), ); Separator.displayName = SeparatorPrimitive.Root.displayName; diff --git a/src/components/ui/switch.tsx b/src/components/ui/switch.tsx index c850c1c..432b8fc 100644 --- a/src/components/ui/switch.tsx +++ b/src/components/ui/switch.tsx @@ -16,18 +16,18 @@ const Switch = React.memo( - )) + )), ); Switch.displayName = SwitchPrimitives.Root.displayName; diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx index 82628fd..23245d7 100644 --- a/src/components/ui/tabs.tsx +++ b/src/components/ui/tabs.tsx @@ -27,7 +27,7 @@ function TabsList({ data-slot="tabs-list" className={cn( "inline-flex w-fit items-center justify-center gap-2 rounded-md bg-slate-200 p-1 text-slate-700 dark:bg-slate-800 dark:text-slate-300", - className + className, )} {...props} /> @@ -43,7 +43,7 @@ function TabsTrigger({ data-slot="tabs-trigger" className={cn( "inline-flex items-center justify-center whitespace-nowrap rounded-[4px] px-3 py-1.5 text-sm font-medium ring-offset-white transition-all hover:bg-slate-100 hover:text-slate-900 focus-visible:border-indigo-500 focus-visible:ring focus-visible:ring-indigo-200 focus-visible:ring-opacity-50 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-white data-[state=active]:text-slate-950 data-[state=active]:shadow-sm dark:ring-offset-slate-950 dark:hover:bg-slate-800 dark:hover:text-slate-50 dark:focus-visible:ring-slate-300 dark:data-[state=active]:bg-slate-950 dark:data-[state=active]:text-slate-50", - className + className, )} {...props} /> diff --git a/src/components/ui/textarea.tsx b/src/components/ui/textarea.tsx index 28ddc21..fe4052e 100644 --- a/src/components/ui/textarea.tsx +++ b/src/components/ui/textarea.tsx @@ -10,7 +10,7 @@ const Textarea = React.forwardRef<