From 5e08b497e1af1dc2871fcc9319027b60836448b0 Mon Sep 17 00:00:00 2001 From: Rajat Date: Tue, 18 Nov 2025 09:12:15 +0000 Subject: [PATCH 1/2] Test suite for Queue --- .../__mocks__/@courselit/email-editor.ts | 12 + apps/queue/__mocks__/css.ts | 2 + apps/queue/__mocks__/lucide-react.ts | 2 + apps/queue/__mocks__/nanoid.ts | 1 + apps/queue/__mocks__/radix-ui.ts | 4 + apps/queue/__mocks__/settings-components.tsx | 9 + apps/queue/__mocks__/slugify.ts | 1 + apps/queue/__mocks__/ui-components.ts | 7 + apps/queue/jest-mongodb-config.js | 13 + apps/queue/jest.config.ts | 52 + apps/queue/package.json | 4 + apps/queue/setupTests.ts | 52 + apps/queue/src/__tests__/constants.test.ts | 29 + .../process-ongoing-sequences.test.ts | 1537 +++++++++++++++++ .../domain/process-ongoing-sequences/index.ts | 43 + .../process-ongoing-sequence.ts} | 83 +- apps/queue/tsconfig.json | 5 +- apps/queue/tsup.config.ts | 2 +- .../courses/__tests__/delete-course.test.ts | 37 +- .../users/__tests__/delete-user.test.ts | 74 +- apps/web/next-env.d.ts | 2 +- jest.config.js | 1 + packages/components-library/package.json | 2 +- packages/email-editor/package.json | 6 +- pnpm-lock.yaml | 893 ++++------ 25 files changed, 2204 insertions(+), 669 deletions(-) create mode 100644 apps/queue/__mocks__/@courselit/email-editor.ts create mode 100644 apps/queue/__mocks__/css.ts create mode 100644 apps/queue/__mocks__/lucide-react.ts create mode 100644 apps/queue/__mocks__/nanoid.ts create mode 100644 apps/queue/__mocks__/radix-ui.ts create mode 100644 apps/queue/__mocks__/settings-components.tsx create mode 100644 apps/queue/__mocks__/slugify.ts create mode 100644 apps/queue/__mocks__/ui-components.ts create mode 100644 apps/queue/jest-mongodb-config.js create mode 100644 apps/queue/jest.config.ts create mode 100644 apps/queue/setupTests.ts create mode 100644 apps/queue/src/__tests__/constants.test.ts create mode 100644 apps/queue/src/domain/__tests__/process-ongoing-sequences.test.ts create mode 100644 apps/queue/src/domain/process-ongoing-sequences/index.ts rename apps/queue/src/domain/{process-ongoing-sequences.ts => process-ongoing-sequences/process-ongoing-sequence.ts} (80%) diff --git a/apps/queue/__mocks__/@courselit/email-editor.ts b/apps/queue/__mocks__/@courselit/email-editor.ts new file mode 100644 index 000000000..edd662761 --- /dev/null +++ b/apps/queue/__mocks__/@courselit/email-editor.ts @@ -0,0 +1,12 @@ +// Re-export only renderEmailToHtml and types to avoid loading EmailEditor and its dependencies +// This allows us to test the real renderEmailToHtml without loading React UI components +export type { + Email, + EmailBlock, + EmailMeta, + EmailStyle, + BlockComponent, +} from "../../../../packages/email-editor/src/types/email-editor"; +export type { BlockRegistry } from "../../../../packages/email-editor/src/types/block-registry"; +export { renderEmailToHtml } from "../../../../packages/email-editor/src/lib/email-renderer"; +export { defaultEmail } from "../../../../packages/email-editor/src/lib/default-email"; diff --git a/apps/queue/__mocks__/css.ts b/apps/queue/__mocks__/css.ts new file mode 100644 index 000000000..aece9d926 --- /dev/null +++ b/apps/queue/__mocks__/css.ts @@ -0,0 +1,2 @@ +// Mock for CSS imports +export default {}; diff --git a/apps/queue/__mocks__/lucide-react.ts b/apps/queue/__mocks__/lucide-react.ts new file mode 100644 index 000000000..72f524380 --- /dev/null +++ b/apps/queue/__mocks__/lucide-react.ts @@ -0,0 +1,2 @@ +// Mock for lucide-react icons +export const Plus = () => null; diff --git a/apps/queue/__mocks__/nanoid.ts b/apps/queue/__mocks__/nanoid.ts new file mode 100644 index 000000000..4e4d2a3a4 --- /dev/null +++ b/apps/queue/__mocks__/nanoid.ts @@ -0,0 +1 @@ +export const nanoid = jest.fn(() => "mock-nanoid-id"); diff --git a/apps/queue/__mocks__/radix-ui.ts b/apps/queue/__mocks__/radix-ui.ts new file mode 100644 index 000000000..984775a44 --- /dev/null +++ b/apps/queue/__mocks__/radix-ui.ts @@ -0,0 +1,4 @@ +// Mock for Radix UI components +export const Popover = { Root: ({ children }: any) => children }; +export const PopoverContent = ({ children }: any) => children; +export const PopoverTrigger = ({ children }: any) => children; diff --git a/apps/queue/__mocks__/settings-components.tsx b/apps/queue/__mocks__/settings-components.tsx new file mode 100644 index 000000000..74bb41b17 --- /dev/null +++ b/apps/queue/__mocks__/settings-components.tsx @@ -0,0 +1,9 @@ +// Mock for settings components used by email-editor +import * as React from "react"; + +export const SettingsSelect = ({ children }: { children?: React.ReactNode }) => + React.createElement(React.Fragment, null, children); +export const SettingsSection = ({ children }: { children?: React.ReactNode }) => + React.createElement(React.Fragment, null, children); +export const SettingsSlider = ({ children }: { children?: React.ReactNode }) => + React.createElement(React.Fragment, null, children); diff --git a/apps/queue/__mocks__/slugify.ts b/apps/queue/__mocks__/slugify.ts new file mode 100644 index 000000000..6ba05ca5a --- /dev/null +++ b/apps/queue/__mocks__/slugify.ts @@ -0,0 +1 @@ +export default jest.fn((str: string) => str.toLowerCase().replace(/\s+/g, "-")); diff --git a/apps/queue/__mocks__/ui-components.ts b/apps/queue/__mocks__/ui-components.ts new file mode 100644 index 000000000..bae8e26e9 --- /dev/null +++ b/apps/queue/__mocks__/ui-components.ts @@ -0,0 +1,7 @@ +// Mock for UI components that aren't needed for renderEmailToHtml testing +export const Popover = ({ children }: { children: React.ReactNode }) => + children; +export const PopoverContent = ({ children }: { children: React.ReactNode }) => + children; +export const PopoverTrigger = ({ children }: { children: React.ReactNode }) => + children; diff --git a/apps/queue/jest-mongodb-config.js b/apps/queue/jest-mongodb-config.js new file mode 100644 index 000000000..46c2e0e23 --- /dev/null +++ b/apps/queue/jest-mongodb-config.js @@ -0,0 +1,13 @@ +module.exports = { + mongodbMemoryServerOptions: { + binary: { + version: "7.0.0", + skipMD5: true, + }, + instance: { + dbName: "jest", + }, + autoStart: false, + }, + useSharedDBForAllJestWorkers: true, +}; diff --git a/apps/queue/jest.config.ts b/apps/queue/jest.config.ts new file mode 100644 index 000000000..e2f3fb996 --- /dev/null +++ b/apps/queue/jest.config.ts @@ -0,0 +1,52 @@ +const config = { + preset: "@shelf/jest-mongodb", + setupFilesAfterEnv: ["/setupTests.ts"], + watchPathIgnorePatterns: ["globalConfig"], + moduleNameMapper: { + "@courselit/utils": "/../../packages/utils/src", + "@courselit/common-logic": "/../../packages/common-logic/src", + "@courselit/common-models": + "/../../packages/common-models/src", + "@courselit/email-editor": + "/__mocks__/@courselit/email-editor.ts", + nanoid: "/__mocks__/nanoid.ts", + "@sindresorhus/slugify": "/__mocks__/slugify.ts", + // Handle @/ paths - prioritize email-editor package paths, then queue app paths + // These must come before the generic @/ pattern + "^@/components/ui/(.*)$": + "/../../packages/email-editor/src/components/ui/$1", + "^@/components/settings/(.*)$": + "/__mocks__/settings-components.tsx", + "^@/components/(.*)$": + "/../../packages/email-editor/src/components/$1", + "^@/lib/(.*)$": "/../../packages/email-editor/src/lib/$1", + "^@/blocks$": "/../../packages/email-editor/src/blocks", + "^@/blocks/(.*)$": + "/../../packages/email-editor/src/blocks/$1", + "^@/types/(.*)$": "/../../packages/email-editor/src/types/$1", + "^@/(.*)$": "/src/$1", + // Mock React UI components and dependencies that aren't available in Node.js + "^@radix-ui/(.*)$": "/__mocks__/radix-ui.ts", + "^lucide-react$": "/__mocks__/lucide-react.ts", + // Mock CSS imports + "\\.css$": "/__mocks__/css.ts", + }, + transformIgnorePatterns: ["node_modules/(?!(nanoid)/)"], + extensionsToTreatAsEsm: [], + transform: { + "^.+\\.(ts|tsx)$": [ + "ts-jest", + { + tsconfig: { + jsx: "react-jsx", + }, + }, + ], + }, + testMatch: ["**/__tests__/**/*.test.ts", "**/?(*.)+(spec|test).ts"], + testPathIgnorePatterns: ["/node_modules/", "/dist/"], + moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json"], + testEnvironment: "node", +}; + +export default config; diff --git a/apps/queue/package.json b/apps/queue/package.json index e2b6ca903..badd2d105 100644 --- a/apps/queue/package.json +++ b/apps/queue/package.json @@ -29,8 +29,12 @@ "zod": "^3.22.4" }, "devDependencies": { + "@shelf/jest-mongodb": "^5.2.2", "@types/express": "^4.17.17", + "@types/jest": "^29.5.14", "@types/nodemailer": "^6.4.8", + "mongodb-memory-server": "^10.1.4", + "ts-jest": "^29.4.4", "tsconfig": "workspace:^", "tsup": "^7.2.0", "typescript": "^5.9.3", diff --git a/apps/queue/setupTests.ts b/apps/queue/setupTests.ts new file mode 100644 index 000000000..df50d087e --- /dev/null +++ b/apps/queue/setupTests.ts @@ -0,0 +1,52 @@ +import mongoose from "mongoose"; +import { MongoMemoryServer } from "mongodb-memory-server"; + +let mongod: MongoMemoryServer | null = null; + +// Suppress console.error during tests to reduce noise +const originalError = console.error; +beforeAll(() => { + console.error = jest.fn(); +}); + +afterAll(() => { + console.error = originalError; +}); + +// Ensure MongoDB connection is established +// @shelf/jest-mongodb provides global.__MONGO_URI__ through globalSetup +// If not available, set up MongoDB Memory Server manually +beforeAll(async () => { + let mongoUri = (global as any).__MONGO_URI__ || process.env.MONGO_URL; + + // If @shelf/jest-mongodb didn't set up MongoDB, do it manually + if (!mongoUri) { + mongod = await MongoMemoryServer.create(); + mongoUri = mongod.getUri(); + (global as any).__MONGO_URI__ = mongoUri; + } + + if (mongoose.connection.readyState === 0) { + await mongoose.connect(mongoUri); + } +}); + +afterAll(async () => { + if (mongoose.connection.readyState !== 0) { + await mongoose.connection.close(); + } + + // Clean up manually created MongoDB instance if it exists + if (mongod) { + await mongod.stop(); + } +}); + +// Clean up database after each test +export const cleanup = async () => { + const collections = mongoose.connection.collections; + for (const key in collections) { + const collection = collections[key]; + await collection.deleteMany(); + } +}; diff --git a/apps/queue/src/__tests__/constants.test.ts b/apps/queue/src/__tests__/constants.test.ts new file mode 100644 index 000000000..1d0e413a6 --- /dev/null +++ b/apps/queue/src/__tests__/constants.test.ts @@ -0,0 +1,29 @@ +/** + * @jest-environment node + */ + +import { sequenceBounceLimit } from "../constants"; + +describe("constants", () => { + it("should have a default sequenceBounceLimit of 3", () => { + expect(sequenceBounceLimit).toBeGreaterThanOrEqual(0); + }); + + it("should read sequenceBounceLimit from environment variable", () => { + const originalEnv = process.env.SEQUENCE_BOUNCE_LIMIT; + + process.env.SEQUENCE_BOUNCE_LIMIT = "5"; + // Note: This test demonstrates the pattern, but since the constant + // is evaluated at module load time, we'd need to reload the module + // to see the change. This is just for demonstration. + + expect(sequenceBounceLimit).toBeDefined(); + + // Restore original value + if (originalEnv) { + process.env.SEQUENCE_BOUNCE_LIMIT = originalEnv; + } else { + delete process.env.SEQUENCE_BOUNCE_LIMIT; + } + }); +}); diff --git a/apps/queue/src/domain/__tests__/process-ongoing-sequences.test.ts b/apps/queue/src/domain/__tests__/process-ongoing-sequences.test.ts new file mode 100644 index 000000000..4568a5a68 --- /dev/null +++ b/apps/queue/src/domain/__tests__/process-ongoing-sequences.test.ts @@ -0,0 +1,1537 @@ +/** + * @jest-environment node + */ + +import mongoose from "mongoose"; +import { + processOngoingSequence, + getNextPublishedEmail, +} from "../process-ongoing-sequences/process-ongoing-sequence"; +import OngoingSequenceModel from "../model/ongoing-sequence"; +import DomainModel, { DomainDocument } from "../model/domain"; +import SequenceModel from "../model/sequence"; +import UserModel from "../model/user"; +import EmailDelivery from "../model/email-delivery"; +import * as queries from "../queries"; +import * as mail from "../../mail"; +import { AdminSequence, InternalUser } from "@courselit/common-logic"; +import { renderEmailToHtml } from "@courselit/email-editor"; +import { jwtUtils } from "@courselit/utils"; +import { getUnsubLink } from "../../utils/get-unsub-link"; +import { getSiteUrl } from "../../utils/get-site-url"; +import { sequenceBounceLimit } from "../../constants"; + +// Mock dependencies +jest.mock("../../mail"); +jest.mock("@courselit/utils"); +jest.mock("../../utils/get-unsub-link"); +jest.mock("../../utils/get-site-url"); +jest.mock("../../logger", () => ({ + logger: { + error: jest.fn(), + }, +})); + +// Mock liquidjs - actually process basic Liquid templates for testing +jest.mock("liquidjs", () => { + return { + Liquid: jest.fn().mockImplementation(() => ({ + parseAndRender: jest.fn(async (template: string, payload: any) => { + // Simple Liquid template processing for tests + let result = template; + if (payload) { + // Replace {{subscriber.name}} + if (payload.subscriber?.name) { + result = result.replace( + /\{\{subscriber\.name\}\}/g, + payload.subscriber.name, + ); + } + // Replace {{subscriber.email}} + if (payload.subscriber?.email) { + result = result.replace( + /\{\{subscriber\.email\}\}/g, + payload.subscriber.email, + ); + } + // Replace {{unsubscribe_link}} + if (payload.unsubscribe_link) { + result = result.replace( + /\{\{unsubscribe_link\}\}/g, + payload.unsubscribe_link, + ); + } + // Replace {{address}} + if (payload.address) { + result = result.replace( + /\{\{address\}\}/g, + payload.address, + ); + } + } + return result; + }), + })), + }; +}); + +const mockedSendMail = mail.sendMail as jest.MockedFunction< + typeof mail.sendMail +>; +const mockedJwtUtils = jwtUtils as jest.Mocked; +const mockedGetUnsubLink = getUnsubLink as jest.MockedFunction< + typeof getUnsubLink +>; +const mockedGetSiteUrl = getSiteUrl as jest.MockedFunction; + +const TEST_DOMAIN_NAME = "queue-test-domain"; +const TEST_SEQUENCE_ID = "queue-sequence-123"; +const TEST_USER_ID = "queue-user-123"; +const TEST_CREATOR_ID = "queue-creator-123"; + +describe("processOngoingSequence", () => { + let testDomain: DomainDocument; + let testSequence: AdminSequence; + let testUser: InternalUser; + let testCreator: InternalUser; + + beforeAll(async () => { + // Set required environment variables + process.env.PIXEL_SIGNING_SECRET = "test-secret"; + process.env.PROTOCOL = "https"; + process.env.DOMAIN = "test.com"; + process.env.NODE_ENV = "test"; + + // Create test domain + testDomain = await (DomainModel.create as any)({ + name: TEST_DOMAIN_NAME, + settings: { + mailingAddress: "test@example.com", + }, + quota: { + mail: { + daily: 1000, + monthly: 30000, + dailyCount: 0, + monthlyCount: 0, + }, + }, + }); + + // Create test users + testUser = (await (UserModel.create as any)({ + userId: TEST_USER_ID, + name: "Queue Test User", + email: "queue-user@example.com", + active: true, + subscribedToUpdates: true, + unsubscribeToken: "unsub-token-123", + tags: [], + domain: testDomain._id, + })) as any; + + testCreator = (await (UserModel.create as any)({ + userId: TEST_CREATOR_ID, + name: "Queue Test Creator", + email: "queue-creator@example.com", + active: true, + subscribedToUpdates: true, + unsubscribeToken: "unsub-token-creator", + tags: [], + domain: testDomain._id, + })) as any; + + // Create test sequence + testSequence = (await (SequenceModel.create as any)({ + domain: testDomain._id, + sequenceId: TEST_SEQUENCE_ID, + creatorId: TEST_CREATOR_ID, + type: "sequence", + emails: [ + { + emailId: "email-1", + subject: "First Email", + published: true, + delayInMillis: 86400000, // 1 day + content: { + content: [ + { + id: "block-1", + blockType: "text", + settings: { + content: "Hello {{subscriber.name}}", + }, + }, + ], + style: { + colors: { + background: "#ffffff", + foreground: "#000000", + border: "#e2e8f0", + accent: "#0284c7", + accentForeground: "#ffffff", + }, + typography: { + header: { + fontFamily: "Arial, sans-serif", + }, + text: { + fontFamily: "Arial, sans-serif", + }, + link: { + fontFamily: "Arial, sans-serif", + }, + }, + interactives: { + button: {}, + link: {}, + }, + structure: { + page: { + marginY: "20px", + }, + section: { + padding: { + x: "24px", + y: "16px", + }, + }, + }, + }, + meta: {}, + }, + }, + { + emailId: "email-2", + subject: "Second Email", + published: true, + delayInMillis: 86400000, // 1 day + content: { + content: [ + { + id: "block-1", + blockType: "text", + settings: { content: "Second email content" }, + }, + ], + style: { + colors: { + background: "#ffffff", + foreground: "#000000", + border: "#e2e8f0", + accent: "#0284c7", + accentForeground: "#ffffff", + }, + typography: { + header: { + fontFamily: "Arial, sans-serif", + }, + text: { + fontFamily: "Arial, sans-serif", + }, + link: { + fontFamily: "Arial, sans-serif", + }, + }, + interactives: { + button: {}, + link: {}, + }, + structure: { + page: { + marginY: "20px", + }, + section: { + padding: { + x: "24px", + y: "16px", + }, + }, + }, + }, + meta: {}, + }, + }, + ], + emailsOrder: ["email-1", "email-2"], + report: { + sequence: { + failed: [], + }, + }, + })) as any; + + // Setup mocks + mockedGetSiteUrl.mockReturnValue("https://test.com"); + mockedGetUnsubLink.mockReturnValue( + "https://test.com/api/unsubscribe/unsub-token-123", + ); + mockedJwtUtils.generateToken = jest.fn().mockReturnValue("test-token"); + // renderEmailToHtml is not mocked - we test the real email formatting + mockedSendMail.mockResolvedValue(undefined); + }); + + beforeEach(async () => { + // Clean up only test-specific data, preserve testDomain, testUser, testCreator, testSequence + await OngoingSequenceModel.deleteMany({ + _id: { $ne: testDomain._id }, // Keep test domain + }); + await EmailDelivery.deleteMany({}); + // Clean up any sequences/ongoing sequences created during tests + await OngoingSequenceModel.deleteMany({ + sequenceId: { $ne: TEST_SEQUENCE_ID }, // Keep main test sequence + }); + jest.clearAllMocks(); + }); + + afterAll(async () => { + await DomainModel.deleteMany({}); + await UserModel.deleteMany({}); + await SequenceModel.deleteMany({}); + await OngoingSequenceModel.deleteMany({}); + await EmailDelivery.deleteMany({}); + }); + + describe("processOngoingSequence", () => { + it("should return early if ongoing sequence is not found", async () => { + const nonExistentId = new mongoose.Types.ObjectId(); + const getDomainSpy = jest.spyOn(queries, "getDomain"); + + await processOngoingSequence(nonExistentId); + + // Should not throw and should not call any queries + expect(getDomainSpy).not.toHaveBeenCalled(); + }); + + it("should return early if domain is invalid (missing mailingAddress)", async () => { + const invalidDomain = await (DomainModel.create as any)({ + name: "invalid-domain", + settings: {}, // Missing mailingAddress + quota: { + mail: { + daily: 1000, + monthly: 30000, + dailyCount: 0, + monthlyCount: 0, + }, + }, + }); + + const ongoingSeq = await OngoingSequenceModel.create({ + domain: invalidDomain._id, + sequenceId: TEST_SEQUENCE_ID, + userId: TEST_USER_ID, + nextEmailScheduledTime: Date.now() - 1000, + retryCount: 0, + sentEmailIds: [], + }); + + jest.spyOn(queries, "getDomain").mockResolvedValue(invalidDomain); + + await processOngoingSequence(ongoingSeq._id as any); + + // Should not send email + expect(mockedSendMail).not.toHaveBeenCalled(); + + await DomainModel.deleteOne({ _id: invalidDomain._id }); + await OngoingSequenceModel.deleteOne({ _id: ongoingSeq._id }); + }); + + it("should return early if quota is exceeded", async () => { + const quotaExceededDomain = await (DomainModel.create as any)({ + name: "quota-domain", + settings: { + mailingAddress: "test@example.com", + }, + quota: { + mail: { + daily: 1000, + monthly: 30000, + dailyCount: 1000, // At limit + monthlyCount: 0, + }, + }, + }); + + const ongoingSeq = await OngoingSequenceModel.create({ + domain: quotaExceededDomain._id, + sequenceId: TEST_SEQUENCE_ID, + userId: TEST_USER_ID, + nextEmailScheduledTime: Date.now() - 1000, + retryCount: 0, + sentEmailIds: [], + }); + + jest.spyOn(queries, "getDomain").mockResolvedValue( + quotaExceededDomain, + ); + + await processOngoingSequence(ongoingSeq._id as any); + + // Should not send email + expect(mockedSendMail).not.toHaveBeenCalled(); + + await DomainModel.deleteOne({ _id: quotaExceededDomain._id }); + await OngoingSequenceModel.deleteOne({ _id: ongoingSeq._id }); + }); + + it("should clean up resources if sequence is missing", async () => { + const ongoingSeq = await OngoingSequenceModel.create({ + domain: testDomain._id, + sequenceId: "nonexistent-sequence", + userId: TEST_USER_ID, + nextEmailScheduledTime: Date.now() - 1000, + retryCount: 0, + sentEmailIds: [], + }); + + jest.spyOn(queries, "getDomain").mockResolvedValue(testDomain); + jest.spyOn(queries, "getSequence").mockResolvedValue(null); + const deleteSpy = jest + .spyOn(queries, "deleteOngoingSequence") + .mockResolvedValue(undefined); + + await processOngoingSequence(ongoingSeq._id as any); + + // Should clean up + expect(deleteSpy).toHaveBeenCalledWith("nonexistent-sequence"); + + await OngoingSequenceModel.deleteOne({ _id: ongoingSeq._id }); + }); + + it("should clean up resources if user is missing", async () => { + const ongoingSeq = await OngoingSequenceModel.create({ + domain: testDomain._id, + sequenceId: TEST_SEQUENCE_ID, + userId: "nonexistent-user", + nextEmailScheduledTime: Date.now() - 1000, + retryCount: 0, + sentEmailIds: [], + }); + + jest.spyOn(queries, "getDomain").mockResolvedValue(testDomain); + jest.spyOn(queries, "getSequence").mockResolvedValue(testSequence); + jest.spyOn(queries, "getUser") + .mockResolvedValueOnce(null) // User not found + .mockResolvedValueOnce(testCreator); + const deleteSpy = jest + .spyOn(queries, "deleteOngoingSequence") + .mockResolvedValue(undefined); + + await processOngoingSequence(ongoingSeq._id as any); + + // Should clean up + expect(deleteSpy).toHaveBeenCalled(); + + await OngoingSequenceModel.deleteOne({ _id: ongoingSeq._id }); + }); + + it("should successfully send email and schedule next one", async () => { + const ongoingSeq = await OngoingSequenceModel.create({ + domain: testDomain._id, + sequenceId: TEST_SEQUENCE_ID, + userId: TEST_USER_ID, + nextEmailScheduledTime: Date.now() - 1000, + retryCount: 0, + sentEmailIds: [], + }); + + // Get fresh references to avoid stale document issues + const freshDomain = await (DomainModel.findById as any)( + testDomain._id, + ); + const freshSequence = await (SequenceModel.findOne as any)({ + sequenceId: TEST_SEQUENCE_ID, + }); + const freshUser = await (UserModel.findOne as any)({ + userId: TEST_USER_ID, + }); + const freshCreator = await (UserModel.findOne as any)({ + userId: TEST_CREATOR_ID, + }); + + if (!freshDomain || !freshSequence || !freshUser || !freshCreator) { + throw new Error("Failed to get fresh references"); + } + + jest.spyOn(queries, "getDomain").mockResolvedValue(freshDomain); + jest.spyOn(queries, "getSequence").mockResolvedValue( + freshSequence as any, + ); + jest.spyOn(queries, "getUser") + .mockResolvedValueOnce(freshUser as any) + .mockResolvedValueOnce(freshCreator as any); + + await processOngoingSequence(ongoingSeq._id as any); + + // Verify email was sent + expect(mockedSendMail).toHaveBeenCalledWith( + expect.objectContaining({ + from: expect.stringContaining("queue-creator@example.com"), + to: "queue-user@example.com", + subject: "First Email", + html: expect.any(String), + }), + ); + + // Verify email delivery was created + const emailDelivery = await (EmailDelivery.findOne as any)({ + sequenceId: TEST_SEQUENCE_ID, + userId: TEST_USER_ID, + emailId: "email-1", + }); + expect(emailDelivery).toBeTruthy(); + + // Verify ongoing sequence was updated + const updatedSeq = await OngoingSequenceModel.findById( + ongoingSeq._id, + ); + expect(updatedSeq?.sentEmailIds).toContain("email-1"); + expect(updatedSeq?.nextEmailScheduledTime).toBeGreaterThan( + Date.now(), + ); + + await OngoingSequenceModel.deleteOne({ _id: ongoingSeq._id }); + await EmailDelivery.deleteMany({}); + }); + + it("should complete sequence when all emails are sent", async () => { + const ongoingSeq = await OngoingSequenceModel.create({ + domain: testDomain._id, + sequenceId: TEST_SEQUENCE_ID, + userId: TEST_USER_ID, + nextEmailScheduledTime: Date.now() - 1000, + retryCount: 0, + sentEmailIds: ["email-1"], // First email already sent + }); + + // Get fresh references + const freshDomain = await (DomainModel.findById as any)( + testDomain._id, + ); + const freshSequence = await (SequenceModel.findOne as any)({ + sequenceId: TEST_SEQUENCE_ID, + }); + const freshUser = await (UserModel.findOne as any)({ + userId: TEST_USER_ID, + }); + const freshCreator = await (UserModel.findOne as any)({ + userId: TEST_CREATOR_ID, + }); + + if (!freshDomain || !freshSequence || !freshUser || !freshCreator) { + throw new Error("Failed to get fresh references"); + } + + jest.spyOn(queries, "getDomain").mockResolvedValue(freshDomain); + jest.spyOn(queries, "getSequence").mockResolvedValue( + freshSequence as any, + ); + jest.spyOn(queries, "getUser") + .mockResolvedValueOnce(freshUser as any) + .mockResolvedValueOnce(freshCreator as any); + const deleteSpy = jest + .spyOn(queries, "deleteOngoingSequence") + .mockResolvedValue(undefined); + + await processOngoingSequence(ongoingSeq._id as any); + + // Should send second email + expect(mockedSendMail).toHaveBeenCalledWith( + expect.objectContaining({ + subject: "Second Email", + }), + ); + + // Should clean up after completion + expect(deleteSpy).toHaveBeenCalled(); + + await OngoingSequenceModel.deleteOne({ _id: ongoingSeq._id }); + }); + + it("should handle sendMail errors and increment retry count", async () => { + const ongoingSeq = await OngoingSequenceModel.create({ + domain: testDomain._id, + sequenceId: TEST_SEQUENCE_ID, + userId: TEST_USER_ID, + nextEmailScheduledTime: Date.now() - 1000, + retryCount: 0, + sentEmailIds: [], + }); + + const error = new Error("SMTP Error"); + mockedSendMail.mockRejectedValueOnce(error); + + jest.spyOn(queries, "getDomain").mockResolvedValue(testDomain); + jest.spyOn(queries, "getSequence").mockResolvedValue(testSequence); + jest.spyOn(queries, "getUser") + .mockResolvedValueOnce(testUser) + .mockResolvedValueOnce(testCreator); + + await expect( + processOngoingSequence(ongoingSeq._id as any), + ).rejects.toThrow("SMTP Error"); + + // Verify retry count was incremented + const updatedSeq = await OngoingSequenceModel.findById( + ongoingSeq._id, + ); + expect(updatedSeq?.retryCount).toBe(1); + + await OngoingSequenceModel.deleteOne({ _id: ongoingSeq._id }); + }); + + it("should delete ongoing sequence when retry limit is exceeded", async () => { + const ongoingSeq = await OngoingSequenceModel.create({ + domain: testDomain._id, + sequenceId: TEST_SEQUENCE_ID, + userId: TEST_USER_ID, + nextEmailScheduledTime: Date.now() - 1000, + retryCount: sequenceBounceLimit - 1, // One less than limit + sentEmailIds: [], + }); + + const error = new Error("SMTP Error"); + mockedSendMail.mockRejectedValueOnce(error); + + // Get fresh references - especially important for sequence to avoid version conflicts + const freshDomain = await (DomainModel.findById as any)( + testDomain._id, + ); + const freshSequence = await (SequenceModel.findOne as any)({ + sequenceId: TEST_SEQUENCE_ID, + }); + const freshUser = await (UserModel.findOne as any)({ + userId: TEST_USER_ID, + }); + const freshCreator = await (UserModel.findOne as any)({ + userId: TEST_CREATOR_ID, + }); + + if (!freshDomain || !freshSequence || !freshUser || !freshCreator) { + throw new Error("Failed to get fresh references"); + } + + jest.spyOn(queries, "getDomain").mockResolvedValue(freshDomain); + jest.spyOn(queries, "getSequence").mockResolvedValue( + freshSequence as any, + ); + jest.spyOn(queries, "getUser") + .mockResolvedValueOnce(freshUser as any) + .mockResolvedValueOnce(freshCreator as any); + const deleteSpy = jest + .spyOn(queries, "deleteOngoingSequence") + .mockResolvedValue(undefined); + + await expect( + processOngoingSequence(ongoingSeq._id as any), + ).rejects.toThrow("SMTP Error"); + + // Should delete after exceeding retry limit + expect(deleteSpy).toHaveBeenCalled(); + + await OngoingSequenceModel.deleteOne({ _id: ongoingSeq._id }); + }); + + it("should return early if email has no content", async () => { + // Create sequence with valid content first, then modify in memory + const sequenceWithoutContent = (await (SequenceModel.create as any)( + { + domain: testDomain._id, + sequenceId: "sequence-no-content", + creatorId: TEST_CREATOR_ID, + type: "sequence", + emails: [ + { + emailId: "email-no-content", + subject: "No Content Email", + published: true, + delayInMillis: 86400000, + content: { + content: [], + style: { + structure: {}, + typography: {}, + colors: {}, + }, + meta: {}, + }, + }, + ], + emailsOrder: ["email-no-content"], + report: { + sequence: { + failed: [], + }, + }, + }, + )) as any; + + // Set content to undefined after creation (simulating missing content) + sequenceWithoutContent.emails[0].content = undefined; + + const ongoingSeq = await OngoingSequenceModel.create({ + domain: testDomain._id, + sequenceId: "sequence-no-content", + userId: TEST_USER_ID, + nextEmailScheduledTime: Date.now() - 1000, + retryCount: 0, + sentEmailIds: [], + }); + + // Use testDomain, testUser, testCreator directly since they're created in beforeAll + // and persist across tests (cleanup only clears collections, not the test data) + jest.spyOn(queries, "getDomain").mockResolvedValue(testDomain); + jest.spyOn(queries, "getSequence").mockResolvedValue( + sequenceWithoutContent, + ); + jest.spyOn(queries, "getUser") + .mockResolvedValueOnce(testUser) + .mockResolvedValueOnce(testCreator); + + await processOngoingSequence(ongoingSeq._id as any); + + // Should not send email + expect(mockedSendMail).not.toHaveBeenCalled(); + + await SequenceModel.deleteOne({ + sequenceId: "sequence-no-content", + }); + await OngoingSequenceModel.deleteOne({ _id: ongoingSeq._id }); + }); + }); + + describe("Mail rendering", () => { + it("should render email content with Liquid templates", async () => { + // Create a sequence with Liquid template variables in the email content + const sequenceWithTemplates = (await (SequenceModel.create as any)({ + domain: testDomain._id, + sequenceId: "sequence-templates", + creatorId: TEST_CREATOR_ID, + type: "sequence", + emails: [ + { + emailId: "email-template", + subject: "Hello {{subscriber.name}}", + published: true, + delayInMillis: 86400000, + content: { + content: [ + { + id: "block-1", + blockType: "text", + settings: { + content: + "Hello {{subscriber.name}}, your email is {{subscriber.email}}. Unsubscribe: {{unsubscribe_link}}", + }, + }, + ], + style: { + colors: { + background: "#ffffff", + foreground: "#000000", + border: "#e2e8f0", + accent: "#0284c7", + accentForeground: "#ffffff", + }, + typography: { + header: { + fontFamily: "Arial, sans-serif", + }, + text: { + fontFamily: "Arial, sans-serif", + }, + link: { + fontFamily: "Arial, sans-serif", + }, + }, + interactives: { + button: {}, + link: {}, + }, + structure: { + page: { + marginY: "20px", + }, + section: { + padding: { + x: "24px", + y: "16px", + }, + }, + }, + }, + meta: {}, + }, + }, + ], + emailsOrder: ["email-template"], + report: { + sequence: { + failed: [], + }, + }, + })) as any; + + const ongoingSeq = await OngoingSequenceModel.create({ + domain: testDomain._id, + sequenceId: "sequence-templates", + userId: TEST_USER_ID, + nextEmailScheduledTime: Date.now() - 1000, + retryCount: 0, + sentEmailIds: [], + }); + + // Get fresh references + const freshDomain = await (DomainModel.findById as any)( + testDomain._id, + ); + const freshSequence = await (SequenceModel.findOne as any)({ + sequenceId: "sequence-templates", + }); + const freshUser = await (UserModel.findOne as any)({ + userId: TEST_USER_ID, + }); + const freshCreator = await (UserModel.findOne as any)({ + userId: TEST_CREATOR_ID, + }); + + if (!freshDomain || !freshSequence || !freshUser || !freshCreator) { + throw new Error("Failed to get fresh references"); + } + + jest.spyOn(queries, "getDomain").mockResolvedValue(freshDomain); + jest.spyOn(queries, "getSequence").mockResolvedValue( + freshSequence as any, + ); + jest.spyOn(queries, "getUser") + .mockResolvedValueOnce(freshUser as any) + .mockResolvedValueOnce(freshCreator as any); + + await processOngoingSequence(ongoingSeq._id as any); + + // Verify email was sent + expect(mockedSendMail).toHaveBeenCalled(); + const sendMailCall = mockedSendMail.mock.calls[0][0]; + const htmlContent = sendMailCall.html; + + // Verify renderEmailToHtml produced valid HTML (not an error) + expect(htmlContent).not.toContain("

Error:"); + expect(htmlContent).toContain(""); + expect(htmlContent).toContain(" { + // Create a sequence with links in the content + const sequenceWithLinks = (await (SequenceModel.create as any)({ + domain: testDomain._id, + sequenceId: "sequence-links", + creatorId: TEST_CREATOR_ID, + type: "sequence", + emails: [ + { + emailId: "email-links", + subject: "Email with Links", + published: true, + delayInMillis: 86400000, + content: { + content: [ + { + id: "block-1", + blockType: "text", + settings: { + content: "Check out our website", + }, + }, + { + id: "block-2", + blockType: "link", + settings: { + url: "https://example.com/page1", + text: "Link 1", + }, + }, + { + id: "block-3", + blockType: "link", + settings: { + url: "https://example.com/page2", + text: "Link 2", + }, + }, + ], + style: { + colors: { + background: "#ffffff", + foreground: "#000000", + border: "#e2e8f0", + accent: "#0284c7", + accentForeground: "#ffffff", + }, + typography: { + header: { + fontFamily: "Arial, sans-serif", + }, + text: { + fontFamily: "Arial, sans-serif", + }, + link: { + fontFamily: "Arial, sans-serif", + }, + }, + interactives: { + button: {}, + link: {}, + }, + structure: { + page: { + marginY: "20px", + }, + section: { + padding: { + x: "24px", + y: "16px", + }, + }, + }, + }, + meta: {}, + }, + }, + ], + emailsOrder: ["email-links"], + report: { + sequence: { + failed: [], + }, + }, + })) as any; + + const ongoingSeq = await OngoingSequenceModel.create({ + domain: testDomain._id, + sequenceId: "sequence-links", + userId: TEST_USER_ID, + nextEmailScheduledTime: Date.now() - 1000, + retryCount: 0, + sentEmailIds: [], + }); + + // Get fresh references + const freshDomain = await (DomainModel.findById as any)( + testDomain._id, + ); + const freshSequence = await (SequenceModel.findOne as any)({ + sequenceId: "sequence-links", + }); + const freshUser = await (UserModel.findOne as any)({ + userId: TEST_USER_ID, + }); + const freshCreator = await (UserModel.findOne as any)({ + userId: TEST_CREATOR_ID, + }); + + if (!freshDomain || !freshSequence || !freshUser || !freshCreator) { + throw new Error("Failed to get fresh references"); + } + + jest.spyOn(queries, "getDomain").mockResolvedValue(freshDomain); + jest.spyOn(queries, "getSequence").mockResolvedValue( + freshSequence as any, + ); + jest.spyOn(queries, "getUser") + .mockResolvedValueOnce(freshUser as any) + .mockResolvedValueOnce(freshCreator as any); + + await processOngoingSequence(ongoingSeq._id as any); + + // Verify email was sent + expect(mockedSendMail).toHaveBeenCalled(); + const sendMailCall = mockedSendMail.mock.calls[0][0]; + const htmlContent = sendMailCall.html; + + // Verify renderEmailToHtml produced valid HTML (not an error) + expect(htmlContent).not.toContain("

Error:"); + expect(htmlContent).toContain(""); + expect(htmlContent).toContain(" tags with tracking URLs + expect(htmlContent).toContain("/api/track/click?d="); + expect(htmlContent).toContain("https://test.com/api/track/click"); + // Original URLs should NOT be directly in the HTML (they should be in the token) + // But the tracking URLs should be present + expect(htmlContent).toMatch( + /href=["']https:\/\/test\.com\/api\/track\/click\?d=/, + ); + // Verify links are properly rendered as tags + expect(htmlContent).toMatch( + /]*href=["']https:\/\/test\.com\/api\/track\/click\?d=/, + ); + + await SequenceModel.deleteOne({ + sequenceId: "sequence-links", + }); + await OngoingSequenceModel.deleteOne({ _id: ongoingSeq._id }); + await EmailDelivery.deleteMany({}); + }); + + it("should not rewrite mailto, tel, anchor, and API links", async () => { + // Create a sequence with various link types that should NOT be rewritten + const sequenceWithSpecialLinks = (await ( + SequenceModel.create as any + )({ + domain: testDomain._id, + sequenceId: "sequence-special-links", + creatorId: TEST_CREATOR_ID, + type: "sequence", + emails: [ + { + emailId: "email-special-links", + subject: "Email with Special Links", + published: true, + delayInMillis: 86400000, + content: { + content: [ + { + id: "block-1", + blockType: "link", + settings: { + url: "mailto:test@example.com", + text: "Email", + }, + }, + { + id: "block-2", + blockType: "link", + settings: { + url: "tel:+1234567890", + text: "Phone", + }, + }, + { + id: "block-3", + blockType: "link", + settings: { + url: "#section", + text: "Anchor", + }, + }, + { + id: "block-4", + blockType: "link", + settings: { + url: "/api/track/something", + text: "Track", + }, + }, + { + id: "block-5", + blockType: "link", + settings: { + url: "/api/unsubscribe/token", + text: "Unsubscribe", + }, + }, + ], + style: { + colors: { + background: "#ffffff", + foreground: "#000000", + border: "#e2e8f0", + accent: "#0284c7", + accentForeground: "#ffffff", + }, + typography: { + header: { + fontFamily: "Arial, sans-serif", + }, + text: { + fontFamily: "Arial, sans-serif", + }, + link: { + fontFamily: "Arial, sans-serif", + }, + }, + interactives: { + button: {}, + link: {}, + }, + structure: { + page: { + marginY: "20px", + }, + section: { + padding: { + x: "24px", + y: "16px", + }, + }, + }, + }, + meta: {}, + }, + }, + ], + emailsOrder: ["email-special-links"], + report: { + sequence: { + failed: [], + }, + }, + })) as any; + + const ongoingSeq = await OngoingSequenceModel.create({ + domain: testDomain._id, + sequenceId: "sequence-special-links", + userId: TEST_USER_ID, + nextEmailScheduledTime: Date.now() - 1000, + retryCount: 0, + sentEmailIds: [], + }); + + // Get fresh references + const freshDomain = await (DomainModel.findById as any)( + testDomain._id, + ); + const freshSequence = await (SequenceModel.findOne as any)({ + sequenceId: "sequence-special-links", + }); + const freshUser = await (UserModel.findOne as any)({ + userId: TEST_USER_ID, + }); + const freshCreator = await (UserModel.findOne as any)({ + userId: TEST_CREATOR_ID, + }); + + if (!freshDomain || !freshSequence || !freshUser || !freshCreator) { + throw new Error("Failed to get fresh references"); + } + + jest.spyOn(queries, "getDomain").mockResolvedValue(freshDomain); + jest.spyOn(queries, "getSequence").mockResolvedValue( + freshSequence as any, + ); + jest.spyOn(queries, "getUser") + .mockResolvedValueOnce(freshUser as any) + .mockResolvedValueOnce(freshCreator as any); + + await processOngoingSequence(ongoingSeq._id as any); + + // Verify email was sent + expect(mockedSendMail).toHaveBeenCalled(); + const sendMailCall = mockedSendMail.mock.calls[0][0]; + const htmlContent = sendMailCall.html; + + // Verify renderEmailToHtml produced valid HTML (not an error) + expect(htmlContent).not.toContain("

Error:"); + expect(htmlContent).toContain(""); + expect(htmlContent).toContain(" { + // Spy on renderEmailToHtml before calling processOngoingSequence + const emailEditorModule = await import("@courselit/email-editor"); + const renderEmailToHtmlSpy = jest.spyOn( + emailEditorModule, + "renderEmailToHtml", + ); + + const ongoingSeq = await OngoingSequenceModel.create({ + domain: testDomain._id, + sequenceId: TEST_SEQUENCE_ID, + userId: TEST_USER_ID, + nextEmailScheduledTime: Date.now() - 1000, + retryCount: 0, + sentEmailIds: [], + }); + + // Get fresh references + const freshDomain = await (DomainModel.findById as any)( + testDomain._id, + ); + const freshSequence = await (SequenceModel.findOne as any)({ + sequenceId: TEST_SEQUENCE_ID, + }); + const freshUser = await (UserModel.findOne as any)({ + userId: TEST_USER_ID, + }); + const freshCreator = await (UserModel.findOne as any)({ + userId: TEST_CREATOR_ID, + }); + + if (!freshDomain || !freshSequence || !freshUser || !freshCreator) { + throw new Error("Failed to get fresh references"); + } + + jest.spyOn(queries, "getDomain").mockResolvedValue(freshDomain); + jest.spyOn(queries, "getSequence").mockResolvedValue( + freshSequence as any, + ); + jest.spyOn(queries, "getUser") + .mockResolvedValueOnce(freshUser as any) + .mockResolvedValueOnce(freshCreator as any); + + await processOngoingSequence(ongoingSeq._id as any); + + // Verify email was sent + expect(mockedSendMail).toHaveBeenCalled(); + const sendMailCall = mockedSendMail.mock.calls[0][0]; + const htmlContent = sendMailCall.html; + + // Verify renderEmailToHtml produced valid HTML (not an error) + expect(htmlContent).not.toContain("

Error:"); + expect(htmlContent).toContain(""); + expect(htmlContent).toContain(" + block.blockType === "image" && + block.settings?.alt === "CourseLit Pixel", + ); + expect(hasPixelBlock).toBe(true); + + // Verify tracking pixel is included in the actual rendered HTML as an tag + expect(htmlContent).toContain("/api/track/open?d="); + expect(htmlContent).toContain('alt="CourseLit Pixel"'); + expect(htmlContent).toContain('height="1px"'); + expect(htmlContent).toContain('width="1px"'); + // Verify it's rendered as an tag + expect(htmlContent).toMatch( + /]*alt=["']CourseLit Pixel["'][^>]*>/, + ); + expect(htmlContent).toMatch( + /]*src=["'][^"']*\/api\/track\/open\?d=/, + ); + expect(htmlContent).toMatch(/width=["']1px["']/i); + expect(htmlContent).toMatch(/height=["']1px["']/i); + expect(htmlContent).toMatch(/alt=["']CourseLit Pixel["']/i); + // Verify it's an img tag + expect(htmlContent).toMatch(/]*\/api\/track\/open/); + + renderEmailToHtmlSpy.mockRestore(); + + await OngoingSequenceModel.deleteOne({ _id: ongoingSeq._id }); + await EmailDelivery.deleteMany({}); + }); + }); + + describe("getNextPublishedEmail", () => { + it("should return the first published email in order", () => { + const sequence: AdminSequence = { + sequenceId: "test-sequence", + creatorId: TEST_CREATOR_ID, + type: "sequence", + emailsOrder: ["email-1", "email-2"], + emails: [ + { + emailId: "email-1", + subject: "First", + published: true, + delayInMillis: 86400000, + content: { + content: [], + style: { + structure: {}, + typography: {}, + colors: {}, + }, + meta: {}, + }, + }, + { + emailId: "email-2", + subject: "Second", + published: true, + delayInMillis: 86400000, + content: { + content: [], + style: { + structure: {}, + typography: {}, + colors: {}, + }, + meta: {}, + }, + }, + ], + report: { + sequence: { + failed: [], + }, + }, + } as any; + + const ongoingSequence = { + sentEmailIds: [], + } as any; + + const result = getNextPublishedEmail(sequence, ongoingSequence); + + expect(result).toBeTruthy(); + expect(result?.emailId).toBe("email-1"); + }); + + it("should skip already sent emails", () => { + const sequence: AdminSequence = { + sequenceId: "test-sequence", + creatorId: TEST_CREATOR_ID, + type: "sequence", + emailsOrder: ["email-1", "email-2"], + emails: [ + { + emailId: "email-1", + subject: "First", + published: true, + delayInMillis: 86400000, + content: { + content: [], + style: { + structure: {}, + typography: {}, + colors: {}, + }, + meta: {}, + }, + }, + { + emailId: "email-2", + subject: "Second", + published: true, + delayInMillis: 86400000, + content: { + content: [], + style: { + structure: {}, + typography: {}, + colors: {}, + }, + meta: {}, + }, + }, + ], + report: { + sequence: { + failed: [], + }, + }, + } as any; + + const ongoingSequence = { + sentEmailIds: ["email-1"], + } as any; + + const result = getNextPublishedEmail(sequence, ongoingSequence); + + expect(result).toBeTruthy(); + expect(result?.emailId).toBe("email-2"); + }); + + it("should skip unpublished emails", () => { + const sequence: AdminSequence = { + sequenceId: "test-sequence", + creatorId: TEST_CREATOR_ID, + type: "sequence", + emailsOrder: ["email-1", "email-2"], + emails: [ + { + emailId: "email-1", + subject: "First", + published: false, // Not published + delayInMillis: 86400000, + content: { + content: [], + style: { + structure: {}, + typography: {}, + colors: {}, + }, + meta: {}, + }, + }, + { + emailId: "email-2", + subject: "Second", + published: true, + delayInMillis: 86400000, + content: { + content: [], + style: { + structure: {}, + typography: {}, + colors: {}, + }, + meta: {}, + }, + }, + ], + report: { + sequence: { + failed: [], + }, + }, + } as any; + + const ongoingSequence = { + sentEmailIds: [], + } as any; + + const result = getNextPublishedEmail(sequence, ongoingSequence); + + expect(result).toBeTruthy(); + expect(result?.emailId).toBe("email-2"); + }); + + it("should return null when all emails are sent", () => { + const sequence: AdminSequence = { + sequenceId: "test-sequence", + creatorId: TEST_CREATOR_ID, + type: "sequence", + emailsOrder: ["email-1", "email-2"], + emails: [ + { + emailId: "email-1", + subject: "First", + published: true, + delayInMillis: 86400000, + content: { + content: [], + style: { + structure: {}, + typography: {}, + colors: {}, + }, + meta: {}, + }, + }, + { + emailId: "email-2", + subject: "Second", + published: true, + delayInMillis: 86400000, + content: { + content: [], + style: { + structure: {}, + typography: {}, + colors: {}, + }, + meta: {}, + }, + }, + ], + report: { + sequence: { + failed: [], + }, + }, + } as any; + + const ongoingSequence = { + sentEmailIds: ["email-1", "email-2"], + } as any; + + const result = getNextPublishedEmail(sequence, ongoingSequence); + + expect(result).toBeNull(); + }); + + it("should return null when no published emails exist", () => { + const sequence: AdminSequence = { + sequenceId: "test-sequence", + creatorId: TEST_CREATOR_ID, + type: "sequence", + emailsOrder: ["email-1", "email-2"], + emails: [ + { + emailId: "email-1", + subject: "First", + published: false, + delayInMillis: 86400000, + content: { + content: [], + style: { + structure: {}, + typography: {}, + colors: {}, + }, + meta: {}, + }, + }, + { + emailId: "email-2", + subject: "Second", + published: false, + delayInMillis: 86400000, + content: { + content: [], + style: { + structure: {}, + typography: {}, + colors: {}, + }, + meta: {}, + }, + }, + ], + report: { + sequence: { + failed: [], + }, + }, + } as any; + + const ongoingSequence = { + sentEmailIds: [], + } as any; + + const result = getNextPublishedEmail(sequence, ongoingSequence); + + expect(result).toBeNull(); + }); + }); +}); diff --git a/apps/queue/src/domain/process-ongoing-sequences/index.ts b/apps/queue/src/domain/process-ongoing-sequences/index.ts new file mode 100644 index 000000000..09adb4b63 --- /dev/null +++ b/apps/queue/src/domain/process-ongoing-sequences/index.ts @@ -0,0 +1,43 @@ +import { logger } from "../../logger"; +import { getDueOngoingSequences } from "../queries"; +import { Worker } from "bullmq"; +import redis from "../../redis"; +import sequenceQueue from "../sequence-queue"; +import { processOngoingSequence } from "./process-ongoing-sequence"; + +if (process.env.NODE_ENV !== "test") { + new Worker( + "sequence", + async (job) => { + const ongoingSequenceId = job.data; + try { + await processOngoingSequence(ongoingSequenceId); + } catch (err: any) { + logger.error(err); + } + }, + { connection: redis }, + ); +} + +export async function processOngoingSequences(): Promise { + if (!process.env.PIXEL_SIGNING_SECRET) { + throw new Error( + "PIXEL_SIGNING_SECRET environment variable is not defined", + ); + } + // eslint-disable-next-line no-constant-condition + while (true) { + // eslint-disable-next-line no-console + console.log( + `Starting process of ongoing sequence at ${new Date().toDateString()}`, + ); + + const dueOngoingSequences = await getDueOngoingSequences(); + for (const ongoingSequence of dueOngoingSequences) { + sequenceQueue.add("sequence", ongoingSequence.id); + } + + await new Promise((resolve) => setTimeout(resolve, 60 * 1000)); + } +} diff --git a/apps/queue/src/domain/process-ongoing-sequences.ts b/apps/queue/src/domain/process-ongoing-sequences/process-ongoing-sequence.ts similarity index 80% rename from apps/queue/src/domain/process-ongoing-sequences.ts rename to apps/queue/src/domain/process-ongoing-sequences/process-ongoing-sequence.ts index 7136fb8d2..03c83c8f2 100644 --- a/apps/queue/src/domain/process-ongoing-sequences.ts +++ b/apps/queue/src/domain/process-ongoing-sequences/process-ongoing-sequence.ts @@ -1,70 +1,32 @@ import { Email } from "@courselit/common-models"; import OngoingSequenceModel, { OngoingSequence, -} from "./model/ongoing-sequence"; -import { logger } from "../logger"; -import { sequenceBounceLimit } from "../constants"; +} from "@/domain/model/ongoing-sequence"; +import SequenceModel from "@/domain/model/sequence"; +import { sequenceBounceLimit } from "@/constants"; import { deleteOngoingSequence, - getDueOngoingSequences, getSequence, getUser, removeRuleForBroadcast, updateSequenceSentAt, getDomain, -} from "./queries"; -import { sendMail } from "../mail"; -import { Liquid } from "liquidjs"; -import { Worker } from "bullmq"; -import redis from "../redis"; +} from "@/domain/queries"; +import { sendMail } from "@/mail"; import mongoose from "mongoose"; -import sequenceQueue from "./sequence-queue"; -import EmailDelivery from "./model/email-delivery"; +import EmailDelivery from "@/domain/model/email-delivery"; import { AdminSequence, InternalUser } from "@courselit/common-logic"; import { Email as EmailType, renderEmailToHtml } from "@courselit/email-editor"; -import { getUnsubLink } from "../utils/get-unsub-link"; -import { getSiteUrl } from "../utils/get-site-url"; +import { getUnsubLink } from "@/utils/get-unsub-link"; +import { getSiteUrl } from "@/utils/get-site-url"; import { jwtUtils } from "@courselit/utils"; import { JSDOM } from "jsdom"; -import { DomainDocument } from "./model/domain"; +import { DomainDocument } from "@/domain/model/domain"; +import { Liquid } from "liquidjs"; +import { logger } from "@/logger"; const liquidEngine = new Liquid(); -new Worker( - "sequence", - async (job) => { - const ongoingSequenceId = job.data; - try { - await processOngoingSequence(ongoingSequenceId); - } catch (err: any) { - logger.error(err); - } - }, - { connection: redis }, -); - -export async function processOngoingSequences(): Promise { - if (!process.env.PIXEL_SIGNING_SECRET) { - throw new Error( - "PIXEL_SIGNING_SECRET environment variable is not defined", - ); - } - // eslint-disable-next-line no-constant-condition - while (true) { - // eslint-disable-next-line no-console - console.log( - `Starting process of ongoing sequence at ${new Date().toDateString()}`, - ); - - const dueOngoingSequences = await getDueOngoingSequences(); - for (const ongoingSequence of dueOngoingSequences) { - sequenceQueue.add("sequence", ongoingSequence.id); - } - - await new Promise((resolve) => setTimeout(resolve, 60 * 1000)); - } -} - -async function processOngoingSequence( +export async function processOngoingSequence( ongoingSequenceId: mongoose.Types.ObjectId, ) { const ongoingSequence = @@ -80,7 +42,10 @@ async function processOngoingSequence( !domain.quota.mail || !domain.settings?.mailingAddress ) { - console.log(`Invalid domain settings for "${domain.name}"`, domain); // eslint-disable-line no-console + console.log( + `Invalid domain settings for "${domain?.name || "unknown"}"`, + domain, + ); // eslint-disable-line no-console return; } if ( @@ -125,7 +90,7 @@ async function processOngoingSequence( } } -function getNextPublishedEmail( +export function getNextPublishedEmail( sequence: AdminSequence, ongoingSequence: OngoingSequence, ) { @@ -264,11 +229,15 @@ async function attemptMailSending({ } catch (err: any) { ongoingSequence.retryCount++; if (ongoingSequence.retryCount >= sequenceBounceLimit) { - sequence.report.sequence.failed = [ - ...sequence.report.sequence.failed, - ongoingSequence.userId, - ]; - await (sequence as any).save(); + // Use findOneAndUpdate to atomically update and avoid version conflicts + await (SequenceModel.findOneAndUpdate as any)( + { sequenceId: sequence.sequenceId }, + { + $addToSet: { + "report.sequence.failed": ongoingSequence.userId, + }, + }, + ); await deleteOngoingSequence(ongoingSequence.sequenceId); } else { await ongoingSequence.save(); diff --git a/apps/queue/tsconfig.json b/apps/queue/tsconfig.json index c9705dfc8..ab1ef5cd9 100644 --- a/apps/queue/tsconfig.json +++ b/apps/queue/tsconfig.json @@ -4,7 +4,10 @@ "compilerOptions": { "outDir": "./dist", "lib": ["dom"], - "strict": false + "strict": false, + "paths": { + "@/*": ["./src/*"] + } }, "include": ["src/**/*", "additional.d.ts"] } diff --git a/apps/queue/tsup.config.ts b/apps/queue/tsup.config.ts index 28163091e..2dd2a5229 100644 --- a/apps/queue/tsup.config.ts +++ b/apps/queue/tsup.config.ts @@ -3,7 +3,7 @@ import { defineConfig, Options } from "tsup"; export default defineConfig((options: Options) => ({ treeshake: true, splitting: true, - entry: ["src/**/*.ts"], + entry: ["src/**/*.ts", "!src/**/*.test.ts", "!src/**/__tests__/**/*.ts"], format: ["esm"], dts: true, minify: true, diff --git a/apps/web/graphql/courses/__tests__/delete-course.test.ts b/apps/web/graphql/courses/__tests__/delete-course.test.ts index aaf887eef..09d7ab961 100644 --- a/apps/web/graphql/courses/__tests__/delete-course.test.ts +++ b/apps/web/graphql/courses/__tests__/delete-course.test.ts @@ -23,6 +23,11 @@ jest.mock("@/payments-new", () => ({ }), })); +const DELETE_COURSE_SUITE_PREFIX = `delete-course-${Date.now()}`; +const dcId = (suffix: string) => `${DELETE_COURSE_SUITE_PREFIX}-${suffix}`; +const dcEmail = (suffix: string) => + `${suffix}-${DELETE_COURSE_SUITE_PREFIX}@example.com`; + describe("deleteCourse - Comprehensive Test Suite", () => { let testDomain: any; let adminUser: any; @@ -32,31 +37,31 @@ describe("deleteCourse - Comprehensive Test Suite", () => { beforeAll(async () => { // Create unique test domain testDomain = await DomainModel.create({ - name: `test-domain-dc-${Date.now()}`, - email: "test@example.com", + name: dcId("domain"), + email: dcEmail("domain"), }); // Create admin user with course management permissions adminUser = await UserModel.create({ domain: testDomain._id, - userId: "admin-user", - email: "admin@example.com", + userId: dcId("admin-user"), + email: dcEmail("admin"), name: "Admin User", permissions: [constants.permissions.manageAnyCourse], active: true, - unsubscribeToken: "unsubscribe-admin", + unsubscribeToken: dcId("unsubscribe-admin"), purchases: [], }); // Create regular user (student) regularUser = await UserModel.create({ domain: testDomain._id, - userId: "regular-user", - email: "regular@example.com", + userId: dcId("regular-user"), + email: dcEmail("regular"), name: "Regular User", permissions: [], active: true, - unsubscribeToken: "unsubscribe-regular", + unsubscribeToken: dcId("unsubscribe-regular"), purchases: [], }); @@ -64,7 +69,7 @@ describe("deleteCourse - Comprehensive Test Suite", () => { // Use unique planId to avoid conflicts when running tests in parallel await PaymentPlanModel.create({ domain: testDomain._id, - planId: `internal-plan-dc-${Date.now()}`, + planId: dcId("internal-plan"), userId: adminUser.userId, entityId: "internal", entityType: Constants.MembershipEntityType.COURSE, @@ -168,12 +173,12 @@ describe("deleteCourse - Comprehensive Test Suite", () => { it("should allow owner with manageCourse permission to delete their own course", async () => { const ownerUser = await UserModel.create({ domain: testDomain._id, - userId: "owner-user", - email: "owner@example.com", + userId: dcId("owner-user"), + email: dcEmail("owner"), name: "Owner User", permissions: [constants.permissions.manageCourse], active: true, - unsubscribeToken: "unsubscribe-owner", + unsubscribeToken: dcId("unsubscribe-owner"), purchases: [], }); @@ -1193,11 +1198,11 @@ describe("deleteCourse - Comprehensive Test Suite", () => { const updatedUser = await UserModel.findOne({ userId: regularUser.userId, }); - expect( - updatedUser?.purchases.some( + const hasCoursePurchase = + updatedUser?.purchases?.some( (p: any) => p.courseId === course.courseId, - ), - ).toBe(false); + ) ?? false; + expect(hasCoursePurchase).toBe(false); // Verify media deletion expect(deleteMedia).toHaveBeenCalledWith("featured-media"); diff --git a/apps/web/graphql/users/__tests__/delete-user.test.ts b/apps/web/graphql/users/__tests__/delete-user.test.ts index e87476fa2..a069fda55 100644 --- a/apps/web/graphql/users/__tests__/delete-user.test.ts +++ b/apps/web/graphql/users/__tests__/delete-user.test.ts @@ -51,6 +51,12 @@ jest.mock("../../communities/logic", () => ({ deleteCommunityPosts: jest.fn().mockResolvedValue(true), })); +const DELETE_USER_SUITE_PREFIX = `delete-user-${Date.now()}`; +const duId = (suffix: string) => `${DELETE_USER_SUITE_PREFIX}-${suffix}`; +const duEmail = (suffix: string) => + `${suffix}-${DELETE_USER_SUITE_PREFIX}@example.com`; +const DU_OTHER_USER_ID = duId("other-user"); + const { permissions } = constants; describe("deleteUser - Comprehensive Test Suite", () => { @@ -62,8 +68,8 @@ describe("deleteUser - Comprehensive Test Suite", () => { beforeAll(async () => { // Create test domain with unique name to avoid conflicts with other tests testDomain = await DomainModel.create({ - name: `Test Domain Delete User ${Date.now()}`, - email: `delete-user-test-${Date.now()}@example.com`, + name: duId("domain"), + email: duEmail("domain"), tags: ["tag1", "tag2"], }); }); @@ -72,9 +78,9 @@ describe("deleteUser - Comprehensive Test Suite", () => { // Create admin user (deleter) adminUser = await UserModel.create({ domain: testDomain._id, - userId: "admin-user", + userId: duId("admin-user"), name: "Admin User", - email: "admin@test.com", + email: duEmail("admin"), active: true, permissions: [ permissions.manageUsers, @@ -82,21 +88,21 @@ describe("deleteUser - Comprehensive Test Suite", () => { permissions.manageSite, ], purchases: [], - unsubscribeToken: "unsubscribe-token-admin", + unsubscribeToken: duId("unsubscribe-admin"), }); // Create target user (to be deleted) targetUser = await UserModel.create({ domain: testDomain._id, - userId: "target-user", + userId: duId("target-user"), name: "Target User", - email: "target@test.com", + email: duEmail("target"), active: true, permissions: [permissions.enrollInCourse], purchases: [], - unsubscribeToken: "unsubscribe-token-target", + unsubscribeToken: duId("unsubscribe-target"), avatar: { - mediaId: "avatar-123", + mediaId: duId("avatar"), file: "avatar.png", originalFileName: "avatar.png", mimeType: "image/png", @@ -168,11 +174,11 @@ describe("deleteUser - Comprehensive Test Suite", () => { it("should require manageUsers permission", async () => { const unauthorizedUser = await UserModel.create({ domain: testDomain._id, - userId: "unauth-user", - email: "unauth@test.com", + userId: duId("unauth-user"), + email: duEmail("unauth"), permissions: [permissions.enrollInCourse], purchases: [], - unsubscribeToken: "unsubscribe-token-unauth", + unsubscribeToken: duId("unsubscribe-unauth"), }); const unauthorizedCtx = { @@ -196,7 +202,7 @@ describe("deleteUser - Comprehensive Test Suite", () => { it("should throw error for non-existent user", async () => { await expect( - deleteUser("non-existent-user", mockCtx), + deleteUser(duId("non-existent-user"), mockCtx), ).rejects.toThrow(responses.user_not_found); }); @@ -311,7 +317,7 @@ describe("deleteUser - Comprehensive Test Suite", () => { creatorId: targetUser.userId, type: "broadcast", emails: [], - entrants: [targetUser.userId, "other-user"], + entrants: [targetUser.userId, DU_OTHER_USER_ID], from: { name: "Test", email: "test@test.com" }, }); @@ -322,7 +328,7 @@ describe("deleteUser - Comprehensive Test Suite", () => { }); expect(updatedSequence?.creatorId).toBe(adminUser.userId); expect(updatedSequence?.entrants).not.toContain(targetUser.userId); - expect(updatedSequence?.entrants).toContain("other-user"); + expect(updatedSequence?.entrants).toContain(DU_OTHER_USER_ID); }); it("should migrate user segments to deleter", async () => { @@ -518,7 +524,7 @@ describe("deleteUser - Comprehensive Test Suite", () => { await NotificationModel.create({ domain: testDomain._id, notificationId: "notif-1", - userId: "other-user", + userId: DU_OTHER_USER_ID, forUserId: targetUser.userId, entityAction: Constants.NotificationEntityAction.COMMUNITY_POSTED, @@ -538,7 +544,7 @@ describe("deleteUser - Comprehensive Test Suite", () => { domain: testDomain._id, notificationId: "notif-2", userId: targetUser.userId, - forUserId: "other-user", + forUserId: DU_OTHER_USER_ID, entityAction: Constants.NotificationEntityAction.COMMUNITY_POSTED, entityId: "post-123", @@ -702,11 +708,11 @@ describe("deleteUser - Comprehensive Test Suite", () => { await CommunityPostModel.create({ domain: testDomain._id, postId: "post-123", - userId: "other-user", + userId: DU_OTHER_USER_ID, communityId: "comm-123", title: "Test Post", content: "Content", - likes: [targetUser.userId, "other-user"], + likes: [targetUser.userId, DU_OTHER_USER_ID], }); await deleteUser(targetUser.userId, mockCtx); @@ -715,7 +721,7 @@ describe("deleteUser - Comprehensive Test Suite", () => { postId: "post-123", }); expect(post?.likes).not.toContain(targetUser.userId); - expect(post?.likes).toContain("other-user"); + expect(post?.likes).toContain(DU_OTHER_USER_ID); }); it("should remove user from comment likes arrays", async () => { @@ -724,9 +730,9 @@ describe("deleteUser - Comprehensive Test Suite", () => { commentId: "comment-123", postId: "post-123", communityId: "comm-123", - userId: "other-user", + userId: DU_OTHER_USER_ID, content: "Test Comment", - likes: [targetUser.userId, "other-user"], + likes: [targetUser.userId, DU_OTHER_USER_ID], replies: [], }); @@ -736,7 +742,7 @@ describe("deleteUser - Comprehensive Test Suite", () => { commentId: "comment-123", }); expect(comment?.likes).not.toContain(targetUser.userId); - expect(comment?.likes).toContain("other-user"); + expect(comment?.likes).toContain(DU_OTHER_USER_ID); }); it("should remove user from reply likes arrays", async () => { @@ -745,15 +751,15 @@ describe("deleteUser - Comprehensive Test Suite", () => { commentId: "comment-123", postId: "post-123", communityId: "comm-123", - userId: "other-user", + userId: DU_OTHER_USER_ID, content: "Test Comment", likes: [], replies: [ { replyId: "reply-123", - userId: "other-user", + userId: DU_OTHER_USER_ID, content: "Reply content", - likes: [targetUser.userId, "other-user"], + likes: [targetUser.userId, DU_OTHER_USER_ID], deleted: false, }, ], @@ -766,7 +772,7 @@ describe("deleteUser - Comprehensive Test Suite", () => { }); const reply = comment?.replies[0]; expect(reply?.likes).not.toContain(targetUser.userId); - expect(reply?.likes).toContain("other-user"); + expect(reply?.likes).toContain(DU_OTHER_USER_ID); }); it("should delete memberships and associated invoices", async () => { @@ -858,7 +864,7 @@ describe("deleteUser - Comprehensive Test Suite", () => { it("should delete user avatar media", async () => { await deleteUser(targetUser.userId, mockCtx); - expect(deleteMedia).toHaveBeenCalledWith("avatar-123"); + expect(deleteMedia).toHaveBeenCalledWith(duId("avatar")); }); it("should delete the user document", async () => { @@ -882,7 +888,7 @@ describe("deleteUser - Comprehensive Test Suite", () => { creatorId: adminUser.userId, type: "broadcast", emails: [], - entrants: [targetUser.userId, "other-user"], + entrants: [targetUser.userId, DU_OTHER_USER_ID], from: { name: "Test", email: "test@test.com" }, }); @@ -892,7 +898,7 @@ describe("deleteUser - Comprehensive Test Suite", () => { sequenceId: "du-seq-123", }); expect(sequence?.entrants).not.toContain(targetUser.userId); - expect(sequence?.entrants).toContain("other-user"); + expect(sequence?.entrants).toContain(DU_OTHER_USER_ID); }); it("should remove user from course customers", async () => { @@ -907,7 +913,7 @@ describe("deleteUser - Comprehensive Test Suite", () => { costType: "free", cost: 0, published: true, - customers: [targetUser.userId, "other-user"], + customers: [targetUser.userId, DU_OTHER_USER_ID], }); await deleteUser(targetUser.userId, mockCtx); @@ -916,7 +922,7 @@ describe("deleteUser - Comprehensive Test Suite", () => { courseId: "du-course-123", }); expect(course?.customers).not.toContain(targetUser.userId); - expect(course?.customers).toContain("other-user"); + expect(course?.customers).toContain(DU_OTHER_USER_ID); }); }); @@ -976,7 +982,7 @@ describe("deleteUser - Comprehensive Test Suite", () => { domain: testDomain._id, notificationId: "notif-1", userId: targetUser.userId, - forUserId: "other-user", + forUserId: DU_OTHER_USER_ID, entityAction: Constants.NotificationEntityAction.COMMUNITY_POSTED, entityId: "post-123", @@ -1016,7 +1022,7 @@ describe("deleteUser - Comprehensive Test Suite", () => { expect(user).toBeNull(); // Verify avatar deleted - expect(deleteMedia).toHaveBeenCalledWith("avatar-123"); + expect(deleteMedia).toHaveBeenCalledWith(duId("avatar")); }); it("should successfully delete user with no owned entities", async () => { diff --git a/apps/web/next-env.d.ts b/apps/web/next-env.d.ts index 9edff1c7c..c4b7818fb 100644 --- a/apps/web/next-env.d.ts +++ b/apps/web/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/types/routes.d.ts"; +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/jest.config.js b/jest.config.js index 5b8205b44..c87df919c 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,5 +2,6 @@ module.exports = { projects: [ "/apps/web/jest.client.config.ts", "/apps/web/jest.server.config.ts", + "/apps/queue/jest.config.ts", ], }; diff --git a/packages/components-library/package.json b/packages/components-library/package.json index 0676711fa..7f8240b59 100644 --- a/packages/components-library/package.json +++ b/packages/components-library/package.json @@ -85,7 +85,7 @@ "currency-symbol-map": "^5.1.0", "lodash.debounce": "^4.0.8", "lucide-react": "^0.553.0", - "react-dom": "^18.2.0", + "react-dom": "^19.2.0", "tailwind-merge": "^2.2.0", "tailwindcss-animate": "^1.0.7", "tus-js-client": "^4.3.1" diff --git a/packages/email-editor/package.json b/packages/email-editor/package.json index 5cc4bc808..5553202f0 100644 --- a/packages/email-editor/package.json +++ b/packages/email-editor/package.json @@ -58,8 +58,8 @@ "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-switch": "^1.2.5", "@radix-ui/react-tooltip": "^1.2.7", - "@react-email/components": "^0.0.42", - "@react-email/render": "^1.1.2", + "@react-email/components": "^1.0.1", + "@react-email/render": "^2.0.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.544.0", @@ -68,6 +68,6 @@ "uuid": "^11.1.0" }, "peerDependencies": { - "react": ">=18.0.0" + "react": "^19.2.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ae8d3c5a6..477ff10a5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -165,12 +165,24 @@ importers: specifier: ^3.22.4 version: 3.24.3 devDependencies: + '@shelf/jest-mongodb': + specifier: ^5.2.2 + version: 5.2.2(@aws-sdk/credential-providers@3.797.0)(jest-environment-node@29.7.0)(mongodb@6.16.0(@aws-sdk/credential-providers@3.797.0)(socks@2.8.4))(socks@2.8.4) '@types/express': specifier: ^4.17.17 version: 4.17.21 + '@types/jest': + specifier: ^29.5.14 + version: 29.5.14 '@types/nodemailer': specifier: ^6.4.8 version: 6.4.17 + mongodb-memory-server: + specifier: ^10.1.4 + version: 10.1.4(@aws-sdk/credential-providers@3.797.0)(socks@2.8.4) + ts-jest: + specifier: ^29.4.4 + version: 29.4.4(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.19.12)(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.0)(typescript@5.9.3)))(typescript@5.9.3) tsconfig: specifier: workspace:^ version: link:../../packages/tsconfig @@ -545,67 +557,67 @@ importers: version: link:../utils '@dnd-kit/core': specifier: ^6.1.0 - version: 6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 6.3.1(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@dnd-kit/sortable': specifier: ^8.0.0 - version: 8.0.0(@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + version: 8.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.0(react@18.3.1))(react@18.3.1))(react@18.3.1) '@dnd-kit/utilities': specifier: ^3.2.2 version: 3.2.2(react@18.3.1) '@radix-ui/react-accordion': specifier: ^1.1.2 - version: 1.2.8(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 1.2.8(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-alert-dialog': specifier: ^1.1.11 - version: 1.1.11(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 1.1.11(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-avatar': specifier: ^1.0.4 - version: 1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-checkbox': specifier: ^1.0.4 - version: 1.2.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 1.2.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-dialog': specifier: ^1.1.14 - version: 1.1.14(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 1.1.14(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-dropdown-menu': specifier: ^2.0.6 - version: 2.1.12(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 2.1.12(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-form': specifier: ^0.0.3 - version: 0.0.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 0.0.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-label': specifier: ^2.1.2 - version: 2.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 2.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-popover': specifier: ^1.1.14 - version: 1.1.14(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 1.1.14(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-progress': specifier: ^1.1.7 - version: 1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-scroll-area': specifier: ^1.0.5 - version: 1.2.6(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 1.2.6(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-select': specifier: ^2.1.6 - version: 2.2.2(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 2.2.2(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-slider': specifier: ^1.1.2 - version: 1.3.2(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 1.3.2(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-slot': specifier: ^1.2.3 version: 1.2.3(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-switch': specifier: ^1.1.3 - version: 1.2.5(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 1.2.5(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-tabs': specifier: ^1.1.3 - version: 1.1.9(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 1.1.9(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-toast': specifier: ^1.2.2 - version: 1.2.11(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 1.2.11(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-tooltip': specifier: ^1.0.7 - version: 1.2.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 1.2.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) class-variance-authority: specifier: ^0.7.0 version: 0.7.1 @@ -614,7 +626,7 @@ importers: version: 2.1.1 cmdk: specifier: ^1.1.1 - version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) currency-symbol-map: specifier: ^5.1.0 version: 5.1.0 @@ -626,13 +638,13 @@ importers: version: 0.553.0(react@18.3.1) next: specifier: '*' - version: 15.5.3(@babel/core@7.26.10)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 15.5.3(@babel/core@7.26.10)(babel-plugin-macros@3.1.0)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) react: specifier: '*' version: 18.3.1 react-dom: - specifier: ^18.2.0 - version: 18.3.1(react@18.3.1) + specifier: ^19.2.0 + version: 19.2.0(react@18.3.1) tailwind-merge: specifier: ^2.2.0 version: 2.6.0 @@ -675,34 +687,34 @@ importers: dependencies: '@radix-ui/react-dialog': specifier: ^1.1.14 - version: 1.1.14(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) + version: 1.1.14(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-label': specifier: ^2.1.4 - version: 2.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) + version: 2.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-popover': specifier: ^1.1.14 - version: 1.1.14(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) + version: 1.1.14(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-select': specifier: ^2.2.2 - version: 2.2.2(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) + version: 2.2.2(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-slider': specifier: ^1.3.2 - version: 1.3.2(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) + version: 1.3.2(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-slot': specifier: ^1.2.3 - version: 1.2.3(@types/react@18.3.7)(react@18.3.1) + version: 1.2.3(@types/react@18.3.7)(react@19.2.0) '@radix-ui/react-switch': specifier: ^1.2.5 - version: 1.2.5(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) + version: 1.2.5(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-tooltip': specifier: ^1.2.7 - version: 1.2.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) + version: 1.2.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@react-email/components': - specifier: ^0.0.42 - version: 0.0.42(react-dom@19.2.0(react@18.3.1))(react@18.3.1) + specifier: ^1.0.1 + version: 1.0.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@react-email/render': - specifier: ^1.1.2 - version: 1.1.2(react-dom@19.2.0(react@18.3.1))(react@18.3.1) + specifier: ^2.0.0 + version: 2.0.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -711,10 +723,10 @@ importers: version: 2.1.1 lucide-react: specifier: ^0.544.0 - version: 0.544.0(react@18.3.1) + version: 0.544.0(react@19.2.0) react: - specifier: '>=18.0.0' - version: 18.3.1 + specifier: ^19.2.0 + version: 19.2.0 tailwind-merge: specifier: ^3.3.1 version: 3.3.1 @@ -3957,20 +3969,20 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} - '@react-email/body@0.0.11': - resolution: {integrity: sha512-ZSD2SxVSgUjHGrB0Wi+4tu3MEpB4fYSbezsFNEJk2xCWDBkFiOeEsjTmR5dvi+CxTK691hQTQlHv0XWuP7ENTg==} + '@react-email/body@0.2.0': + resolution: {integrity: sha512-9GCWmVmKUAoRfloboCd+RKm6X17xn7eGL7HnpAZUnjBXBilWCxsKnLMTC/ixSHDKS/A/057M1Tx6ZUXd89sVBw==} peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-email/button@0.0.19': - resolution: {integrity: sha512-HYHrhyVGt7rdM/ls6FuuD6XE7fa7bjZTJqB2byn6/oGsfiEZaogY77OtoLL/mrQHjHjZiJadtAMSik9XLcm7+A==} + '@react-email/button@0.2.0': + resolution: {integrity: sha512-8i+v6cMxr2emz4ihCrRiYJPp2/sdYsNNsBzXStlcA+/B9Umpm5Jj3WJKYpgTPM+aeyiqlG/MMI1AucnBm4f1oQ==} engines: {node: '>=18.0.0'} peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-email/code-block@0.0.13': - resolution: {integrity: sha512-4DE4yPSgKEOnZMzcrDvRuD6mxsNxOex0hCYEG9F9q23geYgb2WCCeGBvIUXVzK69l703Dg4Vzrd5qUjl+JfcwA==} - engines: {node: '>=18.0.0'} + '@react-email/code-block@0.2.0': + resolution: {integrity: sha512-eIrPW9PIFgDopQU0e/OPpwCW2QWQDtNZDSsiN4sJO8KdMnWWnXJicnRfzrit5rHwFo+Y98i+w/Y5ScnBAFr1dQ==} + engines: {node: '>=22.0.0'} peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc @@ -3986,9 +3998,9 @@ packages: peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-email/components@0.0.42': - resolution: {integrity: sha512-KZtf7RjCoLgEwa5swrsbEF/liGrmfMlZCDNDXqaAjlgF3iRq0h5KY/HNMs6LbLmW7fEneRhnynrwApwbkEwe+A==} - engines: {node: '>=18.0.0'} + '@react-email/components@1.0.1': + resolution: {integrity: sha512-HnL0Y/up61sOBQT2cQg9N/kCoW0bP727gDs2MkFWQYELg6+iIHidMDvENXFC0f1ZE6hTB+4t7sszptvTcJWsDA==} + engines: {node: '>=22.0.0'} peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc @@ -4039,9 +4051,9 @@ packages: peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-email/markdown@0.0.15': - resolution: {integrity: sha512-UQA9pVm5sbflgtg3EX3FquUP4aMBzmLReLbGJ6DZQZnAskBF36aI56cRykDq1o+1jT+CKIK1CducPYziaXliag==} - engines: {node: '>=18.0.0'} + '@react-email/markdown@0.0.17': + resolution: {integrity: sha512-6op3AfsBC9BJKkhG+eoMFRFWlr0/f3FYbtQrK+VhGzJocEAY0WINIFN+W8xzXr//3IL0K/aKtnH3FtpIuescQQ==} + engines: {node: '>=22.0.0'} peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc @@ -4051,9 +4063,9 @@ packages: peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-email/render@1.1.2': - resolution: {integrity: sha512-RnRehYN3v9gVlNMehHPHhyp2RQo7+pSkHDtXPvg3s0GbzM9SQMW4Qrf8GRNvtpLC4gsI+Wt0VatNRUFqjvevbw==} - engines: {node: '>=18.0.0'} + '@react-email/render@2.0.0': + resolution: {integrity: sha512-rdjNj6iVzv8kRKDPFas+47nnoe6B40+nwukuXwY4FCwM7XBg6tmYr+chQryCuavUj2J65MMf6fztk1bxOUiSVA==} + engines: {node: '>=22.0.0'} peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^18.0 || ^19.0 || ^19.0.0-rc @@ -4070,11 +4082,43 @@ packages: peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-email/tailwind@1.0.5': - resolution: {integrity: sha512-BH00cZSeFfP9HiDASl+sPHi7Hh77W5nzDgdnxtsVr/m3uQD9g180UwxcE3PhOfx0vRdLzQUU8PtmvvDfbztKQg==} - engines: {node: '>=18.0.0'} - peerDependencies: + '@react-email/tailwind@2.0.1': + resolution: {integrity: sha512-/xq0IDYVY7863xPY7cdI45Xoz7M6CnIQBJcQvbqN7MNVpopfH9f+mhjayV1JGfKaxlGWuxfLKhgi9T2shsnEFg==} + engines: {node: '>=22.0.0'} + peerDependencies: + '@react-email/body': 0.2.0 + '@react-email/button': 0.2.0 + '@react-email/code-block': 0.2.0 + '@react-email/code-inline': 0.0.5 + '@react-email/container': 0.0.15 + '@react-email/heading': 0.0.15 + '@react-email/hr': 0.0.11 + '@react-email/img': 0.0.11 + '@react-email/link': 0.0.12 + '@react-email/preview': 0.0.13 + '@react-email/text': 0.1.5 react: ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@react-email/body': + optional: true + '@react-email/button': + optional: true + '@react-email/code-block': + optional: true + '@react-email/code-inline': + optional: true + '@react-email/container': + optional: true + '@react-email/heading': + optional: true + '@react-email/hr': + optional: true + '@react-email/img': + optional: true + '@react-email/link': + optional: true + '@react-email/preview': + optional: true '@react-email/text@0.1.5': resolution: {integrity: sha512-o5PNHFSE085VMXayxH+SJ1LSOtGsTv+RpNKnTiJDrJUwoBu77G3PlKOsZZQHCNyD28WsQpl9v2WcJLbQudqwPg==} @@ -6935,9 +6979,6 @@ packages: extract-domain@2.2.1: resolution: {integrity: sha512-lOq1adCJha0tFFBci4quxC4XLa6+Rs2WgAwTo9qbO9OsElvJmGgCvOzmHo/yg5CiqeP4+sHjkXYGkrCcIEprMg==} - fast-deep-equal@2.0.1: - resolution: {integrity: sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==} - fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -8211,16 +8252,16 @@ packages: markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + marked@15.0.12: + resolution: {integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==} + engines: {node: '>= 18'} + hasBin: true + marked@4.3.0: resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==} engines: {node: '>= 12'} hasBin: true - marked@7.0.4: - resolution: {integrity: sha512-t8eP0dXRJMtMvBojtkcsA7n48BkauktUKzfkPSCq85ZMTJ0v76Rke4DYz01omYpPTUh4p/f7HePgRo3ebG8+QQ==} - engines: {node: '>= 16'} - hasBin: true - match-sorter@6.3.4: resolution: {integrity: sha512-jfZW7cWS5y/1xswZo8VBOdudUiSd9nifYRWphc9M5D/ee4w4AoXLgBEdRbgVaxbMuagBPeUC5y2Hi8DO6o9aDg==} @@ -8235,11 +8276,6 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} - md-to-react-email@5.0.5: - resolution: {integrity: sha512-OvAXqwq57uOk+WZqFFNCMZz8yDp8BD3WazW1wAKHUrPbbdr89K9DWS6JXY09vd9xNdPNeurI8DU/X4flcfaD8A==} - peerDependencies: - react: ^18.0 || ^19.0 - mdast-util-definitions@5.1.2: resolution: {integrity: sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==} @@ -9318,9 +9354,6 @@ packages: react-is@19.1.0: resolution: {integrity: sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==} - react-promise-suspense@0.3.4: - resolution: {integrity: sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ==} - react-remove-scroll-bar@2.3.8: resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} engines: {node: '>=10'} @@ -10055,6 +10088,9 @@ packages: engines: {node: '>=14.0.0'} hasBin: true + tailwindcss@4.1.17: + resolution: {integrity: sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==} + tar-stream@2.2.0: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} @@ -12125,17 +12161,17 @@ snapshots: react: 18.3.1 tslib: 2.8.1 - '@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@dnd-kit/core@6.3.1(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@dnd-kit/accessibility': 3.1.1(react@18.3.1) '@dnd-kit/utilities': 3.2.2(react@18.3.1) react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react-dom: 19.2.0(react@18.3.1) tslib: 2.8.1 - '@dnd-kit/sortable@8.0.0(@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': + '@dnd-kit/sortable@8.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.0(react@18.3.1))(react@18.3.1))(react@18.3.1)': dependencies: - '@dnd-kit/core': 6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@dnd-kit/core': 6.3.1(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@dnd-kit/utilities': 3.2.2(react@18.3.1) react: 18.3.1 tslib: 2.8.1 @@ -12512,12 +12548,6 @@ snapshots: '@floating-ui/core': 1.6.9 '@floating-ui/utils': 0.2.9 - '@floating-ui/react-dom@2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@floating-ui/dom': 1.6.13 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - '@floating-ui/react-dom@2.1.2(react-dom@19.2.0(react@17.0.2))(react@17.0.2)': dependencies: '@floating-ui/dom': 1.6.13 @@ -13484,33 +13514,33 @@ snapshots: '@radix-ui/primitive@1.1.3': {} - '@radix-ui/react-accordion@1.2.8(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-accordion@1.2.8(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.2 - '@radix-ui/react-collapsible': 1.1.8(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-collection': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-collapsible': 1.1.8(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) + '@radix-ui/react-collection': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-context': 1.1.2(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-direction': 1.1.1(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-id': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.7)(react@18.3.1) react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react-dom: 19.2.0(react@18.3.1) optionalDependencies: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 - '@radix-ui/react-alert-dialog@1.1.11(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-alert-dialog@1.1.11(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.2 '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-context': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-dialog': 1.1.11(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-dialog': 1.1.11(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-slot': 1.2.0(@types/react@18.3.7)(react@18.3.1) react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react-dom: 19.2.0(react@18.3.1) optionalDependencies: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 @@ -13529,15 +13559,6 @@ snapshots: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 - '@radix-ui/react-arrow@1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(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) - optionalDependencies: - '@types/react': 18.3.7 - '@types/react-dom': 18.3.0 - '@radix-ui/react-arrow@1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) @@ -13556,15 +13577,6 @@ snapshots: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 - '@radix-ui/react-arrow@1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(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) - optionalDependencies: - '@types/react': 18.3.7 - '@types/react-dom': 18.3.0 - '@radix-ui/react-arrow@1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) @@ -13583,15 +13595,15 @@ snapshots: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 - '@radix-ui/react-avatar@1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-avatar@1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-context': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.7)(react@18.3.1) react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react-dom: 19.2.0(react@18.3.1) optionalDependencies: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 @@ -13609,18 +13621,18 @@ snapshots: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 - '@radix-ui/react-checkbox@1.2.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-checkbox@1.2.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.2 '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-context': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-presence': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.7)(react@18.3.1) react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react-dom: 19.2.0(react@18.3.1) optionalDependencies: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 @@ -13641,18 +13653,18 @@ snapshots: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 - '@radix-ui/react-collapsible@1.1.8(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-collapsible@1.1.8(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.2 '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-context': 1.1.2(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-id': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-presence': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.7)(react@18.3.1) react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react-dom: 19.2.0(react@18.3.1) optionalDependencies: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 @@ -13673,18 +13685,6 @@ snapshots: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 - '@radix-ui/react-collection@1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.2.0(@types/react@18.3.7)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.7 - '@types/react-dom': 18.3.0 - '@radix-ui/react-collection@1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.7)(react@18.3.1) @@ -13747,23 +13747,23 @@ snapshots: optionalDependencies: '@types/react': 18.3.7 - '@radix-ui/react-dialog@1.1.11(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-dialog@1.1.11(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.2 '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-context': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-focus-guards': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-focus-scope': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-id': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-portal': 1.1.6(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-presence': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.6(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-slot': 1.2.0(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.7)(react@18.3.1) aria-hidden: 1.2.4 react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react-dom: 19.2.0(react@18.3.1) react-remove-scroll: 2.6.3(@types/react@18.3.7)(react@18.3.1) optionalDependencies: '@types/react': 18.3.7 @@ -13791,28 +13791,6 @@ snapshots: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 - '@radix-ui/react-dialog@1.1.14(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/primitive': 1.1.2 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-focus-guards': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-id': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-presence': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.2.3(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.7)(react@18.3.1) - aria-hidden: 1.2.4 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.6.3(@types/react@18.3.7)(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.7 - '@types/react-dom': 18.3.0 - '@radix-ui/react-dialog@1.1.14(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.2 @@ -13891,19 +13869,6 @@ snapshots: optionalDependencies: '@types/react': 18.3.7 - '@radix-ui/react-dismissable-layer@1.1.10(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/primitive': 1.1.2 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@18.3.7)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.7 - '@types/react-dom': 18.3.0 - '@radix-ui/react-dismissable-layer@1.1.10(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.2 @@ -13943,19 +13908,6 @@ snapshots: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 - '@radix-ui/react-dismissable-layer@1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/primitive': 1.1.2 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@18.3.7)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.7 - '@types/react-dom': 18.3.0 - '@radix-ui/react-dismissable-layer@1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.2 @@ -13982,17 +13934,17 @@ snapshots: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 - '@radix-ui/react-dropdown-menu@2.1.12(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-dropdown-menu@2.1.12(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.2 '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-context': 1.1.2(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-id': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-menu': 2.1.12(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-menu': 2.1.12(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.7)(react@18.3.1) react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react-dom: 19.2.0(react@18.3.1) optionalDependencies: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 @@ -14030,17 +13982,6 @@ snapshots: optionalDependencies: '@types/react': 18.3.7 - '@radix-ui/react-focus-scope@1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.7)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.7 - '@types/react-dom': 18.3.0 - '@radix-ui/react-focus-scope@1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.7)(react@18.3.1) @@ -14063,17 +14004,6 @@ snapshots: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 - '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.7)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.7 - '@types/react-dom': 18.3.0 - '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.7)(react@18.3.1) @@ -14096,17 +14026,17 @@ snapshots: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 - '@radix-ui/react-form@0.0.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-form@0.0.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.27.0 '@radix-ui/primitive': 1.0.1 '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-context': 1.0.1(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-id': 1.0.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-label': 2.0.2(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-label': 2.0.2(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react-dom: 19.2.0(react@18.3.1) optionalDependencies: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 @@ -14137,21 +14067,12 @@ snapshots: optionalDependencies: '@types/react': 18.3.7 - '@radix-ui/react-label@2.0.2(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-label@2.0.2(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.27.0 - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.7 - '@types/react-dom': 18.3.0 - - '@radix-ui/react-label@2.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(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) + react-dom: 19.2.0(react@18.3.1) optionalDependencies: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 @@ -14183,27 +14104,27 @@ snapshots: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 - '@radix-ui/react-menu@2.1.12(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-menu@2.1.12(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.2 - '@radix-ui/react-collection': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-collection': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-context': 1.1.2(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-direction': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-focus-guards': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-focus-scope': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-id': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-popper': 1.2.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-portal': 1.1.6(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-presence': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-roving-focus': 1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-popper': 1.2.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.6(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-slot': 1.2.0(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.7)(react@18.3.1) aria-hidden: 1.2.4 react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react-dom: 19.2.0(react@18.3.1) react-remove-scroll: 2.6.3(@types/react@18.3.7)(react@18.3.1) optionalDependencies: '@types/react': 18.3.7 @@ -14235,29 +14156,6 @@ snapshots: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 - '@radix-ui/react-popover@1.1.14(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/primitive': 1.1.2 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-focus-guards': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-id': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-popper': 1.2.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-presence': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.2.3(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.7)(react@18.3.1) - aria-hidden: 1.2.4 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.6.3(@types/react@18.3.7)(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.7 - '@types/react-dom': 18.3.0 - '@radix-ui/react-popover@1.1.14(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.2 @@ -14304,24 +14202,6 @@ snapshots: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 - '@radix-ui/react-popper@1.2.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(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) - '@radix-ui/react-arrow': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-use-rect': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/rect': 1.1.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.7 - '@types/react-dom': 18.3.0 - '@radix-ui/react-popper@1.2.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@floating-ui/react-dom': 2.1.2(react-dom@19.2.0(react@18.3.1))(react@18.3.1) @@ -14358,24 +14238,6 @@ snapshots: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 - '@radix-ui/react-popper@1.2.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(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) - '@radix-ui/react-arrow': 1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-use-rect': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/rect': 1.1.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.7 - '@types/react-dom': 18.3.0 - '@radix-ui/react-popper@1.2.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@floating-ui/react-dom': 2.1.2(react-dom@19.2.0(react@18.3.1))(react@18.3.1) @@ -14412,16 +14274,6 @@ snapshots: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 - '@radix-ui/react-portal@1.1.6(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.7)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.7 - '@types/react-dom': 18.3.0 - '@radix-ui/react-portal@1.1.6(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) @@ -14442,16 +14294,6 @@ snapshots: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 - '@radix-ui/react-portal@1.1.9(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.7)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.7 - '@types/react-dom': 18.3.0 - '@radix-ui/react-portal@1.1.9(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) @@ -14472,16 +14314,6 @@ snapshots: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 - '@radix-ui/react-presence@1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.7)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.7 - '@types/react-dom': 18.3.0 - '@radix-ui/react-presence@1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.7)(react@18.3.1) @@ -14512,21 +14344,12 @@ snapshots: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 - '@radix-ui/react-primitive@1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-primitive@1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.27.0 '@radix-ui/react-slot': 1.0.2(@types/react@18.3.7)(react@18.3.1) react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.7 - '@types/react-dom': 18.3.0 - - '@radix-ui/react-primitive@2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-slot': 1.2.0(@types/react@18.3.7)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react-dom: 19.2.0(react@18.3.1) optionalDependencies: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 @@ -14549,15 +14372,6 @@ snapshots: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 - '@radix-ui/react-primitive@2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-slot': 1.2.3(@types/react@18.3.7)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.7 - '@types/react-dom': 18.3.0 - '@radix-ui/react-primitive@2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-slot': 1.2.3(@types/react@18.3.7)(react@18.3.1) @@ -14585,12 +14399,12 @@ snapshots: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 - '@radix-ui/react-progress@1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-progress@1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-context': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react-dom: 19.2.0(react@18.3.1) optionalDependencies: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 @@ -14623,19 +14437,19 @@ snapshots: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 - '@radix-ui/react-roving-focus@1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-roving-focus@1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.2 - '@radix-ui/react-collection': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-collection': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-context': 1.1.2(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-direction': 1.1.1(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-id': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.7)(react@18.3.1) react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react-dom: 19.2.0(react@18.3.1) optionalDependencies: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 @@ -14657,19 +14471,19 @@ snapshots: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 - '@radix-ui/react-scroll-area@1.2.6(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-scroll-area@1.2.6(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/number': 1.1.1 '@radix-ui/primitive': 1.1.2 '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-context': 1.1.2(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-direction': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-presence': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.7)(react@18.3.1) react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react-dom: 19.2.0(react@18.3.1) optionalDependencies: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 @@ -14691,35 +14505,6 @@ snapshots: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 - '@radix-ui/react-select@2.2.2(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/number': 1.1.1 - '@radix-ui/primitive': 1.1.2 - '@radix-ui/react-collection': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-direction': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-focus-guards': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-focus-scope': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-id': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-popper': 1.2.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-portal': 1.1.6(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.2.0(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-visually-hidden': 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - aria-hidden: 1.2.4 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.6.3(@types/react@18.3.7)(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.7 - '@types/react-dom': 18.3.0 - '@radix-ui/react-select@2.2.2(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/number': 1.1.1 @@ -14787,40 +14572,40 @@ snapshots: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 - '@radix-ui/react-slider@1.3.2(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-slider@1.3.2(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/number': 1.1.1 '@radix-ui/primitive': 1.1.2 - '@radix-ui/react-collection': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-collection': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-context': 1.1.2(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-direction': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.7)(react@18.3.1) react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react-dom: 19.2.0(react@18.3.1) optionalDependencies: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 - '@radix-ui/react-slider@1.3.2(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-slider@1.3.2(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/number': 1.1.1 '@radix-ui/primitive': 1.1.2 - '@radix-ui/react-collection': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-direction': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.7)(react@18.3.1) - react: 18.3.1 - react-dom: 19.2.0(react@18.3.1) + '@radix-ui/react-collection': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.7)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.7)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.7)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.7)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.7)(react@19.2.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.7)(react@19.2.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.7)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 @@ -14868,21 +14653,6 @@ snapshots: optionalDependencies: '@types/react': 18.3.7 - '@radix-ui/react-switch@1.2.5(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/primitive': 1.1.2 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.7)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.7 - '@types/react-dom': 18.3.0 - '@radix-ui/react-switch@1.2.5(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.2 @@ -14928,18 +14698,18 @@ snapshots: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 - '@radix-ui/react-tabs@1.1.9(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-tabs@1.1.9(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.2 '@radix-ui/react-context': 1.1.2(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-direction': 1.1.1(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-id': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-presence': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-roving-focus': 1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.7)(react@18.3.1) react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react-dom: 19.2.0(react@18.3.1) optionalDependencies: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 @@ -14960,22 +14730,22 @@ snapshots: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 - '@radix-ui/react-toast@1.2.11(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-toast@1.2.11(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.2 - '@radix-ui/react-collection': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-collection': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-context': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-portal': 1.1.6(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-presence': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.6(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.7)(react@18.3.1) '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-visually-hidden': 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react-dom: 19.2.0(react@18.3.1) optionalDependencies: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 @@ -15026,26 +14796,6 @@ snapshots: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 - '@radix-ui/react-tooltip@1.2.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/primitive': 1.1.2 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-id': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-popper': 1.2.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-presence': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.2.3(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(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) - optionalDependencies: - '@types/react': 18.3.7 - '@types/react-dom': 18.3.0 - '@radix-ui/react-tooltip@1.2.7(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.2 @@ -15215,15 +14965,6 @@ snapshots: optionalDependencies: '@types/react': 18.3.7 - '@radix-ui/react-visually-hidden@1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(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) - optionalDependencies: - '@types/react': 18.3.7 - '@types/react-dom': 18.3.0 - '@radix-ui/react-visually-hidden@1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-primitive': 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) @@ -15242,15 +14983,6 @@ snapshots: '@types/react': 18.3.7 '@types/react-dom': 18.3.0 - '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(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) - optionalDependencies: - '@types/react': 18.3.7 - '@types/react-dom': 18.3.0 - '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) @@ -15271,117 +15003,129 @@ snapshots: '@radix-ui/rect@1.1.1': {} - '@react-email/body@0.0.11(react@18.3.1)': + '@react-email/body@0.2.0(react@19.2.0)': dependencies: - react: 18.3.1 + react: 19.2.0 - '@react-email/button@0.0.19(react@18.3.1)': + '@react-email/button@0.2.0(react@19.2.0)': dependencies: - react: 18.3.1 + react: 19.2.0 - '@react-email/code-block@0.0.13(react@18.3.1)': + '@react-email/code-block@0.2.0(react@19.2.0)': dependencies: prismjs: 1.30.0 - react: 18.3.1 + react: 19.2.0 - '@react-email/code-inline@0.0.5(react@18.3.1)': + '@react-email/code-inline@0.0.5(react@19.2.0)': dependencies: - react: 18.3.1 + react: 19.2.0 - '@react-email/column@0.0.13(react@18.3.1)': + '@react-email/column@0.0.13(react@19.2.0)': dependencies: - react: 18.3.1 + react: 19.2.0 - '@react-email/components@0.0.42(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': - dependencies: - '@react-email/body': 0.0.11(react@18.3.1) - '@react-email/button': 0.0.19(react@18.3.1) - '@react-email/code-block': 0.0.13(react@18.3.1) - '@react-email/code-inline': 0.0.5(react@18.3.1) - '@react-email/column': 0.0.13(react@18.3.1) - '@react-email/container': 0.0.15(react@18.3.1) - '@react-email/font': 0.0.9(react@18.3.1) - '@react-email/head': 0.0.12(react@18.3.1) - '@react-email/heading': 0.0.15(react@18.3.1) - '@react-email/hr': 0.0.11(react@18.3.1) - '@react-email/html': 0.0.11(react@18.3.1) - '@react-email/img': 0.0.11(react@18.3.1) - '@react-email/link': 0.0.12(react@18.3.1) - '@react-email/markdown': 0.0.15(react@18.3.1) - '@react-email/preview': 0.0.13(react@18.3.1) - '@react-email/render': 1.1.2(react-dom@19.2.0(react@18.3.1))(react@18.3.1) - '@react-email/row': 0.0.12(react@18.3.1) - '@react-email/section': 0.0.16(react@18.3.1) - '@react-email/tailwind': 1.0.5(react@18.3.1) - '@react-email/text': 0.1.5(react@18.3.1) - react: 18.3.1 + '@react-email/components@1.0.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@react-email/body': 0.2.0(react@19.2.0) + '@react-email/button': 0.2.0(react@19.2.0) + '@react-email/code-block': 0.2.0(react@19.2.0) + '@react-email/code-inline': 0.0.5(react@19.2.0) + '@react-email/column': 0.0.13(react@19.2.0) + '@react-email/container': 0.0.15(react@19.2.0) + '@react-email/font': 0.0.9(react@19.2.0) + '@react-email/head': 0.0.12(react@19.2.0) + '@react-email/heading': 0.0.15(react@19.2.0) + '@react-email/hr': 0.0.11(react@19.2.0) + '@react-email/html': 0.0.11(react@19.2.0) + '@react-email/img': 0.0.11(react@19.2.0) + '@react-email/link': 0.0.12(react@19.2.0) + '@react-email/markdown': 0.0.17(react@19.2.0) + '@react-email/preview': 0.0.13(react@19.2.0) + '@react-email/render': 2.0.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@react-email/row': 0.0.12(react@19.2.0) + '@react-email/section': 0.0.16(react@19.2.0) + '@react-email/tailwind': 2.0.1(@react-email/body@0.2.0(react@19.2.0))(@react-email/button@0.2.0(react@19.2.0))(@react-email/code-block@0.2.0(react@19.2.0))(@react-email/code-inline@0.0.5(react@19.2.0))(@react-email/container@0.0.15(react@19.2.0))(@react-email/heading@0.0.15(react@19.2.0))(@react-email/hr@0.0.11(react@19.2.0))(@react-email/img@0.0.11(react@19.2.0))(@react-email/link@0.0.12(react@19.2.0))(@react-email/preview@0.0.13(react@19.2.0))(@react-email/text@0.1.5(react@19.2.0))(react@19.2.0) + '@react-email/text': 0.1.5(react@19.2.0) + react: 19.2.0 transitivePeerDependencies: - react-dom - '@react-email/container@0.0.15(react@18.3.1)': + '@react-email/container@0.0.15(react@19.2.0)': dependencies: - react: 18.3.1 + react: 19.2.0 - '@react-email/font@0.0.9(react@18.3.1)': + '@react-email/font@0.0.9(react@19.2.0)': dependencies: - react: 18.3.1 + react: 19.2.0 - '@react-email/head@0.0.12(react@18.3.1)': + '@react-email/head@0.0.12(react@19.2.0)': dependencies: - react: 18.3.1 + react: 19.2.0 - '@react-email/heading@0.0.15(react@18.3.1)': + '@react-email/heading@0.0.15(react@19.2.0)': dependencies: - react: 18.3.1 + react: 19.2.0 - '@react-email/hr@0.0.11(react@18.3.1)': + '@react-email/hr@0.0.11(react@19.2.0)': dependencies: - react: 18.3.1 + react: 19.2.0 - '@react-email/html@0.0.11(react@18.3.1)': + '@react-email/html@0.0.11(react@19.2.0)': dependencies: - react: 18.3.1 + react: 19.2.0 - '@react-email/img@0.0.11(react@18.3.1)': + '@react-email/img@0.0.11(react@19.2.0)': dependencies: - react: 18.3.1 + react: 19.2.0 - '@react-email/link@0.0.12(react@18.3.1)': + '@react-email/link@0.0.12(react@19.2.0)': dependencies: - react: 18.3.1 + react: 19.2.0 - '@react-email/markdown@0.0.15(react@18.3.1)': + '@react-email/markdown@0.0.17(react@19.2.0)': dependencies: - md-to-react-email: 5.0.5(react@18.3.1) - react: 18.3.1 + marked: 15.0.12 + react: 19.2.0 - '@react-email/preview@0.0.13(react@18.3.1)': + '@react-email/preview@0.0.13(react@19.2.0)': dependencies: - react: 18.3.1 + react: 19.2.0 - '@react-email/render@1.1.2(react-dom@19.2.0(react@18.3.1))(react@18.3.1)': + '@react-email/render@2.0.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: html-to-text: 9.0.5 prettier: 3.5.3 - react: 18.3.1 - react-dom: 19.2.0(react@18.3.1) - react-promise-suspense: 0.3.4 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) - '@react-email/row@0.0.12(react@18.3.1)': + '@react-email/row@0.0.12(react@19.2.0)': dependencies: - react: 18.3.1 + react: 19.2.0 - '@react-email/section@0.0.16(react@18.3.1)': + '@react-email/section@0.0.16(react@19.2.0)': dependencies: - react: 18.3.1 + react: 19.2.0 - '@react-email/tailwind@1.0.5(react@18.3.1)': + '@react-email/tailwind@2.0.1(@react-email/body@0.2.0(react@19.2.0))(@react-email/button@0.2.0(react@19.2.0))(@react-email/code-block@0.2.0(react@19.2.0))(@react-email/code-inline@0.0.5(react@19.2.0))(@react-email/container@0.0.15(react@19.2.0))(@react-email/heading@0.0.15(react@19.2.0))(@react-email/hr@0.0.11(react@19.2.0))(@react-email/img@0.0.11(react@19.2.0))(@react-email/link@0.0.12(react@19.2.0))(@react-email/preview@0.0.13(react@19.2.0))(@react-email/text@0.1.5(react@19.2.0))(react@19.2.0)': dependencies: - react: 18.3.1 + '@react-email/text': 0.1.5(react@19.2.0) + react: 19.2.0 + tailwindcss: 4.1.17 + optionalDependencies: + '@react-email/body': 0.2.0(react@19.2.0) + '@react-email/button': 0.2.0(react@19.2.0) + '@react-email/code-block': 0.2.0(react@19.2.0) + '@react-email/code-inline': 0.0.5(react@19.2.0) + '@react-email/container': 0.0.15(react@19.2.0) + '@react-email/heading': 0.0.15(react@19.2.0) + '@react-email/hr': 0.0.11(react@19.2.0) + '@react-email/img': 0.0.11(react@19.2.0) + '@react-email/link': 0.0.12(react@19.2.0) + '@react-email/preview': 0.0.13(react@19.2.0) - '@react-email/text@0.1.5(react@18.3.1)': + '@react-email/text@0.1.5(react@19.2.0)': dependencies: - react: 18.3.1 + react: 19.2.0 '@remirror/core-constants@3.0.0': {} @@ -19187,14 +18931,14 @@ snapshots: cluster-key-slot@1.1.2: {} - cmdk@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + cmdk@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1): dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-dialog': 1.1.14(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-dialog': 1.1.14(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) '@radix-ui/react-id': 1.1.1(@types/react@18.3.7)(react@18.3.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@19.2.0(react@18.3.1))(react@18.3.1) react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react-dom: 19.2.0(react@18.3.1) transitivePeerDependencies: - '@types/react' - '@types/react-dom' @@ -20358,8 +20102,6 @@ snapshots: extract-domain@2.2.1: {} - fast-deep-equal@2.0.1: {} - fast-deep-equal@3.1.3: {} fast-equals@5.2.2: {} @@ -21974,6 +21716,10 @@ snapshots: dependencies: react: 18.3.1 + lucide-react@0.544.0(react@19.2.0): + dependencies: + react: 19.2.0 + lucide-react@0.553.0(react@18.3.1): dependencies: react: 18.3.1 @@ -22008,9 +21754,9 @@ snapshots: markdown-table@3.0.4: {} - marked@4.3.0: {} + marked@15.0.12: {} - marked@7.0.4: {} + marked@4.3.0: {} match-sorter@6.3.4: dependencies: @@ -22025,11 +21771,6 @@ snapshots: math-intrinsics@1.1.0: {} - md-to-react-email@5.0.5(react@18.3.1): - dependencies: - marked: 7.0.4 - react: 18.3.1 - mdast-util-definitions@5.1.2: dependencies: '@types/mdast': 3.0.15 @@ -22667,6 +22408,29 @@ snapshots: - '@babel/core' - babel-plugin-macros + next@15.5.3(@babel/core@7.26.10)(babel-plugin-macros@3.1.0)(react-dom@19.2.0(react@18.3.1))(react@18.3.1): + dependencies: + '@next/env': 15.5.3 + '@swc/helpers': 0.5.15 + caniuse-lite: 1.0.30001715 + postcss: 8.4.31 + react: 18.3.1 + react-dom: 19.2.0(react@18.3.1) + styled-jsx: 5.1.6(@babel/core@7.26.10)(babel-plugin-macros@3.1.0)(react@18.3.1) + optionalDependencies: + '@next/swc-darwin-arm64': 15.5.3 + '@next/swc-darwin-x64': 15.5.3 + '@next/swc-linux-arm64-gnu': 15.5.3 + '@next/swc-linux-arm64-musl': 15.5.3 + '@next/swc-linux-x64-gnu': 15.5.3 + '@next/swc-linux-x64-musl': 15.5.3 + '@next/swc-win32-arm64-msvc': 15.5.3 + '@next/swc-win32-x64-msvc': 15.5.3 + sharp: 0.34.3 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + next@16.0.3(@babel/core@7.26.10)(babel-plugin-macros@3.1.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: '@next/env': 16.0.3 @@ -23485,10 +23249,6 @@ snapshots: react-is@19.1.0: {} - react-promise-suspense@0.3.4: - dependencies: - fast-deep-equal: 2.0.1 - react-remove-scroll-bar@2.3.8(@types/react@18.3.7)(react@18.3.1): dependencies: react: 18.3.1 @@ -24705,6 +24465,8 @@ snapshots: transitivePeerDependencies: - ts-node + tailwindcss@4.1.17: {} + tar-stream@2.2.0: dependencies: bl: 4.1.0 @@ -24823,6 +24585,27 @@ snapshots: ts-interface-checker@0.1.13: {} + ts-jest@29.4.4(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.19.12)(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.0)(typescript@5.9.3)))(typescript@5.9.3): + dependencies: + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + handlebars: 4.7.8 + jest: 29.7.0(@types/node@20.19.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.0)(typescript@5.9.3)) + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.7.2 + type-fest: 4.41.0 + typescript: 5.9.3 + yargs-parser: 21.1.1 + optionalDependencies: + '@babel/core': 7.26.10 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.26.10) + esbuild: 0.19.12 + jest-util: 29.7.0 + ts-jest@29.4.4(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(jest-util@29.7.0)(jest@29.7.0(@types/node@17.0.21)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@17.0.21)(typescript@5.9.3)))(typescript@5.9.3): dependencies: bs-logger: 0.2.6 From 780984860767ccbbaf324c883cad24871543ecd2 Mon Sep 17 00:00:00 2001 From: Rajat Date: Tue, 18 Nov 2025 15:34:03 +0000 Subject: [PATCH 2/2] CodeQL fixes --- .../__tests__/process-ongoing-sequences.test.ts | 11 ++++------- apps/web/next-env.d.ts | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/apps/queue/src/domain/__tests__/process-ongoing-sequences.test.ts b/apps/queue/src/domain/__tests__/process-ongoing-sequences.test.ts index 4568a5a68..f1f1418f4 100644 --- a/apps/queue/src/domain/__tests__/process-ongoing-sequences.test.ts +++ b/apps/queue/src/domain/__tests__/process-ongoing-sequences.test.ts @@ -15,7 +15,6 @@ import EmailDelivery from "../model/email-delivery"; import * as queries from "../queries"; import * as mail from "../../mail"; import { AdminSequence, InternalUser } from "@courselit/common-logic"; -import { renderEmailToHtml } from "@courselit/email-editor"; import { jwtUtils } from "@courselit/utils"; import { getUnsubLink } from "../../utils/get-unsub-link"; import { getSiteUrl } from "../../utils/get-site-url"; @@ -839,7 +838,7 @@ describe("processOngoingSequence", () => { it("should rewrite links for click tracking", async () => { // Create a sequence with links in the content - const sequenceWithLinks = (await (SequenceModel.create as any)({ + await (SequenceModel.create as any)({ domain: testDomain._id, sequenceId: "sequence-links", creatorId: TEST_CREATOR_ID, @@ -921,7 +920,7 @@ describe("processOngoingSequence", () => { failed: [], }, }, - })) as any; + }); const ongoingSeq = await OngoingSequenceModel.create({ domain: testDomain._id, @@ -993,9 +992,7 @@ describe("processOngoingSequence", () => { it("should not rewrite mailto, tel, anchor, and API links", async () => { // Create a sequence with various link types that should NOT be rewritten - const sequenceWithSpecialLinks = (await ( - SequenceModel.create as any - )({ + await (SequenceModel.create as any)({ domain: testDomain._id, sequenceId: "sequence-special-links", creatorId: TEST_CREATOR_ID, @@ -1094,7 +1091,7 @@ describe("processOngoingSequence", () => { failed: [], }, }, - })) as any; + }); const ongoingSeq = await OngoingSequenceModel.create({ domain: testDomain._id, diff --git a/apps/web/next-env.d.ts b/apps/web/next-env.d.ts index c4b7818fb..9edff1c7c 100644 --- a/apps/web/next-env.d.ts +++ b/apps/web/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/dev/types/routes.d.ts"; +import "./.next/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.