diff --git a/_docs/security/CVE-2025-55182-React2Shell.md b/_docs/security/CVE-2025-55182-React2Shell.md new file mode 100644 index 00000000..809a9a77 --- /dev/null +++ b/_docs/security/CVE-2025-55182-React2Shell.md @@ -0,0 +1,300 @@ +# CVE-2025-55182 (React2Shell) - Critical Security Update + +**Status**: ✅ **RESOLVED - Upgrade Completed (December 6, 2025)** +**CVE ID**: CVE-2025-55182 +**Vulnerability Name**: React2Shell +**Severity**: Critical (RCE - Remote Code Execution) +**Date Identified**: December 2025 +**Last Updated**: December 6, 2025 + +--- + +## Executive Summary + +CVE-2025-55182 is a critical remote code execution (RCE) vulnerability affecting React Server Components in Next.js applications. Public exploits are available, and threat actors are actively probing for vulnerable applications. **Immediate upgrade is required.** + +### Current Status + +- **Current Next.js Version**: 16.0.7 (PATCHED) ✅ +- **Required Version**: 16.0.7 (PATCHED) +- **Upgrade Status**: ✅ **COMPLETED** (December 6, 2025) + +--- + +## Vulnerability Details + +### What is React2Shell? + +React2Shell is a critical RCE vulnerability that affects React Server Components (RSC) implementations. The vulnerability allows attackers to execute arbitrary code on the server, potentially leading to: + +- Complete server compromise +- Data exfiltration +- Unauthorized access to internal systems +- Service disruption + +### Affected Versions + +All Next.js versions between **15.0.0** and **16.0.6** are vulnerable: + +| Next.js Version Range | Patched Release | +|----------------------|-----------------| +| 15.0.x | 15.0.5 | +| 15.1.x | 15.1.9 | +| 15.2.x | 15.2.6 | +| 15.3.x | 15.3.6 | +| 15.4.x | 15.4.8 | +| 15.5.x | 15.5.7 | +| **16.0.x** | **16.0.7** ⬅️ **Our Target** | +| 14 canaries after 14.3.0-canary.76 | Downgrade to 14.3.0-canary.76 | +| 15 canaries before 15.6.0-canary.58 | 15.6.0-canary.58 | +| 16 canaries before 16.1.0-canary.12 | 16.1.0-canary.12+ | + +### Threat Landscape + +- ✅ **Public Exploits Available**: Proof-of-concept exploits are publicly available +- ✅ **Active Exploitation**: Threat actors are actively probing for vulnerable applications +- ✅ **Vercel WAF Protection**: Vercel has deployed WAF rules to block known exploit patterns +- ⚠️ **WAF Limitations**: WAF rules cannot guarantee 100% protection against all variants +- 🔴 **Upgrade Required**: Upgrading to a patched version is the **only complete fix** + +--- + +## Remediation Steps + +### Step 1: Verify Current Version + +Check your current Next.js version using one of these methods: + +**Method 1: Browser Console** +```javascript +// Load any page from your app and run: +next.version +``` + +**Method 2: Package.json** +```bash +# Check package.json +grep '"next":' package.json +``` + +**Method 3: Installed Package** +```bash +pnpm list next +``` + +### Step 2: Automated Fix (Recommended First Attempt) + +Vercel has provided an automated fix tool: + +```bash +npx fix-react2shell-next +``` + +This tool will: +- Detect vulnerable Next.js versions +- Automatically upgrade to the patched version +- Update related dependencies if needed + +### Step 3: Manual Upgrade (If Automated Tool Doesn't Work) + +If the automated tool doesn't work or you need manual control: + +1. **Update package.json**: + ```json + { + "dependencies": { + "next": "16.0.7" // Update from 16.0.3 + }, + "devDependencies": { + "@next/bundle-analyzer": "^16.0.7" // Update to match Next.js version + } + } + ``` + +2. **Update dependencies**: + ```bash + pnpm install + ``` + +3. **Verify installation**: + ```bash + pnpm list next + # Should show: next@16.0.7 + ``` + +### Step 4: Verify Upgrade + +1. **Check version in browser**: + - Load any page from your app + - Open browser console + - Run: `next.version` + - Should display: `"16.0.7"` + +2. **Run type checking**: + ```bash + pnpm run typecheck + ``` + +3. **Test build**: + ```bash + pnpm run build + ``` + +4. **Test dev server**: + ```bash + pnpm run dev + ``` + +### Step 5: Test Critical Functionality + +After upgrade, verify these critical features still work: + +- ✅ Dynamic routes (e.g., `/features/[slug]`, `/products/[slug]`) +- ✅ Server components and server actions +- ✅ API routes +- ✅ Middleware functionality +- ✅ Metadata generation +- ✅ Image optimization + +--- + +## Project-Specific Upgrade Details + +### Current Configuration + +- **Package Manager**: pnpm 9.15.9+ +- **Node Version**: >=20.0.0 <21.0.0 +- **Next.js Version**: 16.0.3 → **16.0.7** (target) + +### Related Packages to Update + +- `next`: `16.0.3` → `16.0.7` +- `@next/bundle-analyzer`: `^16.0.3` → `^16.0.7` +- `@next/third-parties`: `^15.4.3` (check compatibility, may need update) + +### Files Modified + +- `package.json` - Update Next.js and related @next package versions +- `pnpm-lock.yaml` - Auto-updated by `pnpm install` + +--- + +## Risk Assessment + +### Upgrade Risk: **LOW** + +This is a **patch version upgrade** (16.0.3 → 16.0.7) within the same major.minor version, which means: + +- ✅ **No Breaking Changes Expected**: Patch versions typically only include bug fixes and security patches +- ✅ **Backward Compatible**: Should not require code changes +- ⚠️ **Testing Required**: Still need to verify critical functionality works + +### Security Risk: **CRITICAL** + +- 🔴 **Public Exploits Available**: Attackers can easily exploit this vulnerability +- 🔴 **Active Probing**: Threat actors are actively scanning for vulnerable apps +- 🔴 **RCE Impact**: Successful exploitation leads to complete server compromise +- 🔴 **Immediate Action Required**: Upgrade cannot be delayed + +--- + +## Deployment Checklist + +- [ ] Run automated fix tool: `npx fix-react2shell-next` +- [ ] Verify Next.js version updated to 16.0.7 +- [ ] Run `pnpm install` to update lockfile +- [ ] Run `pnpm run typecheck` - verify no type errors +- [ ] Run `pnpm run build` - verify build succeeds +- [ ] Run `pnpm run dev` - verify dev server starts +- [ ] Test critical routes (dynamic routes, API routes) +- [ ] Test server components and server actions +- [ ] Verify middleware functionality +- [ ] Check production deployment on Vercel +- [ ] Monitor for any errors or warnings post-deployment + +--- + +## Post-Upgrade Monitoring + +After deployment, monitor for: + +1. **Build Errors**: Check build logs for any new errors +2. **Runtime Errors**: Monitor application logs for unexpected behavior +3. **Performance Issues**: Watch for any performance degradation +4. **Function Timeouts**: Monitor for unusual POST requests or timeout spikes +5. **Unexpected Behavior**: Review application activity for anomalies + +--- + +## Additional Resources + +### Official Documentation + +- **Vercel Security Advisory**: [Blog Post](https://vercel.com/blog) +- **React Security Advisory**: [react.dev blog](https://react.dev/blog) +- **Next.js Security Advisory**: Check Next.js documentation + +### Vercel Platform Protections + +- ✅ **WAF Rules**: Vercel has deployed WAF rules to block known exploit patterns +- ✅ **Deployment Blocking**: Vercel blocks new deployments of vulnerable Next.js versions +- ⚠️ **Not a Replacement**: WAF rules are a defense layer, not a replacement for upgrading + +### Support + +- **Vercel Security Email**: security@vercel.com +- **HackerOne Bounty Program**: $25,000 (high) / $50,000 (critical) for bypass reports + +--- + +## FAQ + +### Q: How do I know if my app was exploited? + +A: Review application logs for: +- Unusual POST requests +- Spikes in function timeouts +- Unexpected behavior or activity +- However, note that function timeouts don't reliably indicate compromise + +### Q: Are Vercel WAF rules enough protection? + +A: No. WAF rules provide an additional layer of defense but cannot guarantee 100% protection against all possible attack variants. **Upgrading to a patched version is the only complete fix.** + +### Q: What if I'm using canary-only features? + +A: You should still prioritize updating. See the Next.js Security Advisory for instructions on updating without disabling canary features. + +### Q: Should I test with publicly available POCs? + +A: **No.** We caution against using publicly available exploits against production environments. Instead, follow the verification steps above. + +### Q: What about other frameworks using React Server Components? + +A: If you use another framework implementing RSC, consult the React Security Advisory on react.dev for framework-specific guidance. + +--- + +## Change Log + +| Date | Action | Details | +|------|--------|---------| +| 2025-12-06 | Document Created | Initial documentation for CVE-2025-55182 | +| 2025-12-06 | Upgrade Plan Created | Plan created for Next.js 16.0.3 → 16.0.7 upgrade | +| 2025-12-06 | Upgrade Completed | Successfully upgraded Next.js from 16.0.3 to 16.0.7 | +| 2025-12-06 | Verification Complete | Confirmed Next.js 16.0.7 installed, build tested | + +--- + +## Related Files + +- `package.json` - Package dependencies +- `pnpm-lock.yaml` - Dependency lockfile +- `.cursor/plans/upgrade_next.js_to_patch_cve-2025-55182_*.plan.md` - Upgrade plan + +--- + +**Last Reviewed**: December 6, 2025 +**Next Review**: After upgrade completion +**Document Owner**: Security Team + diff --git a/package.json b/package.json index b4a1442c..ad8b960a 100644 --- a/package.json +++ b/package.json @@ -1,269 +1,269 @@ { - "name": "dealscale", - "private": true, - "engines": { - "node": ">=20.0.0 <21.0.0", - "pnpm": ">=6.0.0" - }, - "version": "0.0.0", - "scripts": { - "dev": "pnpm run check:env && next dev", - "dev-turbo": "pnpm run check:env && next dev --turbopack", - "build": "pnpm run generate:data && pnpm run check:chunk && pnpm run check:meta && next build", - "typecheck": "tsc --noEmit", - "postbuild": "pnpm run submit:sitemap", - "build:cf": "pnpm exec next-on-pages", - "prepare": "node -e \"const{spawnSync}=require('node:child_process');const CI=!!process.env.CI;const SKIP=process.env.SKIP_HUSKY==='1';const PROD=process.env.NODE_ENV==='production';if(CI||SKIP||PROD){console.log('[prepare] skipping husky');process.exit(0)}const r=spawnSync('pnpm',['exec','husky'],{stdio:'inherit',shell:true});process.exit(r.status??0)\"", - "archive:reports": "pnpm exec tsx tools/reports/archive-lighthouse-report.ts", - "archive:coverage": "pnpm exec tsx tools/reports/archive-vitest-coverage.ts", - "archive:trivy": "pnpm exec tsx tools/security/archive-trivy-report.ts", - "archive:owasp": "pnpm exec tsx tools/security/archive-owasp-report.ts", - "archive:security": "pnpm run archive:trivy && pnpm run archive:owasp", - "start": "pnpm run check:env && next start", - "commit:stage": "git add . && pnpm exec lint-staged", - "check:env": "pnpm exec tsx tools/checks/check-analytics-env.ts", - "check:chunk": "pnpm exec tsx tools/checks/verify-chunk-error-handler.ts", - "check:meta": "pnpm exec tsx tools/checks/verify-meta-description.ts", - "validate:sitemap": "pnpm exec tsx tools/validate-sitemap.ts", - "landing:build": "rm -rf dist && pnpm run check:env && next build && next export -o dist", - "dev:vite": "pnpm exec tsx scripts/bundlers/ensure-preview-flag.ts vite:dev && pnpm exec vite --config tools/vite-preview/vite.config.ts", - "build:vite": "pnpm exec tsx scripts/bundlers/ensure-preview-flag.ts vite:build && pnpm exec vite build --config tools/vite-preview/vite.config.ts", - "dev:rspack": "pnpm exec tsx scripts/bundlers/ensure-preview-flag.ts rspack:dev && pnpm exec rsbuild dev --config tools/rspack-preview/rsbuild.config.ts", - "build:rspack": "pnpm exec tsx scripts/bundlers/ensure-preview-flag.ts rspack:build && pnpm exec rsbuild build --config tools/rspack-preview/rsbuild.config.ts", - "test": "pnpm run test:vitest", - "test:vitest": "pnpm exec vitest run", - "test:coverage": "pnpm exec vitest run --run", - "security:trivy": "pnpm exec tsx tools/security/run-trivy-scan.ts", - "security:owasp": "pnpm exec tsx tools/security/run-owasp-baseline.ts", - "security:scan": "pnpm run security:trivy && pnpm run security:owasp", - "lint": "pnpm exec biome check --write src", - "format": "biome format --write src", - "clean-build": "rm -rf .next dist .svelte-kit node_modules/.cache && pnpm build", - "export:categories": "node scripts/run-ts.js scripts/strapi/export-categories.ts", - "seed:categories": "node scripts/run-ts.js scripts/strapi/seed-to-strapi.ts", - "seed:generic": "node scripts/run-ts.js scripts/strapi/seed-generic.ts", - "export:generic": "node scripts/run-ts.js scripts/strapi/export-generic.ts", - "export:categories:generic": "pnpm run export:generic -- --module src/data/categories.ts --collection categories", - "seed:categories:generic": "pnpm run seed:generic -- --module src/data/categories.ts --collection categories", - "export:products": "node scripts/run-ts.js scripts/strapi/export-products.ts", - "export:services": "node scripts/run-ts.js scripts/strapi/export-services.ts", - "export:company": "node scripts/run-ts.js scripts/strapi/export-company.ts", - "seed:products": "STRAPI_COLLECTION=products pnpm run seed:generic", - "seed:services": "STRAPI_COLLECTION=services pnpm run seed:generic", - "seed:company": "STRAPI_COLLECTION=company pnpm run seed:generic", - "export:all": "node scripts/run-ts.js scripts/strapi/export-all.ts", - "test:export": "node scripts/run-ts.js scripts/strapi/test-export.ts", - "seed:all": "pnpm run seed:categories && pnpm run seed:products && pnpm run seed:services && pnpm run seed:company", - "submit:sitemap": "node tools/deploy/submit-sitemap.js", - "seo:check": "pnpm exec tsx .github/scripts/seo-check.ts", - "seo:lhci": "bash .github/scripts/seo-lhci.sh", - "contact:test": "bash .github/scripts/contact-test.sh", - "doctor:rspack": "pnpm run build:rspack && pnpm dlx @rsdoctor/cli analyze --profile .rsdoctor/manifest.json", - "perf:lighthouse:local": "pnpm dlx lighthouse@12 http://localhost:3000/ --preset=perf --output=json --output=html --output-path=reports/lighthouse/local", - "perf:lighthouse:prod-local": "lighthouse http://localhost:3000/ --preset=perf --output=json --output=html --output-path=reports/lighthouse/prod", - "perf:lighthouse:prod": "pnpm dlx lighthouse@12 https://dealscale.io/ --preset=perf --throttling.cpuSlowdownMultiplier=4 --throttling.downloadThroughputKbps=1500 --throttling.uploadThroughputKbps=750 --output=json --output=html --output-path=reports/lighthouse/prod", - "perf:lighthouse:local:stable": "pnpm dlx lighthouse@12 http://localhost:3000/ --preset=perf --max-wait-for-load=180000 --chrome-flags=\"--headless=new --disable-dev-shm-usage --no-sandbox --window-size=412,823\" --output=json --output=html --output-path=reports/lighthouse/local-stable", - "perf:lighthouse:desktop:stable": "pnpm dlx lighthouse@12 http://localhost:3000/ --preset=desktop --only-categories=performance --max-wait-for-load=180000 --chrome-flags=\"--headless=new --disable-dev-shm-usage --no-sandbox --window-size=1365,812\" --output=json --output=html --output-path=reports/lighthouse/local-desktop-stable", - "perf:lighthouse:prod:stable": "pnpm dlx lighthouse@12 https://dealscale.io/ --preset=perf --max-wait-for-load=180000 --chrome-flags=\"--headless=new --disable-dev-shm-usage --no-sandbox --window-size=412,823\" --output=json --output=html --output-path=reports/lighthouse/prod-stable", - "build:analyze:head": "ANALYZE=true pnpm next build 2>&1 | head -20", - "generate:data": "pnpm exec tsx tools/data/generate-data-manifest.ts", - "export:landing": "node tools/run-ts.js tools/strapi/export-landing.ts", - "notion:schema": "node scripts/run-ts.js scripts/notion/fetchLinktreeSchema.ts", - "notion:schema:write": "node scripts/run-ts.js scripts/notion/fetchLinktreeSchema.ts --write", - "pixel:server": "node pixel-mock-server/server.js", - "pixel:test": "pnpm run pixel:server & pnpm run test tests/pixel-ingestion.test.ts", - "dev:with-pixel": "pnpm run pixel:server & pnpm run dev" - }, - "lint-staged": { - "**/*.{js,jsx,ts,tsx,json,css,md,html,scss}": [ - "pnpm exec biome format --write" - ] - }, - "pnpm": { - "overrides": { - "restore-cursor>onetime": "5.1.2" - } - }, - "browserslist": { - "production": [ - "chrome >= 90", - "edge >= 90", - "firefox >= 90", - "safari >= 15", - "supports es6-module", - "supports async-functions", - "supports object-values" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - }, - "dependencies": { - "@analytics/google-analytics": "^1.1.0", - "@calcom/embed-react": "^1.5.2", - "@faker-js/faker": "^9.9.0", - "@hookform/resolvers": "^3.9.0", - "@mdx-js/react": "^3.1.1", - "@microsoft/clarity": "^1.0.0", - "@next/third-parties": "^15.4.3", - "@notionhq/client": "^5.1.0", - "@plausible-analytics/tracker": "^0.4.3", - "@radix-ui/react-accordion": "^1.2.0", - "@radix-ui/react-alert-dialog": "^1.1.1", - "@radix-ui/react-aspect-ratio": "^1.1.0", - "@radix-ui/react-avatar": "^1.1.0", - "@radix-ui/react-checkbox": "^1.1.1", - "@radix-ui/react-collapsible": "^1.1.0", - "@radix-ui/react-context-menu": "^2.2.1", - "@radix-ui/react-dialog": "^1.1.2", - "@radix-ui/react-dropdown-menu": "^2.1.1", - "@radix-ui/react-hover-card": "^1.1.1", - "@radix-ui/react-icons": "^1.3.2", - "@radix-ui/react-label": "^2.1.0", - "@radix-ui/react-menubar": "^1.1.1", - "@radix-ui/react-navigation-menu": "^1.2.0", - "@radix-ui/react-popover": "^1.1.1", - "@radix-ui/react-progress": "^1.1.0", - "@radix-ui/react-radio-group": "^1.2.0", - "@radix-ui/react-scroll-area": "^1.1.0", - "@radix-ui/react-select": "^2.1.1", - "@radix-ui/react-separator": "^1.1.0", - "@radix-ui/react-slider": "^1.2.0", - "@radix-ui/react-slot": "^1.2.3", - "@radix-ui/react-switch": "^1.1.0", - "@radix-ui/react-tabs": "^1.1.0", - "@radix-ui/react-toast": "^1.2.1", - "@radix-ui/react-toggle": "^1.1.0", - "@radix-ui/react-toggle-group": "^1.1.0", - "@radix-ui/react-tooltip": "^1.1.4", - "@sendgrid/client": "^8.1.5", - "@sendgrid/mail": "^8.1.5", - "@slack/web-api": "^7.10.0", - "@splinetool/react-spline": "^4.0.0", - "@stitches/react": "^1.2.8", - "@stripe/react-stripe-js": "^3.6.0", - "@stripe/stripe-js": "^7.2.0", - "@supabase/auth-helpers-nextjs": "^0.10.0", - "@supabase/supabase-js": "^2.58.0", - "@tabler/icons-react": "^3.34.0", - "@tailwindcss/typography": "^0.5.15", - "@tanstack/react-query": "^5.72.1", - "@types/leaflet": "^1.9.20", - "@types/papaparse": "^5.5.0", - "@upstash/ratelimit": "^2.0.6", - "@upstash/redis": "^1.35.3", - "analytics": "^0.8.19", - "autoprefixer": "^10.4.20", - "axios": "^1.8.4", - "cheerio": "^1.0.0", - "class-variance-authority": "^0.7.1", - "cloudinary": "^2.6.0", - "clsx": "^2.1.1", - "cmdk": "^1.0.0", - "country-state-city": "^3.2.1", - "date-fns": "^3.6.0", - "dompurify": "^3.2.6", - "embla-carousel-react": "^8.3.0", - "entities": "^6.0.0", - "fast-xml-parser": "^5.3.1", - "framer-motion": "^12.5.0", - "gray-matter": "^4.0.3", - "input-otp": "^1.2.4", - "is-mobile": "^5.0.0", - "js-cookie": "^3.0.5", - "leaflet": "^1.9.4", - "localtunnel": "^2.0.2", - "lottie-react": "^2.4.1", - "lucide-react": "^0.462.0", - "medium-blogs-fetcher": "^1.0.5", - "motion": "^12.19.1", - "next": "16.0.3", - "next-auth": "^4.24.11", - "next-mdx-remote": "^5.0.0", - "next-themes": "^0.3.0", - "ngrok": "5.0.0-beta.2", - "papaparse": "^5.5.3", - "postcss": "^8.4.47", - "react": "^18.3.1", - "react-day-picker": "^8.10.1", - "react-dom": "^18.3.1", - "react-facebook-pixel": "^1.0.4", - "react-hook-form": "^7.53.0", - "react-hot-toast": "^2.5.2", - "react-leaflet": "^5.0.0", - "react-markdown": "^9.1.0", - "react-resizable-panels": "^2.1.3", - "react-social-icons": "^6.24.0", - "react-syntax-highlighter": "^15.6.1", - "react-tweet": "^3.2.2", - "recharts": "^2.12.7", - "rehype-autolink-headings": "^7.1.0", - "rehype-slug": "^6.0.0", - "remark-gfm": "^4.0.1", - "rss-parser": "^3.13.0", - "server-only": "^0.0.1", - "sonner": "^1.5.0", - "stripe": "^18.0.0", - "tailwind-merge": "^2.6.0", - "tailwindcss": "^3.4.11", - "tailwindcss-animate": "^1.0.7", - "uuid": "^11.1.0", - "vaul": "^0.9.3", - "xlsx": "^0.18.5", - "zod": "^3.25.67", - "zustand": "^5.0.6" - }, - "devDependencies": { - "@biomejs/biome": "1.9.4", - "@cloudflare/next-on-pages": "^1.13.16", - "@commitlint/cli": "^20.1.0", - "@commitlint/config-conventional": "^20.0.0", - "@eslint/js": "^9.9.0", - "@lhci/cli": "^0.13.0", - "@next/bundle-analyzer": "^16.0.3", - "@rsbuild/core": "^1.5.6", - "@rsbuild/plugin-react": "^1.0.8", - "@rspack/cli": "^1.0.8", - "@rspack/core": "^1.0.8", - "@svgr/webpack": "^8.1.0", - "@testing-library/jest-dom": "^6.6.3", - "@testing-library/react": "^16.3.0", - "@testing-library/user-event": "^14.6.1", - "@turbo/gen": "2.5.2", - "@types/dompurify": "^3.2.0", - "@types/js-cookie": "^3.0.6", - "@types/node": "^22.5.5", - "@types/node-fetch": "^2.6.12", - "@types/react": "^18.3.21", - "@types/react-dom": "^18.3.0", - "@typescript-eslint/eslint-plugin": "^8.29.1", - "@typescript-eslint/parser": "^8.29.1", - "@vitejs/plugin-react-swc": "^3.9.0", - "@vitest/coverage-v8": "^4.0.8", - "dotenv": "^16.5.0", - "eslint": "^9.9.0", - "eslint-plugin-react-hooks": "^5.1.0-rc.0", - "eslint-plugin-react-refresh": "^0.4.9", - "fast-glob": "^3.3.2", - "globals": "^15.9.0", - "husky": "^9.1.7", - "jsdom": "^25.0.0", - "lint-staged": "^16.2.6", - "lovable-tagger": "^1.1.7", - "node-fetch": "^2.7.0", - "prettier": "^3.5.3", - "prettier-plugin-tailwindcss": "^0.6.11", - "snyk": "^1.1290.0", - "ts-node": "^10.9.2", - "tsconfig-paths": "^4.2.0", - "tsx": "^4.7.0", - "turbo": "^2.5.2", - "typescript": "^5.8.3", - "typescript-eslint": "^8.0.1", - "vite": "^5.4.10", - "vitest": "^2.1.4" - }, - "packageManager": "pnpm@9.15.9+sha512.68046141893c66fad01c079231128e9afb89ef87e2691d69e4d40eee228988295fd4682181bae55b58418c3a253bde65a505ec7c5f9403ece5cc3cd37dcf2531" + "name": "dealscale", + "private": true, + "engines": { + "node": ">=20.0.0 <21.0.0", + "pnpm": ">=6.0.0" + }, + "version": "0.0.0", + "scripts": { + "dev": "pnpm run check:env && next dev", + "dev-turbo": "pnpm run check:env && next dev --turbopack", + "build": "pnpm run generate:data && pnpm run check:chunk && pnpm run check:meta && next build", + "typecheck": "tsc --noEmit", + "postbuild": "pnpm run submit:sitemap", + "build:cf": "pnpm exec next-on-pages", + "prepare": "node -e \"const{spawnSync}=require('node:child_process');const CI=!!process.env.CI;const SKIP=process.env.SKIP_HUSKY==='1';const PROD=process.env.NODE_ENV==='production';if(CI||SKIP||PROD){console.log('[prepare] skipping husky');process.exit(0)}const r=spawnSync('pnpm',['exec','husky'],{stdio:'inherit',shell:true});process.exit(r.status??0)\"", + "archive:reports": "pnpm exec tsx tools/reports/archive-lighthouse-report.ts", + "archive:coverage": "pnpm exec tsx tools/reports/archive-vitest-coverage.ts", + "archive:trivy": "pnpm exec tsx tools/security/archive-trivy-report.ts", + "archive:owasp": "pnpm exec tsx tools/security/archive-owasp-report.ts", + "archive:security": "pnpm run archive:trivy && pnpm run archive:owasp", + "start": "pnpm run check:env && next start", + "commit:stage": "git add . && pnpm exec lint-staged", + "check:env": "pnpm exec tsx tools/checks/check-analytics-env.ts", + "check:chunk": "pnpm exec tsx tools/checks/verify-chunk-error-handler.ts", + "check:meta": "pnpm exec tsx tools/checks/verify-meta-description.ts", + "validate:sitemap": "pnpm exec tsx tools/validate-sitemap.ts", + "landing:build": "rm -rf dist && pnpm run check:env && next build && next export -o dist", + "dev:vite": "pnpm exec tsx scripts/bundlers/ensure-preview-flag.ts vite:dev && pnpm exec vite --config tools/vite-preview/vite.config.ts", + "build:vite": "pnpm exec tsx scripts/bundlers/ensure-preview-flag.ts vite:build && pnpm exec vite build --config tools/vite-preview/vite.config.ts", + "dev:rspack": "pnpm exec tsx scripts/bundlers/ensure-preview-flag.ts rspack:dev && pnpm exec rsbuild dev --config tools/rspack-preview/rsbuild.config.ts", + "build:rspack": "pnpm exec tsx scripts/bundlers/ensure-preview-flag.ts rspack:build && pnpm exec rsbuild build --config tools/rspack-preview/rsbuild.config.ts", + "test": "pnpm run test:vitest", + "test:vitest": "pnpm exec vitest run", + "test:coverage": "pnpm exec vitest run --run", + "security:trivy": "pnpm exec tsx tools/security/run-trivy-scan.ts", + "security:owasp": "pnpm exec tsx tools/security/run-owasp-baseline.ts", + "security:scan": "pnpm run security:trivy && pnpm run security:owasp", + "lint": "pnpm exec biome check --write src", + "format": "biome format --write src", + "clean-build": "rm -rf .next dist .svelte-kit node_modules/.cache && pnpm build", + "export:categories": "node scripts/run-ts.js scripts/strapi/export-categories.ts", + "seed:categories": "node scripts/run-ts.js scripts/strapi/seed-to-strapi.ts", + "seed:generic": "node scripts/run-ts.js scripts/strapi/seed-generic.ts", + "export:generic": "node scripts/run-ts.js scripts/strapi/export-generic.ts", + "export:categories:generic": "pnpm run export:generic -- --module src/data/categories.ts --collection categories", + "seed:categories:generic": "pnpm run seed:generic -- --module src/data/categories.ts --collection categories", + "export:products": "node scripts/run-ts.js scripts/strapi/export-products.ts", + "export:services": "node scripts/run-ts.js scripts/strapi/export-services.ts", + "export:company": "node scripts/run-ts.js scripts/strapi/export-company.ts", + "seed:products": "STRAPI_COLLECTION=products pnpm run seed:generic", + "seed:services": "STRAPI_COLLECTION=services pnpm run seed:generic", + "seed:company": "STRAPI_COLLECTION=company pnpm run seed:generic", + "export:all": "node scripts/run-ts.js scripts/strapi/export-all.ts", + "test:export": "node scripts/run-ts.js scripts/strapi/test-export.ts", + "seed:all": "pnpm run seed:categories && pnpm run seed:products && pnpm run seed:services && pnpm run seed:company", + "submit:sitemap": "node tools/deploy/submit-sitemap.js", + "seo:check": "pnpm exec tsx .github/scripts/seo-check.ts", + "seo:lhci": "bash .github/scripts/seo-lhci.sh", + "contact:test": "bash .github/scripts/contact-test.sh", + "doctor:rspack": "pnpm run build:rspack && pnpm dlx @rsdoctor/cli analyze --profile .rsdoctor/manifest.json", + "perf:lighthouse:local": "pnpm dlx lighthouse@12 http://localhost:3000/ --preset=perf --output=json --output=html --output-path=reports/lighthouse/local", + "perf:lighthouse:prod-local": "lighthouse http://localhost:3000/ --preset=perf --output=json --output=html --output-path=reports/lighthouse/prod", + "perf:lighthouse:prod": "pnpm dlx lighthouse@12 https://dealscale.io/ --preset=perf --throttling.cpuSlowdownMultiplier=4 --throttling.downloadThroughputKbps=1500 --throttling.uploadThroughputKbps=750 --output=json --output=html --output-path=reports/lighthouse/prod", + "perf:lighthouse:local:stable": "pnpm dlx lighthouse@12 http://localhost:3000/ --preset=perf --max-wait-for-load=180000 --chrome-flags=\"--headless=new --disable-dev-shm-usage --no-sandbox --window-size=412,823\" --output=json --output=html --output-path=reports/lighthouse/local-stable", + "perf:lighthouse:desktop:stable": "pnpm dlx lighthouse@12 http://localhost:3000/ --preset=desktop --only-categories=performance --max-wait-for-load=180000 --chrome-flags=\"--headless=new --disable-dev-shm-usage --no-sandbox --window-size=1365,812\" --output=json --output=html --output-path=reports/lighthouse/local-desktop-stable", + "perf:lighthouse:prod:stable": "pnpm dlx lighthouse@12 https://dealscale.io/ --preset=perf --max-wait-for-load=180000 --chrome-flags=\"--headless=new --disable-dev-shm-usage --no-sandbox --window-size=412,823\" --output=json --output=html --output-path=reports/lighthouse/prod-stable", + "build:analyze:head": "ANALYZE=true pnpm next build 2>&1 | head -20", + "generate:data": "pnpm exec tsx tools/data/generate-data-manifest.ts", + "export:landing": "node tools/run-ts.js tools/strapi/export-landing.ts", + "notion:schema": "node scripts/run-ts.js scripts/notion/fetchLinktreeSchema.ts", + "notion:schema:write": "node scripts/run-ts.js scripts/notion/fetchLinktreeSchema.ts --write", + "pixel:server": "node pixel-mock-server/server.js", + "pixel:test": "pnpm run pixel:server & pnpm run test tests/pixel-ingestion.test.ts", + "dev:with-pixel": "pnpm run pixel:server & pnpm run dev" + }, + "lint-staged": { + "**/*.{js,jsx,ts,tsx,json,css,md,html,scss}": [ + "pnpm exec biome format --write" + ] + }, + "pnpm": { + "overrides": { + "restore-cursor>onetime": "5.1.2" + } + }, + "browserslist": { + "production": [ + "chrome >= 90", + "edge >= 90", + "firefox >= 90", + "safari >= 15", + "supports es6-module", + "supports async-functions", + "supports object-values" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "dependencies": { + "@analytics/google-analytics": "^1.1.0", + "@calcom/embed-react": "^1.5.2", + "@faker-js/faker": "^9.9.0", + "@hookform/resolvers": "^3.9.0", + "@mdx-js/react": "^3.1.1", + "@microsoft/clarity": "^1.0.0", + "@next/third-parties": "^15.4.3", + "@notionhq/client": "^5.1.0", + "@plausible-analytics/tracker": "^0.4.3", + "@radix-ui/react-accordion": "^1.2.0", + "@radix-ui/react-alert-dialog": "^1.1.1", + "@radix-ui/react-aspect-ratio": "^1.1.0", + "@radix-ui/react-avatar": "^1.1.0", + "@radix-ui/react-checkbox": "^1.1.1", + "@radix-ui/react-collapsible": "^1.1.0", + "@radix-ui/react-context-menu": "^2.2.1", + "@radix-ui/react-dialog": "^1.1.2", + "@radix-ui/react-dropdown-menu": "^2.1.1", + "@radix-ui/react-hover-card": "^1.1.1", + "@radix-ui/react-icons": "^1.3.2", + "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-menubar": "^1.1.1", + "@radix-ui/react-navigation-menu": "^1.2.0", + "@radix-ui/react-popover": "^1.1.1", + "@radix-ui/react-progress": "^1.1.0", + "@radix-ui/react-radio-group": "^1.2.0", + "@radix-ui/react-scroll-area": "^1.1.0", + "@radix-ui/react-select": "^2.1.1", + "@radix-ui/react-separator": "^1.1.0", + "@radix-ui/react-slider": "^1.2.0", + "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-switch": "^1.1.0", + "@radix-ui/react-tabs": "^1.1.0", + "@radix-ui/react-toast": "^1.2.1", + "@radix-ui/react-toggle": "^1.1.0", + "@radix-ui/react-toggle-group": "^1.1.0", + "@radix-ui/react-tooltip": "^1.1.4", + "@sendgrid/client": "^8.1.5", + "@sendgrid/mail": "^8.1.5", + "@slack/web-api": "^7.10.0", + "@splinetool/react-spline": "^4.0.0", + "@stitches/react": "^1.2.8", + "@stripe/react-stripe-js": "^3.6.0", + "@stripe/stripe-js": "^7.2.0", + "@supabase/auth-helpers-nextjs": "^0.10.0", + "@supabase/supabase-js": "^2.58.0", + "@tabler/icons-react": "^3.34.0", + "@tailwindcss/typography": "^0.5.15", + "@tanstack/react-query": "^5.72.1", + "@types/leaflet": "^1.9.20", + "@types/papaparse": "^5.5.0", + "@upstash/ratelimit": "^2.0.6", + "@upstash/redis": "^1.35.3", + "analytics": "^0.8.19", + "autoprefixer": "^10.4.20", + "axios": "^1.8.4", + "cheerio": "^1.0.0", + "class-variance-authority": "^0.7.1", + "cloudinary": "^2.6.0", + "clsx": "^2.1.1", + "cmdk": "^1.0.0", + "country-state-city": "^3.2.1", + "date-fns": "^3.6.0", + "dompurify": "^3.2.6", + "embla-carousel-react": "^8.3.0", + "entities": "^6.0.0", + "fast-xml-parser": "^5.3.1", + "framer-motion": "^12.5.0", + "gray-matter": "^4.0.3", + "input-otp": "^1.2.4", + "is-mobile": "^5.0.0", + "js-cookie": "^3.0.5", + "leaflet": "^1.9.4", + "localtunnel": "^2.0.2", + "lottie-react": "^2.4.1", + "lucide-react": "^0.462.0", + "medium-blogs-fetcher": "^1.0.5", + "motion": "^12.19.1", + "next": "16.0.7", + "next-auth": "^4.24.11", + "next-mdx-remote": "^5.0.0", + "next-themes": "^0.3.0", + "ngrok": "5.0.0-beta.2", + "papaparse": "^5.5.3", + "postcss": "^8.4.47", + "react": "^18.3.1", + "react-day-picker": "^8.10.1", + "react-dom": "^18.3.1", + "react-facebook-pixel": "^1.0.4", + "react-hook-form": "^7.53.0", + "react-hot-toast": "^2.5.2", + "react-leaflet": "^5.0.0", + "react-markdown": "^9.1.0", + "react-resizable-panels": "^2.1.3", + "react-social-icons": "^6.24.0", + "react-syntax-highlighter": "^15.6.1", + "react-tweet": "^3.2.2", + "recharts": "^2.12.7", + "rehype-autolink-headings": "^7.1.0", + "rehype-slug": "^6.0.0", + "remark-gfm": "^4.0.1", + "rss-parser": "^3.13.0", + "server-only": "^0.0.1", + "sonner": "^1.5.0", + "stripe": "^18.0.0", + "tailwind-merge": "^2.6.0", + "tailwindcss": "^3.4.11", + "tailwindcss-animate": "^1.0.7", + "uuid": "^11.1.0", + "vaul": "^0.9.3", + "xlsx": "^0.18.5", + "zod": "^3.25.67", + "zustand": "^5.0.6" + }, + "devDependencies": { + "@biomejs/biome": "1.9.4", + "@cloudflare/next-on-pages": "^1.13.16", + "@commitlint/cli": "^20.1.0", + "@commitlint/config-conventional": "^20.0.0", + "@eslint/js": "^9.9.0", + "@lhci/cli": "^0.13.0", + "@next/bundle-analyzer": "^16.0.7", + "@rsbuild/core": "^1.5.6", + "@rsbuild/plugin-react": "^1.0.8", + "@rspack/cli": "^1.0.8", + "@rspack/core": "^1.0.8", + "@svgr/webpack": "^8.1.0", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", + "@turbo/gen": "2.5.2", + "@types/dompurify": "^3.2.0", + "@types/js-cookie": "^3.0.6", + "@types/node": "^22.5.5", + "@types/node-fetch": "^2.6.12", + "@types/react": "^18.3.21", + "@types/react-dom": "^18.3.0", + "@typescript-eslint/eslint-plugin": "^8.29.1", + "@typescript-eslint/parser": "^8.29.1", + "@vitejs/plugin-react-swc": "^3.9.0", + "@vitest/coverage-v8": "^4.0.8", + "dotenv": "^16.5.0", + "eslint": "^9.9.0", + "eslint-plugin-react-hooks": "^5.1.0-rc.0", + "eslint-plugin-react-refresh": "^0.4.9", + "fast-glob": "^3.3.2", + "globals": "^15.9.0", + "husky": "^9.1.7", + "jsdom": "^25.0.0", + "lint-staged": "^16.2.6", + "lovable-tagger": "^1.1.7", + "node-fetch": "^2.7.0", + "prettier": "^3.5.3", + "prettier-plugin-tailwindcss": "^0.6.11", + "snyk": "^1.1290.0", + "ts-node": "^10.9.2", + "tsconfig-paths": "^4.2.0", + "tsx": "^4.7.0", + "turbo": "^2.5.2", + "typescript": "^5.8.3", + "typescript-eslint": "^8.0.1", + "vite": "^5.4.10", + "vitest": "^2.1.4" + }, + "packageManager": "pnpm@9.15.9+sha512.68046141893c66fad01c079231128e9afb89ef87e2691d69e4d40eee228988295fd4682181bae55b58418c3a253bde65a505ec7c5f9403ece5cc3cd37dcf2531" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 97802b21..bf15a537 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -31,7 +31,7 @@ importers: version: 1.0.0 '@next/third-parties': specifier: ^15.4.3 - version: 15.4.3(next@16.0.3(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + version: 15.4.3(next@16.0.7(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) '@notionhq/client': specifier: ^5.1.0 version: 5.1.0 @@ -133,7 +133,7 @@ importers: version: 7.10.0 '@splinetool/react-spline': specifier: ^4.0.0 - version: 4.0.0(@splinetool/runtime@1.10.14)(next@16.0.3(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 4.0.0(@splinetool/runtime@1.10.14)(next@16.0.7(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@stitches/react': specifier: ^1.2.8 version: 1.2.8(react@18.3.1) @@ -246,11 +246,11 @@ importers: specifier: ^12.19.1 version: 12.19.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next: - specifier: 16.0.3 - version: 16.0.3(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: 16.0.7 + version: 16.0.7(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-auth: specifier: ^4.24.11 - version: 4.24.11(next@16.0.3(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 4.24.11(next@16.0.7(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-mdx-remote: specifier: ^5.0.0 version: 5.0.0(@types/react@18.3.23)(react@18.3.1) @@ -356,7 +356,7 @@ importers: version: 1.9.4 '@cloudflare/next-on-pages': specifier: ^1.13.16 - version: 1.13.16(next@16.0.3(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(wrangler@4.47.0) + version: 1.13.16(next@16.0.7(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(wrangler@4.47.0) '@commitlint/cli': specifier: ^20.1.0 version: 20.1.0(@types/node@22.15.33)(typescript@5.8.3) @@ -370,8 +370,8 @@ importers: specifier: ^0.13.0 version: 0.13.0 '@next/bundle-analyzer': - specifier: ^16.0.3 - version: 16.0.3 + specifier: ^16.0.7 + version: 16.0.7 '@rsbuild/core': specifier: ^1.5.6 version: 1.6.5 @@ -2305,56 +2305,56 @@ packages: '@napi-rs/wasm-runtime@1.0.7': resolution: {integrity: sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==} - '@next/bundle-analyzer@16.0.3': - resolution: {integrity: sha512-6Xo8f8/ZXtASfTPa6TH1aUn+xDg9Pkyl1YHVxu+89cVdLH7MnYjxv3rPOfEJ9BwCZCU2q4Flyw5MwltfD2pGbA==} + '@next/bundle-analyzer@16.0.7': + resolution: {integrity: sha512-Um2YA3TSQND+DpqlMDuPZsdjdpcgLzo1wF3zx4zcBCLecS7ucP7O9YFqvHhg000HXTgt++KIjZ9FUwyJSKk1Kw==} - '@next/env@16.0.3': - resolution: {integrity: sha512-IqgtY5Vwsm14mm/nmQaRMmywCU+yyMIYfk3/MHZ2ZTJvwVbBn3usZnjMi1GacrMVzVcAxJShTCpZlPs26EdEjQ==} + '@next/env@16.0.7': + resolution: {integrity: sha512-gpaNgUh5nftFKRkRQGnVi5dpcYSKGcZZkQffZ172OrG/XkrnS7UBTQ648YY+8ME92cC4IojpI2LqTC8sTDhAaw==} - '@next/swc-darwin-arm64@16.0.3': - resolution: {integrity: sha512-MOnbd92+OByu0p6QBAzq1ahVWzF6nyfiH07dQDez4/Nku7G249NjxDVyEfVhz8WkLiOEU+KFVnqtgcsfP2nLXg==} + '@next/swc-darwin-arm64@16.0.7': + resolution: {integrity: sha512-LlDtCYOEj/rfSnEn/Idi+j1QKHxY9BJFmxx7108A6D8K0SB+bNgfYQATPk/4LqOl4C0Wo3LACg2ie6s7xqMpJg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@16.0.3': - resolution: {integrity: sha512-i70C4O1VmbTivYdRlk+5lj9xRc2BlK3oUikt3yJeHT1unL4LsNtN7UiOhVanFdc7vDAgZn1tV/9mQwMkWOJvHg==} + '@next/swc-darwin-x64@16.0.7': + resolution: {integrity: sha512-rtZ7BhnVvO1ICf3QzfW9H3aPz7GhBrnSIMZyr4Qy6boXF0b5E3QLs+cvJmg3PsTCG2M1PBoC+DANUi4wCOKXpA==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@16.0.3': - resolution: {integrity: sha512-O88gCZ95sScwD00mn/AtalyCoykhhlokxH/wi1huFK+rmiP5LAYVs/i2ruk7xST6SuXN4NI5y4Xf5vepb2jf6A==} + '@next/swc-linux-arm64-gnu@16.0.7': + resolution: {integrity: sha512-mloD5WcPIeIeeZqAIP5c2kdaTa6StwP4/2EGy1mUw8HiexSHGK/jcM7lFuS3u3i2zn+xH9+wXJs6njO7VrAqww==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@16.0.3': - resolution: {integrity: sha512-CEErFt78S/zYXzFIiv18iQCbRbLgBluS8z1TNDQoyPi8/Jr5qhR3e8XHAIxVxPBjDbEMITprqELVc5KTfFj0gg==} + '@next/swc-linux-arm64-musl@16.0.7': + resolution: {integrity: sha512-+ksWNrZrthisXuo9gd1XnjHRowCbMtl/YgMpbRvFeDEqEBd523YHPWpBuDjomod88U8Xliw5DHhekBC3EOOd9g==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@16.0.3': - resolution: {integrity: sha512-Tc3i+nwt6mQ+Dwzcri/WNDj56iWdycGVh5YwwklleClzPzz7UpfaMw1ci7bLl6GRYMXhWDBfe707EXNjKtiswQ==} + '@next/swc-linux-x64-gnu@16.0.7': + resolution: {integrity: sha512-4WtJU5cRDxpEE44Ana2Xro1284hnyVpBb62lIpU5k85D8xXxatT+rXxBgPkc7C1XwkZMWpK5rXLXTh9PFipWsA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@16.0.3': - resolution: {integrity: sha512-zTh03Z/5PBBPdTurgEtr6nY0vI9KR9Ifp/jZCcHlODzwVOEKcKRBtQIGrkc7izFgOMuXDEJBmirwpGqdM/ZixA==} + '@next/swc-linux-x64-musl@16.0.7': + resolution: {integrity: sha512-HYlhqIP6kBPXalW2dbMTSuB4+8fe+j9juyxwfMwCe9kQPPeiyFn7NMjNfoFOfJ2eXkeQsoUGXg+O2SE3m4Qg2w==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@16.0.3': - resolution: {integrity: sha512-Jc1EHxtZovcJcg5zU43X3tuqzl/sS+CmLgjRP28ZT4vk869Ncm2NoF8qSTaL99gh6uOzgM99Shct06pSO6kA6g==} + '@next/swc-win32-arm64-msvc@16.0.7': + resolution: {integrity: sha512-EviG+43iOoBRZg9deGauXExjRphhuYmIOJ12b9sAPy0eQ6iwcPxfED2asb/s2/yiLYOdm37kPaiZu8uXSYPs0Q==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@16.0.3': - resolution: {integrity: sha512-N7EJ6zbxgIYpI/sWNzpVKRMbfEGgsWuOIvzkML7wxAAZhPk1Msxuo/JDu1PKjWGrAoOLaZcIX5s+/pF5LIbBBg==} + '@next/swc-win32-x64-msvc@16.0.7': + resolution: {integrity: sha512-gniPjy55zp5Eg0896qSrf3yB1dw4F/3s8VK1ephdsZZ129j2n6e1WqCbE2YgcKhW9hPB9TVZENugquWJD5x0ug==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -2729,8 +2729,8 @@ packages: peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react: 18.3.1 + react-dom: 18.3.1 peerDependenciesMeta: '@types/react': optional: true @@ -6858,8 +6858,8 @@ packages: react: ^16.8 || ^17 || ^18 react-dom: ^16.8 || ^17 || ^18 - next@16.0.3: - resolution: {integrity: sha512-Ka0/iNBblPFcIubTA1Jjh6gvwqfjrGq1Y2MTI5lbjeLIAfmC+p5bQmojpRZqgHHVu5cG4+qdIiwXiBSm/8lZ3w==} + next@16.0.7: + resolution: {integrity: sha512-3mBRJyPxT4LOxAJI6IsXeFtKfiJUbjCLgvXO02fV8Wy/lIhPvP94Fe7dGhUgHXcQy4sSuYwQNcOLhIfOm0rL0A==} engines: {node: '>=20.9.0'} hasBin: true peerDependencies: @@ -9814,7 +9814,7 @@ snapshots: dependencies: mime: 3.0.0 - '@cloudflare/next-on-pages@1.13.16(next@16.0.3(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(wrangler@4.47.0)': + '@cloudflare/next-on-pages@1.13.16(next@16.0.7(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(wrangler@4.47.0)': dependencies: acorn: 8.15.0 ast-types: 0.14.2 @@ -9825,7 +9825,7 @@ snapshots: esbuild: 0.15.18 js-yaml: 4.1.0 miniflare: 3.20250718.2 - next: 16.0.3(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 16.0.7(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) package-manager-manager: 0.2.0 pcre-to-regexp: 1.1.0 semver: 7.7.3 @@ -10752,42 +10752,42 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true - '@next/bundle-analyzer@16.0.3': + '@next/bundle-analyzer@16.0.7': dependencies: webpack-bundle-analyzer: 4.10.1 transitivePeerDependencies: - bufferutil - utf-8-validate - '@next/env@16.0.3': {} + '@next/env@16.0.7': {} - '@next/swc-darwin-arm64@16.0.3': + '@next/swc-darwin-arm64@16.0.7': optional: true - '@next/swc-darwin-x64@16.0.3': + '@next/swc-darwin-x64@16.0.7': optional: true - '@next/swc-linux-arm64-gnu@16.0.3': + '@next/swc-linux-arm64-gnu@16.0.7': optional: true - '@next/swc-linux-arm64-musl@16.0.3': + '@next/swc-linux-arm64-musl@16.0.7': optional: true - '@next/swc-linux-x64-gnu@16.0.3': + '@next/swc-linux-x64-gnu@16.0.7': optional: true - '@next/swc-linux-x64-musl@16.0.3': + '@next/swc-linux-x64-musl@16.0.7': optional: true - '@next/swc-win32-arm64-msvc@16.0.3': + '@next/swc-win32-arm64-msvc@16.0.7': optional: true - '@next/swc-win32-x64-msvc@16.0.3': + '@next/swc-win32-x64-msvc@16.0.7': optional: true - '@next/third-parties@15.4.3(next@16.0.3(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': + '@next/third-parties@15.4.3(next@16.0.7(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': dependencies: - next: 16.0.3(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 16.0.7(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 third-party-capital: 1.0.20 @@ -11873,7 +11873,7 @@ snapshots: '@speed-highlight/core@1.2.12': {} - '@splinetool/react-spline@4.0.0(@splinetool/runtime@1.10.14)(next@16.0.3(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@splinetool/react-spline@4.0.0(@splinetool/runtime@1.10.14)(next@16.0.7(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@splinetool/runtime': 1.10.14 blurhash: 2.0.5 @@ -11883,7 +11883,7 @@ snapshots: react-merge-refs: 2.1.1 thumbhash: 0.1.1 optionalDependencies: - next: 16.0.3(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 16.0.7(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@splinetool/runtime@1.10.14': dependencies: @@ -15852,13 +15852,13 @@ snapshots: netmask@2.0.2: {} - next-auth@4.24.11(next@16.0.3(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next-auth@4.24.11(next@16.0.7(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.27.6 '@panva/hkdf': 1.2.1 cookie: 0.7.2 jose: 4.15.9 - next: 16.0.3(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 16.0.7(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) oauth: 0.9.15 openid-client: 5.7.1 preact: 10.26.9 @@ -15885,9 +15885,9 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - next@16.0.3(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next@16.0.7(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@next/env': 16.0.3 + '@next/env': 16.0.7 '@swc/helpers': 0.5.15 caniuse-lite: 1.0.30001756 postcss: 8.4.31 @@ -15895,14 +15895,14 @@ snapshots: react-dom: 18.3.1(react@18.3.1) styled-jsx: 5.1.6(@babel/core@7.27.4)(react@18.3.1) optionalDependencies: - '@next/swc-darwin-arm64': 16.0.3 - '@next/swc-darwin-x64': 16.0.3 - '@next/swc-linux-arm64-gnu': 16.0.3 - '@next/swc-linux-arm64-musl': 16.0.3 - '@next/swc-linux-x64-gnu': 16.0.3 - '@next/swc-linux-x64-musl': 16.0.3 - '@next/swc-win32-arm64-msvc': 16.0.3 - '@next/swc-win32-x64-msvc': 16.0.3 + '@next/swc-darwin-arm64': 16.0.7 + '@next/swc-darwin-x64': 16.0.7 + '@next/swc-linux-arm64-gnu': 16.0.7 + '@next/swc-linux-arm64-musl': 16.0.7 + '@next/swc-linux-x64-gnu': 16.0.7 + '@next/swc-linux-x64-musl': 16.0.7 + '@next/swc-win32-arm64-msvc': 16.0.7 + '@next/swc-win32-x64-msvc': 16.0.7 sharp: 0.34.5 transitivePeerDependencies: - '@babel/core' diff --git a/src/app/features/[slug]/page.tsx b/src/app/features/[slug]/page.tsx index 82ace1ba..1ae7cd2d 100644 --- a/src/app/features/[slug]/page.tsx +++ b/src/app/features/[slug]/page.tsx @@ -6,15 +6,16 @@ import { SchemaInjector, buildServiceJsonLd } from "@/utils/seo/schema"; import type { Metadata } from "next"; import { notFound } from "next/navigation"; -// Next.js 15+ Dynamic Route Compatibility Workaround -// Use Promise type for params in generateMetadata; use type assertion inside the page function. -// This prevents type errors in production builds due to Next.js 15+ breaking changes. +// Next.js 15+ Dynamic Route Compatibility +// Params are now Promises and must be awaited in both generateMetadata and page components. + +interface ServicePageProps { + params: Promise<{ slug: string }>; +} export async function generateMetadata({ params, -}: { - params: Promise<{ slug: string }>; -}): Promise { +}: ServicePageProps): Promise { const { slug } = await params; const allServices: ServiceItemData[] = Object.values(allServicesRaw).flatMap( (category) => Object.values(category), @@ -22,13 +23,13 @@ export async function generateMetadata({ return getSeoMetadataForService(slug, allServices); } -export default async function ServicePage(props: unknown) { - const { params } = props as { params: { slug: string } }; +export default async function ServicePage({ params }: ServicePageProps) { + const { slug } = await params; const allServices: ServiceItemData[] = Object.values(allServicesRaw).flatMap( (category) => Object.values(category), ); const service = - allServices.find((s) => s.slugDetails.slug === params.slug) || null; + allServices.find((s) => s.slugDetails.slug === slug) || null; if (!service) return notFound(); const serviceSchema = buildServiceJsonLd(service); diff --git a/src/components/dynamic-hero/src/components/PersonaCTA.tsx b/src/components/dynamic-hero/src/components/PersonaCTA.tsx index 72d019a5..2c3d18a6 100644 --- a/src/components/dynamic-hero/src/components/PersonaCTA.tsx +++ b/src/components/dynamic-hero/src/components/PersonaCTA.tsx @@ -1,7 +1,7 @@ "use client"; import { AnimatePresence, motion } from "motion/react"; -import type { FC, ReactNode } from "react"; +import type { FC, MouseEvent, ReactNode } from "react"; import { useCallback, useMemo } from "react"; import { z } from "zod"; @@ -77,51 +77,97 @@ const PersonaCTA: FC = ({ const elements: ReactNode[] = []; let lastIndex = 0; const linkRegex = /(.*?)<\/link>/gi; + const brRegex = //gi; + const allMatches: Array<{ + type: "link" | "br"; + index: number; + match: RegExpExecArray; + }> = []; + + // Collect all link matches let match: RegExpExecArray | null = linkRegex.exec(copy); + while (match !== null) { + allMatches.push({ type: "link", index: match.index, match }); + match = linkRegex.exec(copy); + } + // Collect all br matches + brRegex.lastIndex = 0; + match = brRegex.exec(copy); while (match !== null) { - const [fullMatch, href, text] = match; - if (match.index > lastIndex) { - elements.push(copy.slice(lastIndex, match.index)); + allMatches.push({ type: "br", index: match.index, match }); + match = brRegex.exec(copy); + } + + // Sort matches by index + allMatches.sort((a, b) => a.index - b.index); + + // Process matches in order + for (const item of allMatches) { + const { type, index, match: currentMatch } = item; + if (index > lastIndex) { + elements.push(copy.slice(lastIndex, index)); } - elements.push( - - ) => { + e.preventDefault(); + const targetId = href.slice(1); + const targetElement = document.getElementById(targetId); + if (targetElement) { + targetElement.scrollIntoView({ + behavior: "smooth", + block: "center", + }); + } + } + : undefined; + elements.push( + - {text} - - - , - ); - lastIndex = match.index + fullMatch.length; - match = linkRegex.exec(copy); + + {text} + + + , + ); + lastIndex = index + currentMatch[0].length; + } else if (type === "br") { + elements.push(
); + lastIndex = index + currentMatch[0].length; + } } + if (lastIndex < copy.length) { elements.push(copy.slice(lastIndex)); } diff --git a/src/components/home/ReactivateCampaignBadges.tsx b/src/components/home/ReactivateCampaignBadges.tsx index ae64b1a8..8efae62b 100644 --- a/src/components/home/ReactivateCampaignBadges.tsx +++ b/src/components/home/ReactivateCampaignBadges.tsx @@ -1,8 +1,8 @@ "use client"; -import { Badge } from "@/components/ui/badge"; +import { motion, type Variants } from "framer-motion"; + import { cn } from "@/lib/utils"; -import { type Variants, motion } from "framer-motion"; export interface BadgeMetrics { dollarAmount: number; @@ -55,7 +55,7 @@ export function ReactivateCampaignBadges({ }, { id: "hobby", - label: `+${metrics.hobbyTimeHours} hours hobby time by the week`, + label: `${metrics.hobbyTimeHours}+ hobby time this week`, color: "bg-orange-100 text-orange-800 border-orange-200 dark:bg-orange-900 dark:text-orange-100 dark:border-orange-700", }, diff --git a/src/components/home/heros/live-dynamic-hero-demo/__tests__/page.test.tsx b/src/components/home/heros/live-dynamic-hero-demo/__tests__/page.test.tsx index 70e6d0b6..c7b62afa 100644 --- a/src/components/home/heros/live-dynamic-hero-demo/__tests__/page.test.tsx +++ b/src/components/home/heros/live-dynamic-hero-demo/__tests__/page.test.tsx @@ -1,9 +1,8 @@ import type { HeroVideoConfig } from "@external/dynamic-hero"; import { fireEvent, render, screen, waitFor } from "@testing-library/react"; -import React from "react"; import type { ReactNode } from "react"; -import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import type { Mock } from "vitest"; +import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import "@testing-library/jest-dom"; const startTrialMock = vi.fn(() => Promise.resolve()); @@ -40,7 +39,7 @@ vi.mock("../../live-dynamic-hero-demo/LiveDynamicHeroClient", () => { }, "See How It Works", ), - React.createElement("p", {}, "Review the rollout steps"), + React.createElement("p", {}, "See AI In Action"), ), ); }, @@ -136,9 +135,7 @@ vi.mock("motion/react", () => ({ useInView: () => true, })); -let LiveDynamicHeroDemoPage: typeof import( - "../../live-dynamic-hero-demo/page", -).default; +let LiveDynamicHeroDemoPage: typeof import("../../live-dynamic-hero-demo/page").default; beforeAll(async () => { Object.defineProperty(window.HTMLElement.prototype, "scrollIntoView", { @@ -211,7 +208,7 @@ describe("LiveDynamicHeroDemoPage", () => { // Wait for hydrated version to render - findByText already asserts element exists await screen.findByText("Get Started in 1 Click", {}, { timeout: 3000 }); - await screen.findByText(/Review the rollout steps/i, {}, { timeout: 3000 }); + await screen.findByText(/See AI In Action/i, {}, { timeout: 3000 }); }); it("triggers Stripe trial checkout when primary CTA is clicked", async () => { diff --git a/src/components/home/heros/live-dynamic-hero-demo/_config.ts b/src/components/home/heros/live-dynamic-hero-demo/_config.ts index 925c0f02..1eb86264 100644 --- a/src/components/home/heros/live-dynamic-hero-demo/_config.ts +++ b/src/components/home/heros/live-dynamic-hero-demo/_config.ts @@ -222,7 +222,7 @@ export const LIVE_SECONDARY_CTA = { }; export const LIVE_MICROCOPY = - 'Stop dialing. Start closing. Your AI coworker books qualified sellers while you sleep. Review the rollout steps. "People buy from people who make them feel capable and confident." - Dale Carnegie.'; + 'See AI In Action
"People buy from people who make them feel capable and confident." - Dale Carnegie.'; export const LIVE_SOCIAL_PROOF = { ...DEFAULT_HERO_SOCIAL_PROOF,