diff --git a/app/api/internal/api-keys/[id]/route.ts b/app/api/internal/api-keys/[id]/route.ts index d90bf21..1b389c9 100644 --- a/app/api/internal/api-keys/[id]/route.ts +++ b/app/api/internal/api-keys/[id]/route.ts @@ -53,7 +53,7 @@ export async function PATCH( const parsed = updateApiKeySchema.safeParse(body) if (!parsed.success) { return NextResponse.json( - { error: parsed.error.errors.map((e) => e.message).join(', ') }, + { error: parsed.error.issues.map((e) => e.message).join(', ') }, { status: 400 }, ) } diff --git a/app/api/internal/api-keys/route.ts b/app/api/internal/api-keys/route.ts index 44ef1ca..a7e38d4 100644 --- a/app/api/internal/api-keys/route.ts +++ b/app/api/internal/api-keys/route.ts @@ -38,7 +38,7 @@ export async function POST(req: NextRequest) { const parsed = createApiKeySchema.safeParse(body) if (!parsed.success) { - const message = parsed.error.errors.map((e) => e.message).join(', ') + const message = parsed.error.issues.map((e) => e.message).join(', ') return NextResponse.json({ error: message }, { status: 400 }) } diff --git a/app/api/internal/assets/[id]/route.ts b/app/api/internal/assets/[id]/route.ts index c55e7c8..64ce66d 100644 --- a/app/api/internal/assets/[id]/route.ts +++ b/app/api/internal/assets/[id]/route.ts @@ -18,7 +18,7 @@ export async function PATCH(req: NextRequest, { params }: { params: { id: string const parsed = renameAssetSchema.safeParse(body) if (!parsed.success) { - const message = parsed.error.errors.map((e) => e.message).join(', ') + const message = parsed.error.issues.map((e) => e.message).join(', ') return NextResponse.json({ error: message }, { status: 400 }) } diff --git a/app/api/internal/forms/[id]/route.ts b/app/api/internal/forms/[id]/route.ts index 1473544..c8d8deb 100644 --- a/app/api/internal/forms/[id]/route.ts +++ b/app/api/internal/forms/[id]/route.ts @@ -21,7 +21,7 @@ export async function PATCH(req: NextRequest, { params }: { params: { id: string const parsed = updateFormSchema.safeParse(body) if (!parsed.success) { - const message = parsed.error.errors.map((e) => e.message).join(', ') + const message = parsed.error.issues.map((e) => e.message).join(', ') return NextResponse.json({ error: message }, { status: 400 }) } diff --git a/app/api/internal/forms/route.ts b/app/api/internal/forms/route.ts index 3baebe0..8aca2e7 100644 --- a/app/api/internal/forms/route.ts +++ b/app/api/internal/forms/route.ts @@ -34,7 +34,7 @@ export async function POST(req: NextRequest) { const parsed = createFormSchema.safeParse(body) if (!parsed.success) { - const message = parsed.error.errors.map((e) => e.message).join(', ') + const message = parsed.error.issues.map((e) => e.message).join(', ') return NextResponse.json({ error: message }, { status: 400 }) } diff --git a/app/api/internal/lists/[id]/contacts/route.ts b/app/api/internal/lists/[id]/contacts/route.ts index 1a4625c..cf940bd 100644 --- a/app/api/internal/lists/[id]/contacts/route.ts +++ b/app/api/internal/lists/[id]/contacts/route.ts @@ -67,7 +67,7 @@ export async function POST( const parsed = createContactSchema.safeParse(body) if (!parsed.success) { - const message = parsed.error.errors.map((e) => e.message).join(', ') + const message = parsed.error.issues.map((e) => e.message).join(', ') return NextResponse.json({ error: message }, { status: 400 }) } diff --git a/app/api/internal/lists/[id]/duplicates/merge/route.ts b/app/api/internal/lists/[id]/duplicates/merge/route.ts index c25a333..d34e34a 100644 --- a/app/api/internal/lists/[id]/duplicates/merge/route.ts +++ b/app/api/internal/lists/[id]/duplicates/merge/route.ts @@ -16,7 +16,7 @@ export async function POST( const parsed = mergeGroupsSchema.safeParse(body) if (!parsed.success) { - const message = parsed.error.errors.map((e) => e.message).join(', ') + const message = parsed.error.issues.map((e) => e.message).join(', ') return NextResponse.json({ error: message }, { status: 400 }) } diff --git a/app/api/internal/lists/[id]/route.ts b/app/api/internal/lists/[id]/route.ts index 2893426..8cc596b 100644 --- a/app/api/internal/lists/[id]/route.ts +++ b/app/api/internal/lists/[id]/route.ts @@ -49,7 +49,7 @@ export async function PATCH( const parsed = updateListSchema.safeParse(body) if (!parsed.success) { - const message = parsed.error.errors.map((e) => e.message).join(', ') + const message = parsed.error.issues.map((e) => e.message).join(', ') return NextResponse.json({ error: message }, { status: 400 }) } diff --git a/app/api/internal/lists/route.ts b/app/api/internal/lists/route.ts index 040fc23..bfa1bbc 100644 --- a/app/api/internal/lists/route.ts +++ b/app/api/internal/lists/route.ts @@ -39,7 +39,7 @@ export async function POST(req: NextRequest) { const parsed = createListSchema.safeParse(body) if (!parsed.success) { - const message = parsed.error.errors.map((e) => e.message).join(', ') + const message = parsed.error.issues.map((e) => e.message).join(', ') return NextResponse.json({ error: message }, { status: 400 }) } diff --git a/app/api/internal/providers/route.ts b/app/api/internal/providers/route.ts index 210d250..24baab4 100644 --- a/app/api/internal/providers/route.ts +++ b/app/api/internal/providers/route.ts @@ -73,7 +73,7 @@ export async function POST(req: NextRequest) { const parsed = createProviderSchema.safeParse(body) if (!parsed.success) { - const message = parsed.error.errors.map((e) => e.message).join(', ') + const message = parsed.error.issues.map((e) => e.message).join(', ') return NextResponse.json({ error: message }, { status: 400 }) } diff --git a/app/api/internal/settings/route.ts b/app/api/internal/settings/route.ts index 0d1fde4..6ce6810 100644 --- a/app/api/internal/settings/route.ts +++ b/app/api/internal/settings/route.ts @@ -23,7 +23,7 @@ export async function PATCH(req: NextRequest) { const parsed = updateAppSettingsSchema.safeParse(body) if (!parsed.success) { - const message = parsed.error.errors.map((e) => e.message).join(', ') + const message = parsed.error.issues.map((e) => e.message).join(', ') return NextResponse.json({ error: message }, { status: 400 }) } diff --git a/app/api/internal/suppressions/route.ts b/app/api/internal/suppressions/route.ts index ac83cf1..c8e01aa 100644 --- a/app/api/internal/suppressions/route.ts +++ b/app/api/internal/suppressions/route.ts @@ -45,7 +45,7 @@ export async function POST(req: NextRequest) { const parsed = createSuppressionSchema.safeParse(body) if (!parsed.success) { - const message = parsed.error.errors.map((e) => e.message).join(', ') + const message = parsed.error.issues.map((e) => e.message).join(', ') return NextResponse.json({ error: message }, { status: 400 }) } diff --git a/app/api/internal/team/invites/route.ts b/app/api/internal/team/invites/route.ts index a998117..533d3f2 100644 --- a/app/api/internal/team/invites/route.ts +++ b/app/api/internal/team/invites/route.ts @@ -45,7 +45,7 @@ export async function POST(req: NextRequest) { const parsed = createInviteSchema.safeParse(body) if (!parsed.success) { return NextResponse.json( - { error: parsed.error.errors.map((e) => e.message).join(', ') }, + { error: parsed.error.issues.map((e) => e.message).join(', ') }, { status: 400 }, ) } diff --git a/app/api/internal/team/members/[id]/route.ts b/app/api/internal/team/members/[id]/route.ts index 4d87e30..0fa91e9 100644 --- a/app/api/internal/team/members/[id]/route.ts +++ b/app/api/internal/team/members/[id]/route.ts @@ -62,7 +62,7 @@ export async function PATCH( const parsed = updateMemberSchema.safeParse(body) if (!parsed.success) { return NextResponse.json( - { error: parsed.error.errors.map((e) => e.message).join(', ') }, + { error: parsed.error.issues.map((e) => e.message).join(', ') }, { status: 400 }, ) } diff --git a/app/api/public/accept-invite/[token]/route.ts b/app/api/public/accept-invite/[token]/route.ts index 69172af..5d146b9 100644 --- a/app/api/public/accept-invite/[token]/route.ts +++ b/app/api/public/accept-invite/[token]/route.ts @@ -47,7 +47,7 @@ export async function POST( const parsed = acceptInviteSchema.safeParse(body) if (!parsed.success) { return NextResponse.json( - { error: parsed.error.errors.map((e) => e.message).join(', ') }, + { error: parsed.error.issues.map((e) => e.message).join(', ') }, { status: 400 }, ) } diff --git a/lib/validations/contacts.ts b/lib/validations/contacts.ts index b9b7600..ffa712c 100644 --- a/lib/validations/contacts.ts +++ b/lib/validations/contacts.ts @@ -1,17 +1,17 @@ import { z } from 'zod' export const createContactSchema = z.object({ - email: z.string().email('Valid email is required'), + email: z.email('Valid email is required'), firstName: z.string().optional(), lastName: z.string().optional(), - metadata: z.record(z.string()).optional(), + metadata: z.record(z.string(), z.string()).optional(), }) export const updateContactSchema = z.object({ - email: z.string().email().optional(), + email: z.email().optional(), firstName: z.string().optional(), lastName: z.string().optional(), - metadata: z.record(z.string()).optional(), + metadata: z.record(z.string(), z.string()).optional(), status: z.enum(['active', 'pending', 'bounced', 'unsubscribed', 'undeliverable']).optional(), }) diff --git a/lib/validations/providers.ts b/lib/validations/providers.ts index a686379..3a490cf 100644 --- a/lib/validations/providers.ts +++ b/lib/validations/providers.ts @@ -6,12 +6,12 @@ const optionalEmail = z.preprocess( const trimmed = v.trim() return trimmed.length === 0 ? undefined : trimmed }, - z.string().email('Must be a valid email address').optional(), + z.email('Must be a valid email address').optional(), ) export const createProviderSchema = z.object({ name: z.string().min(1, 'Name is required'), - type: z.enum(['resend', 'ses'], { required_error: 'Type must be resend or ses' }), + type: z.enum(['resend', 'ses'], { message: 'Type must be resend or ses' }), apiKey: z.string().min(1, 'API key is required'), region: z.string().optional(), replyToEmail: optionalEmail, diff --git a/package-lock.json b/package-lock.json index dd5fbbf..0ce79d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,7 @@ "date-fns": "^4.1.0", "disposable-email-domains": "^1.0.62", "drizzle-orm": "^0.45.2", - "handlebars": "^4.7.9", + "handlebars": "^4.7.10", "html-to-text": "^10.0.0", "isemail": "^3.2.0", "js-beautify": "^1.15.4", @@ -38,8 +38,8 @@ "lucide-react": "^1.8.0", "mailchecker": "^6.0.20", "nanoid": "^5.1.9", - "next": "14.2.35", - "next-auth": "^4.24.14", + "next": "15.5.24", + "next-auth": "^4.24.15", "next-themes": "^0.4.6", "pg": "^8.20.0", "pg-boss": "^12.15.0", @@ -49,12 +49,12 @@ "react-dom": "^18", "recharts": "^3.8.1", "resend": "^6.11.0", - "sanitize-html": "^2.17.3", + "sanitize-html": "^2.17.4", "svix": "^1.90.0", "tailwind-merge": "^3.5.0", "tailwindcss-animate": "^1.0.7", "xlsx": "^0.18.5", - "zod": "^3.25.76" + "zod": "4.6.6" }, "devDependencies": { "@types/bcryptjs": "^2.4.6", @@ -95,7 +95,7 @@ "integrity": "sha512-iyM8mB556KdiZ6a1GTZ67ACLnJakU1hrzzXoh7PLaReldAdMq88MlZn/Ir/U56/TBuQctBhh/4seo0b0B343uw==", "license": "MIT", "dependencies": { - "ts-deepmerge": "^6.0.3" + "ts-deepmerge": "^8.0.0" }, "peerDependencies": { "openapi3-ts": "^4.1.2", @@ -1038,7 +1038,7 @@ "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.14.0", - "fast-xml-parser": "5.5.8", + "fast-xml-parser": "5.7.0", "tslib": "^2.6.2" }, "engines": { @@ -5038,32 +5038,6 @@ "path-browserify": "^1.0.1" } }, - "node_modules/@ts-morph/common/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@ts-morph/common/node_modules/minimatch": { - "version": "7.4.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.9.tgz", - "integrity": "sha512-Brg/fp/iAVDOQoHxkuN5bEYhyQlZhxddI78yWsCbeEwTHXQjlNLtiJDUsp1GIptVqMI7/gkJMz4vVAc01mpoBw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", @@ -5465,45 +5439,6 @@ "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@typescript-eslint/utils": { "version": "8.58.2", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.2.tgz", @@ -6283,10 +6218,13 @@ } }, "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } }, "node_modules/base64-js": { "version": "1.5.1", @@ -6310,9 +6248,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.19.tgz", - "integrity": "sha512-qCkNLi2sfBOn8XhZQ0FXsT1Ki/Yo5P90hrkRamVFRS7/KV9hpfA4HkoWNU152+8w0zPjnxo5psx5NL3PSGgv5g==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.0.tgz", + "integrity": "sha512-oCu2wfipvX3AePSgmOuKkIywOu+8n9psz7hXYmk56ghpu3+7KzNIBopaOs4c9BrtdnTtW30unG9GTfHo7EwERQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -6356,21 +6294,21 @@ } }, "node_modules/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.4.0.tgz", + "integrity": "sha512-Ifk1KcLrxaGt1occQagQNFNNOqRt1LFuehEnHHzB8Z6cRgIxRU1TBaiIsUiCdA+gy5PL7Y1IX31DNKkRmxtXjQ==", "dev": true, "license": "MIT", "dependencies": { "bytes": "^3.1.2", - "content-type": "^1.0.5", + "content-type": "^2.1.0", "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.3", "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" + "qs": "^6.16.1", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" }, "engines": { "node": ">=18" @@ -6381,9 +6319,9 @@ } }, "node_modules/body-parser/node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6410,14 +6348,15 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", - "dev": true, + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" } }, "node_modules/braces": { @@ -6454,7 +6393,7 @@ "license": "MIT", "peer": true, "dependencies": { - "baseline-browser-mapping": "^2.10.12", + "baseline-browser-mapping": "^2.11.0", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", @@ -7508,9 +7447,9 @@ } }, "node_modules/dompurify": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", - "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.7.tgz", + "integrity": "sha512-2jBxDJY4RR06tQNy4w5FlFH7kfxsQZlufd0sbv+chfHCxeJwrFw2baUDsSwvBISD4K4RDbd0PTfy3uNXsR6siA==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -7768,15 +7707,6 @@ "node": ">=14" } }, - "node_modules/editorconfig/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/editorconfig/node_modules/commander": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", @@ -7786,21 +7716,6 @@ "node": ">=14" } }, - "node_modules/editorconfig/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -8679,7 +8594,7 @@ "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", - "qs": "^6.14.0", + "qs": "^6.16.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", @@ -8788,9 +8703,9 @@ "license": "BSD-3-Clause" }, "node_modules/fast-xml-builder": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz", - "integrity": "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.7.tgz", + "integrity": "sha512-Yh7/7rQuMXICNr0oMYDR2yHP6oUvmQsTToFeOWj/kIDhAwQ+c4Ol/lbcwOmEM5OHYQmh6S6EQSQ1sljCKP36bQ==", "funding": [ { "type": "github", @@ -8803,9 +8718,9 @@ } }, "node_modules/fast-xml-parser": { - "version": "5.5.8", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.8.tgz", - "integrity": "sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.0.tgz", + "integrity": "sha512-MTcrUoRQ1GSQ9iG3QJzBGquYYYeA7piZaJoIWbPFGbRn6Jj6z7xgoAyi4DrZX4y2ZIQQBF59gc/zmvvejjgoFQ==", "funding": [ { "type": "github", @@ -8814,7 +8729,7 @@ ], "license": "MIT", "dependencies": { - "fast-xml-builder": "^1.1.4", + "fast-xml-builder": "^1.1.7", "path-expression-matcher": "^1.2.0", "strnum": "^2.2.0" }, @@ -9262,32 +9177,6 @@ "node": ">=10.13.0" } }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", @@ -10378,15 +10267,6 @@ "node": ">=14" } }, - "node_modules/js-beautify/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/js-beautify/node_modules/glob": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", @@ -10423,21 +10303,6 @@ "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/js-beautify/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/js-cookie": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", @@ -10894,16 +10759,19 @@ } }, "node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-11.0.1.tgz", + "integrity": "sha512-t+Vn6DX4wPCxS23y03C0uhF7S1jpi1GfqPrnlEipCJaA2ykiNbMkVlDutZtmvLfRcUN6R8AYVlzTsQrT4RZoLQ==", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "@isaacs/nonrecursive-glob-match": "^1.0.1", + "brace-expansion": "^5.0.12" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { @@ -10947,7 +10815,7 @@ "license": "MIT", "peer": true, "dependencies": { - "dompurify": "3.2.7", + "dompurify": "3.4.7", "marked": "14.0.0" } }, @@ -12339,7 +12207,7 @@ ], "license": "MIT", "dependencies": { - "postcss-selector-parser": "^6.1.1" + "postcss-selector-parser": "^6.1.3" }, "engines": { "node": ">=12.0" @@ -12349,9 +12217,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.3.tgz", + "integrity": "sha512-cDoO18VWCIWRsSaws3C23b0HXRxlUknttfdNcbXnf3IGHAPRNLlXPHc6dYiatOkxW0W4uZh524Plaaw5a7WEtQ==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -12572,13 +12440,14 @@ } }, "node_modules/qs": { - "version": "6.15.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", - "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.17.1.tgz", + "integrity": "sha512-jd3COkaKVgGUW+ZHyEHCG9bH7RoaaiWuMgzsIBjUMVjLAaWGWpoUAVwljVvBHxRu/2qEHnadLGZrkutYUBPQJA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.1.0" + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" }, "engines": { "node": ">=0.6" @@ -13563,15 +13432,15 @@ } }, "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, @@ -14232,7 +14101,7 @@ "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", "postcss-nested": "^6.2.0", - "postcss-selector-parser": "^6.1.2", + "postcss-selector-parser": "^6.1.3", "resolve": "^1.22.8", "sucrase": "^3.35.0" }, @@ -14444,9 +14313,9 @@ } }, "node_modules/ts-deepmerge": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ts-deepmerge/-/ts-deepmerge-6.2.1.tgz", - "integrity": "sha512-8CYSLazCyj0DJDpPIxOFzJG46r93uh6EynYjuey+bxcLltBeqZL7DMfaE5ZPzZNFlav7wx+2TDa/mBl8gkTYzw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ts-deepmerge/-/ts-deepmerge-8.0.0.tgz", + "integrity": "sha512-133O+10nJmVI8w5xeVZPEv5PIrv7iaUae07wv1aH8XJH95Ur6YIhWAPhPyP1YPlbPS9fCVcNIZTu7m8urRVF0A==", "license": "ISC", "engines": { "node": ">=14.13.1" @@ -15032,18 +14901,22 @@ } }, "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", "dev": true, "license": "MIT", "dependencies": { - "content-type": "^1.0.5", + "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/typed-array-buffer": { @@ -15837,11 +15710,10 @@ } }, "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.6.tgz", + "integrity": "sha512-4UmzQUF5fxvfcYHlbjBmejU+w+hbDnWFx4aS8kBx7nPBHcO2SqPbpAG8snbdg+Xse4pRefR98hgjFgIMCI/UhA==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -15855,6 +15727,50 @@ "peerDependencies": { "zod": "^3.25.28 || ^4" } + }, + "node_modules/shadcn/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@isaacs/nonrecursive-glob-match": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@isaacs/nonrecursive-glob-match/-/nonrecursive-glob-match-1.0.3.tgz", + "integrity": "sha512-SI7kyKHq4XXDRozw+r9GlRZkia1ZkFrpLW98r2IC2Bpr8YkFGE3fvjzmxCG0x78A++69HmDNrDvSMYBZmHGpKA==", + "license": "BlueOak-1.0.0" } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index 40d7bbf..b5a8261 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "date-fns": "^4.1.0", "disposable-email-domains": "^1.0.62", "drizzle-orm": "^0.45.2", - "handlebars": "^4.7.9", + "handlebars": "^4.7.10", "html-to-text": "^10.0.0", "isemail": "^3.2.0", "js-beautify": "^1.15.4", @@ -46,8 +46,8 @@ "lucide-react": "^1.8.0", "mailchecker": "^6.0.20", "nanoid": "^5.1.9", - "next": "14.2.35", - "next-auth": "^4.24.14", + "next": "15.5.24", + "next-auth": "^4.24.15", "next-themes": "^0.4.6", "pg": "^8.20.0", "pg-boss": "^12.15.0", @@ -57,12 +57,19 @@ "react-dom": "^18", "recharts": "^3.8.1", "resend": "^6.11.0", - "sanitize-html": "^2.17.3", + "sanitize-html": "^2.17.4", "svix": "^1.90.0", "tailwind-merge": "^3.5.0", "tailwindcss-animate": "^1.0.7", "xlsx": "^0.18.5", - "zod": "^3.25.76" + "zod": "4.6.6" + }, + "overrides": { + "body-parser": "2.4.0", + "minimatch": "11.0.1", + "@anatine/zod-openapi": { + "zod": "4.6.6" + } }, "devDependencies": { "@types/bcryptjs": "^2.4.6", @@ -77,7 +84,7 @@ "dotenv-cli": "^11.0.0", "drizzle-kit": "^0.31.10", "eslint": "^8", - "eslint-config-next": "14.2.35", + "eslint-config-next": "15.5.24", "postcss": "^8", "shadcn": "^2.10.0", "tailwindcss": "^3.4.1", diff --git a/test/validations.test.ts b/test/validations.test.ts new file mode 100644 index 0000000..3bfc562 --- /dev/null +++ b/test/validations.test.ts @@ -0,0 +1,54 @@ +import { describe, it } from 'node:test' +import assert from 'node:assert/strict' +import { createContactSchema } from '@/lib/validations/contacts' +import { formSubmissionSchema } from '@/lib/validations/forms' +import { createProviderSchema } from '@/lib/validations/providers' +import { createTemplateSchema } from '@/lib/validations/templates' +import { createInviteSchema } from '@/lib/validations/team' + +describe('zod validation schemas', () => { + it('createContactSchema accepts a valid email + metadata record and rejects an invalid email', () => { + const parsed = createContactSchema.parse({ + email: 'ada@example.com', + metadata: { source: 'signup' }, + }) + assert.equal(parsed.email, 'ada@example.com') + assert.deepEqual(parsed.metadata, { source: 'signup' }) + assert.equal(createContactSchema.safeParse({ email: 'not-an-email' }).success, false) + }) + + it('createContactSchema keeps the custom email error message', () => { + const result = createContactSchema.safeParse({ email: 'nope' }) + assert.equal(result.success, false) + assert.ok(result.error.issues.some((issue) => issue.message === 'Valid email is required')) + }) + + it('createInviteSchema validates the invited email', () => { + assert.equal(createInviteSchema.safeParse({ email: 'ada@example.com' }).success, true) + const result = createInviteSchema.safeParse({ email: 'not-an-email' }) + assert.equal(result.success, false) + assert.ok(result.error.issues.some((issue) => issue.message === 'Enter a valid email')) + }) + + it('createProviderSchema keeps the custom message for a missing provider type', () => { + const result = createProviderSchema.safeParse({ name: 'Provider', apiKey: 'k' }) + assert.equal(result.success, false) + assert.ok( + result.error.issues.some((issue) => issue.message === 'Type must be resend or ses'), + ) + }) + + it('createTemplateSchema rejects a non-uuid campaign id', () => { + const result = createTemplateSchema.safeParse({ + name: 'Welcome', + fromCampaignId: 'not-a-uuid', + }) + assert.equal(result.success, false) + }) + + it('formSubmissionSchema normalises and validates the submitted email', () => { + const parsed = formSubmissionSchema.parse({ email: 'Ada@Example.com' }) + assert.equal(parsed.email, 'ada@example.com') + assert.equal(formSubmissionSchema.safeParse({ email: 'bad' }).success, false) + }) +})