diff --git a/frontend/package.json b/frontend/package.json index 2599d89a..230344e4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -20,7 +20,7 @@ "@tanstack/react-query": "^5.101.0", "@tanstack/react-virtual": "^3.14.9", "lucide-react": "^0.575.0", - "next": "16.3.8", + "next": "16.4.3", "next-themes": "^0.4.6", "react": "19.2.7", "react-dom": "19.2.7", diff --git a/frontend/src/__tests__/activity-content.test.tsx b/frontend/src/__tests__/activity-content.test.tsx new file mode 100644 index 00000000..218ca416 --- /dev/null +++ b/frontend/src/__tests__/activity-content.test.tsx @@ -0,0 +1,377 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { render, screen, waitFor } from "@testing-library/react"; +import React from "react"; + +// ─── Mocks ────────────────────────────────────────────────────────────────── +// ActivityContent fetches from /v1/events endpoint and handles various +// error states including unauthorized, timeout, and malformed responses. + +vi.mock("next/navigation", () => ({ + useRouter: vi.fn(() => ({ push: vi.fn() })), + useSearchParams: vi.fn(() => new URLSearchParams()), +})); + +vi.mock("@/context/wallet-context", () => { + const session = { + publicKey: "GABCDEFPUBLICKEY000000000000000000000000000000000000000", + }; + return { + useWallet: () => { + const connected = localStorage.getItem("session") !== null; + return { + session: connected ? session : null, + status: connected ? "connected" : "disconnected", + disconnect: vi.fn(), + isHydrated: true, + }; + }, + }; +}); + +vi.mock("@/lib/wallet", () => ({ + shortenPublicKey: (key: string) => `${key.slice(0, 4)}...${key.slice(-4)}`, + formatNetwork: (n: string) => n, + STELLAR_NETWORK: "TESTNET", +})); + +vi.mock("@/lib/api/_shared", () => ({ + getApiBaseUrl: () => "http://localhost:4000", +})); + +vi.mock("@/lib/logger", () => ({ + logger: { warn: vi.fn(), error: vi.fn(), info: vi.fn() }, +})); + +// Every icon used by the rendered tree (ActivityHistory, TransactionTracker and +// ActivityContent) must be stubbed: an undefined component type makes React +// unmount the tree, so the activity list would render as an empty container. +vi.mock("lucide-react", () => ({ + Loader2: () => null, + Download: () => null, + Clock: () => null, + ExternalLink: () => null, + CheckCircle: () => null, + XCircle: () => null, + RefreshCw: () => null, + Ban: () => null, + FileSearch: () => null, +})); + +vi.mock("@/components/ui/Button", () => ({ + Button: ({ + children, + onClick, + disabled, + ...rest + }: React.ButtonHTMLAttributes & { + glow?: boolean; + variant?: string; + size?: string; + children?: React.ReactNode; + }) => ( + + ), +})); + +// ─── Activity Content Component ──────────────────────────────────────────── +import ActivityContent from "@/app/activity/activity-content"; + +describe("ActivityContent API failure states", () => { + beforeEach(() => { + // Clear any session to test unauthorized state + vi.stubGlobal("localStorage", { + getItem: vi.fn(() => null), + setItem: vi.fn(), + clear: vi.fn(), + removeItem: vi.fn(), + key: vi.fn(() => null), + length: 0, + }); + }); + + it("shows access denied when wallet is not connected", () => { + const { unmount } = render(); + + // Should show access denied state + const accessDenied = screen.getByText(/Access Denied/); + expect(accessDenied).toBeInTheDocument(); + + const pleaseConnect = screen.getByText(/Please connect your wallet/); + expect(pleaseConnect).toBeInTheDocument(); + + unmount(); + }); + + it("handles unauthorized API response (401)", async () => { + // Mock connected session but unauthorized response + vi.stubGlobal("localStorage", { + getItem: vi.fn(() => "connected-token"), + setItem: vi.fn(), + clear: vi.fn(), + removeItem: vi.fn(), + key: vi.fn(() => null), + length: 0, + }); + + // Mock fetch to return 401 + vi.stubGlobal("fetch", vi.fn(() => + Promise.resolve({ + ok: false, + status: 401, + statusText: "Unauthorized", + json: async () => ({ error: "Unauthorized" }), + } as Response), + )); + + const { unmount } = render(); + + // Should show error state after failed fetch + await waitFor(() => + expect(screen.getByText(/failed to fetch activity/i)).toBeInTheDocument(), + ); + + const retryButton = screen.getByRole("button", { name: /retry/i }); + expect(retryButton).toBeInTheDocument(); + + unmount(); + }); + + it("handles timeout API response", async () => { + vi.stubGlobal("localStorage", { + getItem: vi.fn(() => "connected-token"), + setItem: vi.fn(), + clear: vi.fn(), + removeItem: vi.fn(), + key: vi.fn(() => null), + length: 0, + }); + + // Mock fetch to timeout + vi.stubGlobal("fetch", vi.fn(() => Promise.reject(new Error("Request timed out")))); + + const { unmount } = render(); + + // Should handle timeout gracefully + await waitFor(() => + expect(screen.getByText(/failed to fetch activity/i)).toBeInTheDocument(), + ); + + unmount(); + }); + + it("handles malformed API response (invalid JSON)", async () => { + vi.stubGlobal("localStorage", { + getItem: vi.fn(() => "connected-token"), + setItem: vi.fn(), + clear: vi.fn(), + removeItem: vi.fn(), + key: vi.fn(() => null), + length: 0, + }); + + // Mock fetch to return non-ok response with malformed JSON + vi.stubGlobal("fetch", vi.fn(() => + Promise.resolve({ + ok: false, + status: 500, + statusText: "Internal Server Error", + // Malformed JSON that can't be parsed + json: async () => { + throw new Error("Unexpected end of JSON input"); + }, + }), + )); + + const { unmount } = render(); + + // Should handle malformed JSON gracefully + await waitFor(() => + expect(screen.getByText(/failed to fetch activity/i)).toBeInTheDocument(), + ); + + unmount(); + }); + + it("handles empty events response", async () => { + vi.stubGlobal("localStorage", { + getItem: vi.fn(() => "connected-token"), + setItem: vi.fn(), + clear: vi.fn(), + removeItem: vi.fn(), + key: vi.fn(() => null), + length: 0, + }); + + // Mock fetch to return successful response with empty events + vi.stubGlobal("fetch", vi.fn(() => + Promise.resolve({ + ok: true, + status: 200, + json: async () => ({ + events: [], + hasMore: false, + }), + } as Response), + )); + + const { unmount } = render(); + + // Should show empty state when no events + await waitFor(() => + expect(screen.getByText(/No activity found for this filter/i)).toBeInTheDocument(), + ); + + unmount(); + }); + + it("handles partial events response", async () => { + vi.stubGlobal("localStorage", { + getItem: vi.fn(() => "connected-token"), + setItem: vi.fn(), + clear: vi.fn(), + removeItem: vi.fn(), + key: vi.fn(() => null), + length: 0, + }); + + // Mock fetch to return successful response with partial events + vi.stubGlobal("fetch", vi.fn(() => + Promise.resolve({ + ok: true, + status: 200, + json: async () => ({ + events: [ + { + id: "evt-1", + streamId: 12345, + eventType: "CREATED", + amount: "10000000000", + transactionHash: "", + ledgerSequence: 1000, + timestamp: 1700000000, + metadata: null, + createdAt: "2026-01-01T00:00:00Z", + }, + ], + hasMore: false, + }), + } as Response), + )); + + const { unmount } = render(); + + // Should render activity with the events + await waitFor(() => + expect(screen.getByText(/Stream Activity/i)).toBeInTheDocument(), + ); + + // Should show the event + const createdText = await screen.findByText( + (_, element) => + element?.tagName === "P" && + element.textContent?.startsWith("New stream created (") === true, + ); + expect(createdText).toBeInTheDocument(); + + unmount(); + }); + + it("handles large page size response", async () => { + vi.stubGlobal("localStorage", { + getItem: vi.fn(() => "connected-token"), + setItem: vi.fn(), + clear: vi.fn(), + removeItem: vi.fn(), + key: vi.fn(() => null), + length: 0, + }); + + // Mock fetch to return response with many events + const manyEvents = Array.from({ length: 25 }, (_, i) => ({ + id: `evt-${i}`, + streamId: 12345 + i, + eventType: i % 2 === 0 ? "CREATED" : "TOPPED_UP", + amount: "10000000000", + transactionHash: i % 3 === 0 ? "" : "abc123", + ledgerSequence: 1000 + i, + timestamp: 1700000000 + i, + metadata: i % 4 === 0 ? null : JSON.stringify({ key: "value" }), + createdAt: "2026-01-01T00:00:00Z", + })); + + vi.stubGlobal("fetch", vi.fn(() => + Promise.resolve({ + ok: true, + status: 200, + json: async () => ({ + events: manyEvents, + hasMore: true, + }), + } as Response), + )); + + const { unmount } = render(); + + // Should render activity with events + await waitFor(() => + expect(screen.getByText(/Stream Activity/i)).toBeInTheDocument(), + ); + + // Should show load more button since hasMore is true + const loadMoreButton = await screen.findByRole("button", { + name: /Load More Activity/i, + }); + expect(loadMoreButton).toBeInTheDocument(); + + unmount(); + }); + + it("handles network error during fetch", async () => { + vi.stubGlobal("localStorage", { + getItem: vi.fn(() => "connected-token"), + setItem: vi.fn(), + clear: vi.fn(), + removeItem: vi.fn(), + key: vi.fn(() => null), + length: 0, + }); + + // Mock fetch to throw network error + vi.stubGlobal("fetch", vi.fn(() => + Promise.reject(new Error("Network error: fetch failed")), + )); + + const { unmount } = render(); + + // Should handle network error gracefully + await waitFor(() => + expect(screen.getByText(/failed to fetch activity/i)).toBeInTheDocument(), + ); + + unmount(); + }); + + it("handles redirect after session expiry", async () => { + vi.stubGlobal("localStorage", { + getItem: vi.fn(() => null), // No valid session + setItem: vi.fn(), + clear: vi.fn(), + removeItem: vi.fn(), + key: vi.fn(() => null), + length: 0, + }); + + const { unmount } = render(); + + // Should show access denied when no session + const accessDenied = screen.getByText(/Access Denied/); + expect(accessDenied).toBeInTheDocument(); + + const pleaseConnect = screen.getByText(/Please connect your wallet/); + expect(pleaseConnect).toBeInTheDocument(); + + unmount(); + }); +}); \ No newline at end of file diff --git a/frontend/src/app/activity/activity-content.tsx b/frontend/src/app/activity/activity-content.tsx index 2cd7492c..be228794 100644 --- a/frontend/src/app/activity/activity-content.tsx +++ b/frontend/src/app/activity/activity-content.tsx @@ -28,6 +28,7 @@ export default function ActivityContent() { const [events, setEvents] = useState([]); const [activeTab, setActiveTab] = useState("ALL"); const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); const [page, setPage] = useState(1); const [hasMore, setHasMore] = useState(true); @@ -35,6 +36,7 @@ export default function ActivityContent() { async (pageNum: number, tab: string, append: boolean = false, signal?: AbortSignal) => { if (!session?.publicKey) return; setLoading(true); + setError(null); try { const typeFilter = tab === "PAUSED" ? "PAUSED,RESUMED" : tab; @@ -65,6 +67,7 @@ export default function ActivityContent() { logger.error("Failed to fetch activity:", error); if (!append) setEvents([]); setHasMore(false); + setError("Failed to fetch activity. Please try again."); } finally { setLoading(false); } @@ -154,6 +157,22 @@ export default function ActivityContent() { + {error && ( +
+

{error}

+ +
+ )} + {hasMore && (
+ ), })); vi.mock("../stream-creation/CancelConfirmModal", () => ({ - CancelConfirmModal: () =>
, + CancelConfirmModal: ({ + streamId, + onConfirm, + }: { + streamId: string; + onConfirm: (streamId: string) => Promise; + }) => ( + + ), })); vi.mock("./StreamDetailsModal", () => ({ @@ -92,6 +114,12 @@ vi.mock("../ui/Button", () => ({ })); import { DashboardView } from "./dashboard-view"; +import { cancelStream, topUpStream } from "@/lib/soroban"; +import { + dashboardQueryKey, + type DashboardSnapshot, + type Stream, +} from "@/lib/dashboard"; import type { WalletSession } from "@/lib/wallet"; const PUBLIC_KEY = "GABCDEFPUBLICKEY000000000000000000000000000000000000000"; @@ -105,115 +133,94 @@ const session: WalletSession = { mocked: false, }; -function jsonResponse(body: unknown) { +function makeStream( + id: string, + deposited: number, + withdrawn: number, +): Stream { return { - ok: true, - status: 200, - json: async () => body, - } as Response; + id, + recipient: `GABCDEF12345678901234567890123456789012${id.slice(-1)}`, + amount: deposited, + token: "XLM", + status: "Active", + deposited, + withdrawn, + date: "2024-01-01", + ratePerSecond: 10, + lastUpdateTime: 1000, + isActive: true, + }; +} + +function renderDashboard(streams: Stream[]) { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { staleTime: 10_000, refetchOnWindowFocus: false, retry: 1 }, + }, + }); + const snapshot: DashboardSnapshot = { + totalSent: 0, + totalReceived: 0, + totalValueLocked: 1000, + activeStreamsCount: streams.length, + recentActivity: [], + outgoingStreams: streams, + incomingStreams: [], + }; + queryClient.setQueryData(dashboardQueryKey(PUBLIC_KEY), snapshot); + + return render( + + + , + ); } -describe("DashboardView + useDashboard cache integration", () => { +describe("DashboardView failed actions", () => { beforeEach(() => { - vi.stubGlobal("fetch", vi.fn().mockResolvedValue(jsonResponse([]))); + vi.mocked(topUpStream).mockReset(); + vi.mocked(cancelStream).mockReset(); }); - afterEach(() => { - vi.unstubAllGlobals(); + it("preserves the deposited amount when a top-up fails", async () => { + vi.mocked(topUpStream).mockRejectedValueOnce(new Error("top-up failed")); + renderDashboard([makeStream("stream-1", 100, 0)]); + + expect(screen.getByText("100 XLM")).toBeInTheDocument(); + fireEvent.click(screen.getByRole("button", { name: "Add Funds" })); + fireEvent.click(screen.getByRole("button", { name: "Confirm top-up" })); + + await waitFor(() => expect(topUpStream).toHaveBeenCalledTimes(1)); + expect(screen.getByText("100 XLM")).toBeInTheDocument(); }); - it("does not issue a duplicate network fetch when the dashboard is unmounted and remounted within staleTime (e.g. switching tabs away and back)", async () => { - // Mirror the app's real QueryClient defaults (see - // frontend/src/components/providers/query-provider.tsx) so this proves - // the behavior users actually get: staleTime 10s means a remount within - // that window should be served from cache, not refetched. - const queryClient = new QueryClient({ - defaultOptions: { - queries: { staleTime: 10_000, refetchOnWindowFocus: false, retry: 1 }, - }, - }); - - const renderDashboard = () => - render( - - - , - ); - - const first = renderDashboard(); - await waitFor(() => - expect(screen.getByText(/start your first stream/i)).toBeInTheDocument(), - ); - // One request each for outgoing ("sender") and incoming ("recipient") streams. - expect(fetch).toHaveBeenCalledTimes(2); - - // Simulate navigating/switching away from the dashboard and back within - // the 10s staleTime window. - first.unmount(); - - render( - - - , - ); - - await waitFor(() => - expect(screen.getByText(/start your first stream/i)).toBeInTheDocument(), - ); - - // Still just the 2 calls from the first mount — the remounted dashboard - // was served from the React Query cache instead of refetching. - expect(fetch).toHaveBeenCalledTimes(2); - }, 20000); - - it("shows the loading skeleton, then renders content once useDashboard resolves", async () => { - const queryClient = new QueryClient({ - defaultOptions: { queries: { retry: false } }, - }); - - render( - - - , - ); - - expect(screen.getByLabelText(/loading dashboard/i)).toBeInTheDocument(); - - await waitFor(() => - expect(screen.getByText(/start your first stream/i)).toBeInTheDocument(), - ); - }, 20000); - - it("shows the error state with a retry button when the fetch fails, and retrying refetches", async () => { - vi.stubGlobal( - "fetch", - vi.fn().mockRejectedValue(new Error("network down")), - ); - const queryClient = new QueryClient({ - defaultOptions: { queries: { retry: false } }, - }); - - render( - - - , - ); - - await waitFor(() => - expect(screen.getByText(/failed to load streams/i)).toBeInTheDocument(), - ); - - const callsBeforeRetry = vi.mocked(fetch).mock.calls.length; - - vi.mocked(fetch).mockResolvedValue(jsonResponse([])); - screen.getByRole("button", { name: /retry/i }).click(); - - await waitFor(() => - expect(screen.getByText(/start your first stream/i)).toBeInTheDocument(), - ); - - expect(vi.mocked(fetch).mock.calls.length).toBeGreaterThan( - callsBeforeRetry, - ); - }, 20000); + it("keeps the stream active when cancellation fails", async () => { + vi.mocked(cancelStream).mockRejectedValueOnce(new Error("cancel failed")); + renderDashboard([makeStream("stream-1", 100, 50)]); + + expect(screen.getByText("50 XLM")).toBeInTheDocument(); + fireEvent.click(screen.getByRole("button", { name: "Cancel" })); + fireEvent.click(screen.getByRole("button", { name: "Confirm cancellation" })); + + await waitFor(() => expect(cancelStream).toHaveBeenCalledTimes(1)); + expect(screen.getByText("50 XLM")).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Cancel" })).toBeInTheDocument(); + }); + + it("does not change other streams when one top-up fails", async () => { + vi.mocked(topUpStream).mockRejectedValueOnce(new Error("top-up failed")); + renderDashboard([ + makeStream("stream-1", 100, 0), + makeStream("stream-2", 200, 50), + ]); + + fireEvent.click(screen.getAllByRole("button", { name: "Add Funds" })[1]!); + fireEvent.click(screen.getByRole("button", { name: "Confirm top-up" })); + + await waitFor(() => expect(topUpStream).toHaveBeenCalledTimes(1)); + expect(screen.getByText("100 XLM")).toBeInTheDocument(); + expect(screen.getByText("200 XLM")).toBeInTheDocument(); + expect(screen.getByText("50 XLM")).toBeInTheDocument(); + }); }); diff --git a/package-lock.json b/package-lock.json index e15e86b8..a9163c42 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "concurrently": "^9.1.2", "husky": "^9.1.7", "lint-staged": "^17.0.7", - "next": "16.3.8", + "next": "16.4.3", "vitest": "^3.2.7" }, "optionalDependencies": { @@ -158,7 +158,7 @@ "@tanstack/react-query": "^5.101.0", "@tanstack/react-virtual": "^3.14.9", "lucide-react": "^0.575.0", - "next": "16.3.8", + "next": "16.4.3", "next-themes": "^0.4.6", "react": "19.2.7", "react-dom": "19.2.7", @@ -2292,9 +2292,9 @@ } }, "node_modules/@next/env": { - "version": "16.3.8", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.3.8.tgz", - "integrity": "sha512-Al9zqHVV7TJv0eFuOU4U7Lvv74PTih4Ch63sk2xCIpSTkE3udFnaOcnzP2lQVymiL7yS9Cj2iClUXlR3EQ5sEw==", + "version": "16.4.3", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.4.3.tgz", + "integrity": "sha512-a7tz1QCcWWkwC/rPJcPMJk/UNRu9CFhDd3OHYE5hddbJrs8BqRTalxI6GlKCWEGzRLHN+S/B7a4PAHCtm25aBQ==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { @@ -2308,9 +2308,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "16.3.8", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.3.8.tgz", - "integrity": "sha512-2JPRMh2nmQG5CiL7cXGL9AGwnPWJQ//cTtAUCT+w511QHk79SYz3LGv/pc5X643B/WEO0rvu3Yww0hqwt3kgeA==", + "version": "16.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.4.3.tgz", + "integrity": "sha512-4yq5EXBt+VDtAZwTyfijaFp5D2C5oz3Ikw5a2kiX2K9nw72vrZX2q5UuAlkPtUMO1tQz6JTKpaKWjS7OOefTDQ==", "cpu": [ "arm64" ], @@ -2324,9 +2324,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "16.3.8", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.3.8.tgz", - "integrity": "sha512-GZtCCOBKJ4leVIT/Th0llWKhD1ca92lzbQiS5R5ON9QkoiFnilFsebDae1JU2a3HWoKMEmEZWGs1AGLavVM72Q==", + "version": "16.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.4.3.tgz", + "integrity": "sha512-1Rvkn/LUhnEF8WxnRkOrqGhbwXui1bkWFxONKIbN+r7C+9KHDSZ+iUoj4CzymVMWUyZIyx9szcb9+kbKhD500A==", "cpu": [ "x64" ], @@ -2340,9 +2340,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.3.8", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.3.8.tgz", - "integrity": "sha512-O659ygeQYqneJ1fBKMpFxIFqYkYswu8IAS1OCKK/4f3ZgJJm1dRz4fVJZRi/kLLWjnBKnebOePA4WNv+sV1pVA==", + "version": "16.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.4.3.tgz", + "integrity": "sha512-dCKpXErGg3abUM6t5JTJV/ottVBTStJB5lRJTbgdUdQaQ+jNHiK56Hsc0YAEg/rinSh42hYG3HWiYmEMdilnxg==", "cpu": [ "arm64" ], @@ -2359,9 +2359,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.3.8", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.3.8.tgz", - "integrity": "sha512-dSjKSyWpzxoO1d3DIZZcP4XJcNaKeLmxQMFOiYl5vuBRMmweIqnAhty8tAmRsvTss779cK1FtYnDMj40e4TQlg==", + "version": "16.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.4.3.tgz", + "integrity": "sha512-WwYbhWkhYBvBtib1K4W0kL8MW2snBD2A5b3SN4vPpqalZbZs5+bQ1yzAdepVoOqrFfEW3dmREy4Zs1MrWcrFbw==", "cpu": [ "arm64" ], @@ -2378,9 +2378,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.3.8", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.3.8.tgz", - "integrity": "sha512-lbqOuz3RPRcv+o9msNsJw5x4+Y1ZwPTs6vmL6DCf7i0fZfvng/F59wyeDwqHIvV0mK//RBy/jJkZ+nCKsSMXjQ==", + "version": "16.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.4.3.tgz", + "integrity": "sha512-QymWvfUwDEC5/lpJOsxX5VAbyoR2D13+2Jsjz4be7gT4LPqEgdpkeLIR/HFfCLgKzoMhpJbp6I/JnogyNZHcaA==", "cpu": [ "x64" ], @@ -2397,9 +2397,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "16.3.8", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.3.8.tgz", - "integrity": "sha512-+316WswI8ScVgZeUd+1KGaXkHhaYQzCjvH/05TZSpJ8zBizb1a4G7DtO7F12jcBIqMOtsz9ji1t48fmKtzqsGA==", + "version": "16.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.4.3.tgz", + "integrity": "sha512-Vmjd9D7DpA4Vv3cUlr82QWgJD/BQZmns801vtC5VWHg6jCIA4a5vMKpQnfrM2pyCrp2eV0ENdUg4PlN3bAc2Yw==", "cpu": [ "x64" ], @@ -2416,9 +2416,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.3.8", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.3.8.tgz", - "integrity": "sha512-ji0gd4kMYUxO+1fJBIbiBVRCjzG/lloiyCccnlebvb1ZJ5qXCPZqYg4Jl1DrrixnWNMKylzgpmMWx0yNDYXlzw==", + "version": "16.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.4.3.tgz", + "integrity": "sha512-lG1pPdPLk7gTyGb00n5JIMLdBJgjb+UEhSdZvFbhUMg0DyC6z4B7RnpSKF9VCd4imkLObVbXOFA0kFCpw7yFow==", "cpu": [ "arm64" ], @@ -2432,9 +2432,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.3.8", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.3.8.tgz", - "integrity": "sha512-WcTlaKt/TWkh5kUjdJcUmB1XgZ+1c6fz4Y9fDHL73YNSdGaUWjceeWrrlwF0nv19iABYWC4iAq1oX1w4Bn0vfg==", + "version": "16.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.4.3.tgz", + "integrity": "sha512-NmQC9RkJdnPXRaMxtkCzobB3teVjqF33oSOnwDfi+XYGWWYpt+xlGK3mtktSKLbdjwaWbJu2OkkMiRFGUUncKg==", "cpu": [ "x64" ], @@ -10858,19 +10858,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/index-to-position": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", - "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -12310,12 +12297,12 @@ } }, "node_modules/next": { - "version": "16.3.8", - "resolved": "https://registry.npmjs.org/next/-/next-16.3.8.tgz", - "integrity": "sha512-U7QEZaTini6wKrb8A8hqLLqYQyCetegKjCpJOyxk642vWoMoU1x5PyZCJFvgYgiptA8xc5j/9xYlZFO7w9Sjmw==", + "version": "16.4.3", + "resolved": "https://registry.npmjs.org/next/-/next-16.4.3.tgz", + "integrity": "sha512-OqxKTGi9u80FydydsMNYNjnuje9Au+kvHlt2GZ//ftdpDHm1dxKqAaENoOOTX5wKzn+9vaKUNIfZeyZUuLhCnA==", "license": "MIT", "dependencies": { - "@next/env": "16.3.8", + "@next/env": "16.4.3", "@swc/helpers": "0.5.23", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", @@ -12329,15 +12316,15 @@ "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "16.3.8", - "@next/swc-darwin-x64": "16.3.8", - "@next/swc-linux-arm64-gnu": "16.3.8", - "@next/swc-linux-arm64-musl": "16.3.8", - "@next/swc-linux-x64-gnu": "16.3.8", - "@next/swc-linux-x64-musl": "16.3.8", - "@next/swc-win32-arm64-msvc": "16.3.8", - "@next/swc-win32-x64-msvc": "16.3.8", - "sharp": "^0.35.4" + "@next/swc-darwin-arm64": "16.4.3", + "@next/swc-darwin-x64": "16.4.3", + "@next/swc-linux-arm64-gnu": "16.4.3", + "@next/swc-linux-arm64-musl": "16.4.3", + "@next/swc-linux-x64-gnu": "16.4.3", + "@next/swc-linux-x64-musl": "16.4.3", + "@next/swc-win32-arm64-msvc": "16.4.3", + "@next/swc-win32-x64-msvc": "16.4.3", + "sharp": "^0.35.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", @@ -13097,47 +13084,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pkg-types": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", - "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "confbox": "^0.2.2", - "exsolve": "^1.0.7", - "pathe": "^2.0.3" - } - }, - "node_modules/playwright": { - "version": "1.64.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.64.0.tgz", - "integrity": "sha512-kVzTnYFYEyQ9RGzBjNCd2+yEqWmVbxl3AHPynv3qQ6JTjv4a+kwRcnqQwIpuP3NnrnIrE/Q6wWAIAvMfIE2/Yg==", - "devOptional": true, - "license": "Apache-2.0", - "dependencies": { - "playwright-core": "1.64.0" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/playwright-core": { - "version": "1.64.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.64.0.tgz", - "integrity": "sha512-T9r+MZkTECl2+oUcZ26YgZzTrYidTsIhYYkJix+6iDOymOb1FB2RLLSOxRGnZGv5DStt+aduIVbt+E8WygXYpg==", - "devOptional": true, - "license": "Apache-2.0", - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=20" - } - }, "node_modules/pkg-types": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.3.tgz", @@ -13150,13 +13096,6 @@ "pathe": "^2.0.3" } }, - "node_modules/pkg-types/node_modules/confbox": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.3.1.tgz", - "integrity": "sha512-cKUSoKa8YxFZZSmraVi7onONx3amu77ngK3kGpsYHDH7drPwCRkQE1RYMPlLRrMtnciRj274XNRxcHxnKmDSnA==", - "devOptional": true, - "license": "MIT" - }, "node_modules/playwright": { "version": "1.63.0", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz", @@ -13186,6 +13125,13 @@ "node": ">=20" } }, + "node_modules/pkg-types/node_modules/confbox": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.3.1.tgz", + "integrity": "sha512-cKUSoKa8YxFZZSmraVi7onONx3amu77ngK3kGpsYHDH7drPwCRkQE1RYMPlLRrMtnciRj274XNRxcHxnKmDSnA==", + "devOptional": true, + "license": "MIT" + }, "node_modules/pluralize": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", diff --git a/package.json b/package.json index fe369163..33b3815c 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "concurrently": "^9.1.2", "husky": "^9.1.7", "lint-staged": "^17.0.7", - "next": "16.3.8", + "next": "16.4.3", "vitest": "^3.2.7" }, "lint-staged": {