diff --git a/package-lock.json b/package-lock.json index b2bbfdc..15e8fa4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,62 @@ "name": "@pacto-connect/docs", "version": "0.0.0" }, + "node_modules/@adobe/css-tools": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.5.0.tgz", + "integrity": "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/runtime": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", @@ -442,6 +498,121 @@ "prettier": "^2.7.1" } }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.28.1", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", @@ -1529,6 +1700,96 @@ "devOptional": true, "license": "MIT" }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, "node_modules/@turbo/darwin-64": { "version": "2.9.18", "resolved": "https://registry.npmjs.org/@turbo/darwin-64/-/darwin-64-2.9.18.tgz", @@ -1613,6 +1874,14 @@ "win32" ] }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/@types/chai": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", @@ -1660,6 +1929,16 @@ "csstype": "^3.2.2" } }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, "node_modules/@vitest/expect": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.6.tgz", @@ -1788,6 +2067,16 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -1808,6 +2097,20 @@ "node": ">=8" } }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", @@ -1822,6 +2125,16 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -1842,6 +2155,13 @@ "node": ">=12" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/better-path-resolve": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/better-path-resolve/-/better-path-resolve-1.0.0.tgz", @@ -1942,6 +2262,20 @@ "node": ">=8" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/chai": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", @@ -2002,6 +2336,19 @@ "consola": "^3.2.3" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -2044,6 +2391,34 @@ "node": ">= 8" } }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cssstyle/node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -2051,6 +2426,20 @@ "dev": true, "license": "MIT" }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -2069,6 +2458,13 @@ } } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, "node_modules/deep-eql": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", @@ -2096,6 +2492,26 @@ "devOptional": true, "license": "MIT" }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/destr": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", @@ -2126,6 +2542,14 @@ "node": ">=8" } }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/dotenv": { "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", @@ -2139,6 +2563,21 @@ "url": "https://dotenvx.com" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/effect": { "version": "3.21.0", "resolved": "https://registry.npmjs.org/effect/-/effect-3.21.0.tgz", @@ -2174,13 +2613,75 @@ "node": ">=8.6" } }, - "node_modules/es-module-lexer": { + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", "dev": true, "license": "MIT" }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.28.1", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", @@ -2360,6 +2861,23 @@ "rollup": "^4.34.8" } }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fs-extra": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", @@ -2390,6 +2908,55 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/giget": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", @@ -2442,6 +3009,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -2449,6 +3029,48 @@ "dev": true, "license": "ISC" }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/hono": { "version": "4.12.25", "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.25.tgz", @@ -2458,6 +3080,47 @@ "node": ">=16.9.0" } }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/human-id": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/human-id/-/human-id-4.2.0.tgz", @@ -2495,6 +3158,16 @@ "node": ">= 4" } }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2528,6 +3201,13 @@ "node": ">=0.12.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/is-subdir": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-subdir/-/is-subdir-1.2.0.tgz", @@ -2608,6 +3288,47 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "25.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz", + "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.1.0", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.12", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.7.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, "node_modules/jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -2675,6 +3396,24 @@ "dev": true, "license": "MIT" }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -2685,6 +3424,16 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -2709,6 +3458,39 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/mlly": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", @@ -2777,6 +3559,13 @@ "devOptional": true, "license": "MIT" }, + "node_modules/nwsapi": { + "version": "2.2.24", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.24.tgz", + "integrity": "sha512-7YRhZ3jS45LwmSCT4b2sVFHt/WuovaktDU07QrtOBY2PXskss5a9jfmR9jptyumwXST+rFjrmppMY1KT/yn35A==", + "dev": true, + "license": "MIT" + }, "node_modules/nypm": { "version": "0.6.7", "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.7.tgz", @@ -2908,6 +3697,19 @@ "quansync": "^0.2.7" } }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3102,6 +3904,22 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, "node_modules/prisma": { "version": "6.19.3", "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.19.3.tgz", @@ -3128,6 +3946,16 @@ } } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/pure-rand": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", @@ -3198,6 +4026,7 @@ "version": "19.2.7", "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -3207,8 +4036,8 @@ "version": "19.2.7", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -3216,6 +4045,14 @@ "react": "^19.2.7" } }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/read-yaml-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-yaml-file/-/read-yaml-file-1.1.0.tgz", @@ -3270,6 +4107,20 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -3336,6 +4187,13 @@ "fsevents": "~2.3.2" } }, + "node_modules/rrweb-cssom": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", + "dev": true, + "license": "MIT" + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -3367,12 +4225,25 @@ "dev": true, "license": "MIT" }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "license": "MIT", - "peer": true + "dev": true, + "license": "MIT" }, "node_modules/semver": { "version": "7.8.4", @@ -3515,6 +4386,19 @@ "node": ">=4" } }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-literal": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", @@ -3551,6 +4435,13 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, "node_modules/term-size": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", @@ -3679,6 +4570,26 @@ "node": ">=14.0.0" } }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -3692,6 +4603,32 @@ "node": ">=8.0" } }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -4054,6 +4991,80 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -4087,6 +5098,45 @@ "node": ">=8" } }, + "node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, "packages/connect-core": { "name": "@pacto-connect/core", "version": "0.0.0", @@ -4116,8 +5166,14 @@ "@pacto-connect/core": "*" }, "devDependencies": { + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.1.0", + "@testing-library/user-event": "^14.5.2", "@types/react": "^19.0.1", + "@types/react-dom": "^19.0.2", + "jsdom": "^25.0.1", "react": "^19.0.0", + "react-dom": "^19.0.0", "tsup": "^8.3.5", "typescript": "^5.6.3" }, diff --git a/packages/connect-core/src/index.ts b/packages/connect-core/src/index.ts index bf13dd7..1f3b4da 100644 --- a/packages/connect-core/src/index.ts +++ b/packages/connect-core/src/index.ts @@ -38,9 +38,12 @@ export { export type { CreateEscrowParams, CreateQuoteParams, + DepositParams, Escrow, EscrowStatus, EscrowStatusResponse, + FiatPaymentMethod, + FiatReceiptParams, Listing, PactoApiClient, Quote, diff --git a/packages/connect-core/src/resources.ts b/packages/connect-core/src/resources.ts index a25f7f2..cc7dbeb 100644 --- a/packages/connect-core/src/resources.ts +++ b/packages/connect-core/src/resources.ts @@ -51,6 +51,20 @@ export interface CreateEscrowParams { quoteId: string; } +export interface DepositParams { + /** When true, simulates on-chain deposit in Gateway test mode. */ + testMode?: boolean; +} + +export type FiatPaymentMethod = 'SINPE' | 'SPEI'; + +export interface FiatReceiptParams { + method: FiatPaymentMethod; + reference: string; + /** Base64-encoded receipt image or document. */ + receipt?: string; +} + export interface ListingsResource { list(): Promise<{ listings: Listing[] }>; retrieve(id: string): Promise<{ listing: Listing }>; @@ -65,6 +79,8 @@ export interface EscrowsResource { create(params: CreateEscrowParams): Promise<{ escrow: Escrow }>; retrieve(id: string): Promise<{ escrow: Escrow }>; getStatus(id: string): Promise<{ status: EscrowStatusResponse }>; + deposit(id: string, params?: DepositParams): Promise<{ escrow: Escrow }>; + reportFiatPayment(id: string, params: FiatReceiptParams): Promise<{ escrow: Escrow }>; } export interface PactoApiClient { @@ -122,6 +138,22 @@ export function createApiClient(options: HttpClientOptions): PactoApiClient { path: `/v1/escrows/${id}/status`, resource: 'escrow', }), + deposit: (id, params = {}) => + request<{ escrow: Escrow }>(options, { + method: 'POST', + path: `/v1/escrows/${id}/deposit`, + body: params, + idempotent: true, + resource: 'escrow', + }), + reportFiatPayment: (id, params) => + request<{ escrow: Escrow }>(options, { + method: 'POST', + path: `/v1/escrows/${id}/fiat-receipt`, + body: params, + idempotent: true, + resource: 'escrow', + }), }, }; } diff --git a/packages/connect-react/package.json b/packages/connect-react/package.json index abb18df..c47bac2 100644 --- a/packages/connect-react/package.json +++ b/packages/connect-react/package.json @@ -33,8 +33,14 @@ "@pacto-connect/core": "*" }, "devDependencies": { + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.1.0", + "@testing-library/user-event": "^14.5.2", "@types/react": "^19.0.1", + "@types/react-dom": "^19.0.2", + "jsdom": "^25.0.1", "react": "^19.0.0", + "react-dom": "^19.0.0", "tsup": "^8.3.5", "typescript": "^5.6.3" } diff --git a/packages/connect-react/src/PactoCheckout.test.tsx b/packages/connect-react/src/PactoCheckout.test.tsx new file mode 100644 index 0000000..b423b29 --- /dev/null +++ b/packages/connect-react/src/PactoCheckout.test.tsx @@ -0,0 +1,371 @@ +import { cleanup, render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { PactoCheckout } from './PactoCheckout'; + +const gatewayUrl = 'https://gateway.example'; +const publishableKey = 'pk_test_123'; +const listingId = 'lst_1'; + +const listing = { + id: listingId, + asset: 'USDC', + amount: '100', + price: '5000', + side: 'buy' as const, + status: 'active', + createdAt: '2024-01-01T00:00:00.000Z', +}; + +const quote = { + id: 'quo_1', + listingId, + asset: 'USDC', + amount: '100', + price: '5000', + side: 'buy' as const, + expiresAt: '2024-01-02T00:00:00.000Z', + createdAt: '2024-01-01T00:00:00.000Z', +}; + +const escrow = { + id: 'esc_1', + quoteId: quote.id, + status: 'pending' as const, + amount: '100', + asset: 'USDC', + createdAt: '2024-01-01T00:00:00.000Z', + updatedAt: '2024-01-01T00:00:00.000Z', +}; + +function encodeSse(block: string): Uint8Array { + return new TextEncoder().encode(block); +} + +function jsonResponse(body: unknown, status = 200): Response { + return { + ok: status >= 200 && status < 300, + status, + headers: new Headers(), + json: async () => body, + } as Response; +} + +function sseResponse(events: string[]): Response { + const stream = new ReadableStream({ + start(controller) { + for (const event of events) { + controller.enqueue(encodeSse(event)); + } + controller.close(); + }, + }); + + return { + ok: true, + status: 200, + body: stream, + headers: new Headers(), + } as Response; +} + +function createDeferredSseResponse() { + let controller: ReadableStreamDefaultController; + + const stream = new ReadableStream({ + start(streamController) { + controller = streamController; + }, + }); + + return { + response: () => + ({ + ok: true, + status: 200, + body: stream, + headers: new Headers(), + }) as Response, + push: (block: string) => controller.enqueue(encodeSse(block)), + close: () => { + try { + controller.close(); + } catch { + // Stream may already be closed. + } + }, + }; +} + +function createFetchMock(sse?: ReturnType) { + return vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => { + const url = String(input); + const method = init?.method ?? 'GET'; + + if (url.includes('/v1/session') && method === 'POST') { + return jsonResponse({ + sessionId: 'sess_1', + clientSecret: 'cs_sess_1.sig', + expiresAt: '2099-01-01T00:00:00.000Z', + mode: 'buy', + }); + } + + if (url.includes(`/v1/listings/${listingId}`)) { + return jsonResponse({ listing }); + } + + if (url.endsWith('/v1/listings')) { + return jsonResponse({ listings: [listing] }); + } + + if (url.endsWith('/v1/quotes') && method === 'POST') { + return jsonResponse({ quote }); + } + + if (url.endsWith('/v1/escrows') && method === 'POST') { + return jsonResponse({ escrow }); + } + + if (url.includes('/deposit') && method === 'POST') { + return jsonResponse({ escrow: { ...escrow, status: 'funded' } }); + } + + if (url.includes('/fiat-receipt') && method === 'POST') { + return jsonResponse({ escrow: { ...escrow, status: 'active' } }); + } + + if (url.includes('/v1/escrows/events')) { + if (sse) { + return sse.response(); + } + return sseResponse([]); + } + + return jsonResponse({ error: 'not found' }, 404); + }); +} + +describe('PactoCheckout', () => { + let defaultSse: ReturnType; + + beforeEach(() => { + defaultSse = createDeferredSseResponse(); + vi.stubGlobal('fetch', createFetchMock(defaultSse)); + }); + + afterEach(() => { + defaultSse.close(); + cleanup(); + vi.unstubAllGlobals(); + }); + + it('completes buy flow end-to-end and calls onComplete', async () => { + const onComplete = vi.fn(); + const user = userEvent.setup(); + const sse = createDeferredSseResponse(); + + vi.stubGlobal('fetch', createFetchMock(sse)); + + render( + {}} + onComplete={onComplete} + testMode + />, + ); + + await waitFor(() => { + expect(screen.getByTestId('deposit-step')).toBeInTheDocument(); + }); + + await user.click(screen.getByRole('button', { name: 'Confirm deposit' })); + + await waitFor(() => { + expect(screen.getByTestId('receipt-form')).toBeInTheDocument(); + }); + + await user.type(screen.getByLabelText('Payment reference'), 'REF-123'); + await user.click(screen.getByRole('button', { name: 'Submit receipt' })); + + await waitFor(() => { + expect(screen.getByTestId('tracking-step')).toBeInTheDocument(); + }); + + sse.push( + 'id: cursor-1\nevent: released\ndata: {"escrowId":"esc_1","occurredAt":"2024-01-01T00:10:00.000Z"}\n\n', + ); + sse.close(); + + await waitFor(() => { + expect(onComplete).toHaveBeenCalledWith(expect.objectContaining({ id: 'esc_1' })); + }); + + expect(screen.getByTestId('checkout-success')).toBeInTheDocument(); + }); + + it('calls onDispute when escrow is disputed', async () => { + const onDispute = vi.fn(); + const user = userEvent.setup(); + const sse = createDeferredSseResponse(); + + vi.stubGlobal('fetch', createFetchMock(sse)); + + render( + {}} + onDispute={onDispute} + testMode + />, + ); + + await waitFor(() => { + expect(screen.getByTestId('deposit-step')).toBeInTheDocument(); + }); + + await user.click(screen.getByRole('button', { name: 'Confirm deposit' })); + await waitFor(() => { + expect(screen.getByTestId('receipt-form')).toBeInTheDocument(); + }); + + await user.type(screen.getByLabelText('Payment reference'), 'REF-456'); + await user.click(screen.getByRole('button', { name: 'Submit receipt' })); + + await waitFor(() => { + expect(screen.getByTestId('tracking-step')).toBeInTheDocument(); + }); + + sse.push( + 'id: cursor-1\nevent: disputed\ndata: {"escrowId":"esc_1","occurredAt":"2024-01-01T00:10:00.000Z"}\n\n', + ); + sse.close(); + + await waitFor(() => { + expect(onDispute).toHaveBeenCalledWith(expect.objectContaining({ id: 'esc_1' })); + }); + + expect(screen.getByTestId('checkout-disputed')).toBeInTheDocument(); + }); + + it('shows error state when session creation fails', async () => { + vi.stubGlobal( + 'fetch', + vi.fn(async () => jsonResponse({ error: 'invalid_key', message: 'Bad key' }, 401)), + ); + + const onError = vi.fn(); + + render( + {}} + onError={onError} + />, + ); + + await waitFor(() => { + expect(screen.getByTestId('checkout-error')).toBeInTheDocument(); + }); + + expect(onError).toHaveBeenCalled(); + }); + + it('lists listings when no listingId is provided', async () => { + const user = userEvent.setup(); + + render( + {}} + testMode + />, + ); + + await waitFor(() => { + expect(screen.getByTestId('listing-list')).toBeInTheDocument(); + }); + + await user.click(screen.getByRole('button', { name: /USDC/ })); + + await waitFor(() => { + expect(screen.getByTestId('deposit-step')).toBeInTheDocument(); + }); + }); + + describe('accessibility', () => { + it('renders dialog with ARIA attributes', async () => { + render( + {}} + />, + ); + + const dialog = await screen.findByTestId('pacto-checkout-dialog'); + expect(dialog).toHaveAttribute('role', 'dialog'); + expect(dialog).toHaveAttribute('aria-modal', 'true'); + expect(dialog).toHaveAttribute('aria-labelledby', 'pacto-checkout-title'); + }); + + it('closes on Escape key', async () => { + const onClose = vi.fn(); + const user = userEvent.setup(); + + render( + , + ); + + await screen.findByTestId('pacto-checkout-dialog'); + await user.keyboard('{Escape}'); + + expect(onClose).toHaveBeenCalled(); + }); + + it('traps focus within the dialog on Tab', async () => { + render( + {}} + />, + ); + + const dialog = await screen.findByTestId('pacto-checkout-dialog'); + const closeButton = screen.getByRole('button', { name: 'Close checkout' }); + const confirmButton = await screen.findByRole('button', { name: 'Confirm deposit' }); + + expect(closeButton).toHaveFocus(); + + await userEvent.tab(); + expect(confirmButton).toHaveFocus(); + + await userEvent.tab(); + expect(closeButton).toHaveFocus(); + expect(dialog.contains(document.activeElement)).toBe(true); + }); + }); +}); diff --git a/packages/connect-react/src/PactoCheckout.tsx b/packages/connect-react/src/PactoCheckout.tsx new file mode 100644 index 0000000..a4254df --- /dev/null +++ b/packages/connect-react/src/PactoCheckout.tsx @@ -0,0 +1,193 @@ +import type { EscrowEvent, FiatPaymentMethod } from '@pacto-connect/core'; +import { useRef, useState } from 'react'; +import { type CheckoutStep, useCheckoutFlow } from './hooks/useCheckoutFlow.js'; +import { useFocusTrap } from './hooks/useFocusTrap.js'; + +export interface PactoCheckoutProps { + publishableKey: string; + gatewayUrl?: string; + listingId?: string; + mode?: 'buy' | 'sell'; + testMode?: boolean; + open: boolean; + onClose: () => void; + onComplete?: (escrow: import('@pacto-connect/core').Escrow) => void; + onDispute?: (escrow: import('@pacto-connect/core').Escrow) => void; + onError?: (error: Error) => void; +} + +function stepLabel(step: CheckoutStep): string { + switch (step) { + case 'selectListing': + return 'Select a listing'; + case 'deposit': + return 'Deposit to escrow'; + case 'uploadReceipt': + return 'Upload payment receipt'; + case 'tracking': + return 'Tracking escrow status'; + case 'success': + return 'Payment complete'; + case 'disputed': + return 'Escrow disputed'; + case 'error': + return 'Checkout error'; + default: + return 'Processing checkout'; + } +} + +function milestoneLabel(type: EscrowEvent['type']): string { + switch (type) { + case 'escrow.funded': + return 'Escrow funded'; + case 'fiat.reported': + return 'Fiat payment reported'; + case 'released': + return 'Funds released'; + case 'disputed': + return 'Escrow disputed'; + } +} + +export function PactoCheckout(props: PactoCheckoutProps) { + const dialogRef = useRef(null); + const flow = useCheckoutFlow({ + publishableKey: props.publishableKey, + gatewayUrl: props.gatewayUrl, + listingId: props.listingId, + mode: props.mode, + testMode: props.testMode, + enabled: props.open, + onComplete: props.onComplete, + onDispute: props.onDispute, + onError: props.onError, + }); + + const [method, setMethod] = useState('SINPE'); + const [reference, setReference] = useState(''); + + useFocusTrap(dialogRef, props.open, props.onClose); + + if (!props.open) { + return null; + } + + const titleId = 'pacto-checkout-title'; + + return ( +
+
+
+

{stepLabel(flow.step)}

+ +
+ + {flow.step === 'loading' && ( + + Loading… + + )} + + {flow.step === 'error' && ( +
+

{flow.error?.message ?? 'Something went wrong'}

+ +
+ )} + + {flow.step === 'selectListing' && ( +
    + {flow.listings.map((listing) => ( +
  • + +
  • + ))} +
+ )} + + {flow.step === 'deposit' && flow.escrow && ( +
+

+ Deposit {flow.escrow.amount} {flow.escrow.asset} to the escrow + contract. +

+ +
+ )} + + {flow.step === 'uploadReceipt' && ( +
{ + event.preventDefault(); + flow.submitReceipt(method, reference); + }} + > + + + +
+ )} + + {flow.step === 'tracking' && ( +
+

Waiting for escrow release…

+
    + {flow.milestones.map((milestone) => ( +
  1. {milestoneLabel(milestone.type)}
  2. + ))} +
+
+ )} + + {flow.step === 'success' && ( + + Payment complete. Escrow {flow.escrow?.id} released. + + )} + + {flow.step === 'disputed' && ( + + Escrow {flow.escrow?.id} has been disputed. + + )} +
+
+ ); +} diff --git a/packages/connect-react/src/hooks/useCheckoutFlow.ts b/packages/connect-react/src/hooks/useCheckoutFlow.ts new file mode 100644 index 0000000..2018013 --- /dev/null +++ b/packages/connect-react/src/hooks/useCheckoutFlow.ts @@ -0,0 +1,278 @@ +import { + type CheckoutMode, + type Escrow, + type EscrowEvent, + type Listing, + Pacto, + type PactoApiClient, + type PactoClient, + type PactoSession, + type Quote, +} from '@pacto-connect/core'; +import { useCallback, useEffect, useRef, useState } from 'react'; + +export type CheckoutStep = + | 'loading' + | 'selectListing' + | 'deposit' + | 'uploadReceipt' + | 'tracking' + | 'success' + | 'disputed' + | 'error'; + +export interface UseCheckoutFlowOptions { + publishableKey: string; + gatewayUrl?: string; + listingId?: string; + mode?: CheckoutMode; + testMode?: boolean; + enabled: boolean; + onComplete?: (escrow: Escrow) => void; + onDispute?: (escrow: Escrow) => void; + onError?: (error: Error) => void; +} + +export interface UseCheckoutFlowResult { + step: CheckoutStep; + listings: Listing[]; + selectedListing: Listing | null; + escrow: Escrow | null; + quote: Quote | null; + error: Error | null; + milestones: EscrowEvent[]; + selectListing: (listing: Listing) => Promise; + confirmDeposit: () => Promise; + submitReceipt: (method: 'SINPE' | 'SPEI', reference: string, receipt?: string) => Promise; + retry: () => void; +} + +export function useCheckoutFlow(options: UseCheckoutFlowOptions): UseCheckoutFlowResult { + const [step, setStep] = useState('loading'); + const [listings, setListings] = useState([]); + const [selectedListing, setSelectedListing] = useState(null); + const [escrow, setEscrow] = useState(null); + const [quote, setQuote] = useState(null); + const [error, setError] = useState(null); + const [milestones, setMilestones] = useState([]); + + const sessionRef = useRef(null); + const clientRef = useRef(null); + const apiRef = useRef(null); + const escrowRef = useRef(null); + const eventsBoundRef = useRef(false); + + const onCompleteRef = useRef(options.onComplete); + const onDisputeRef = useRef(options.onDispute); + const onErrorRef = useRef(options.onError); + + useEffect(() => { + onCompleteRef.current = options.onComplete; + onDisputeRef.current = options.onDispute; + onErrorRef.current = options.onError; + }); + + const handleError = useCallback((err: unknown) => { + const normalized = err instanceof Error ? err : new Error(String(err)); + setError(normalized); + setStep('error'); + onErrorRef.current?.(normalized); + }, []); + + const bindEscrowEvents = useCallback(() => { + const session = sessionRef.current; + const currentEscrow = escrowRef.current; + if (!session || !currentEscrow || eventsBoundRef.current) { + return; + } + + eventsBoundRef.current = true; + const escrowId = currentEscrow.id; + + const trackMilestone = (event: EscrowEvent) => { + setMilestones((prev) => [...prev, event]); + }; + + session.on( + 'released', + (event) => { + trackMilestone(event); + setStep('success'); + onCompleteRef.current?.(currentEscrow); + }, + { escrowId }, + ); + + session.on( + 'disputed', + (event) => { + trackMilestone(event); + setStep('disputed'); + onDisputeRef.current?.(currentEscrow); + }, + { escrowId }, + ); + + session.on('escrow.funded', trackMilestone, { escrowId }); + session.on('fiat.reported', trackMilestone, { escrowId }); + }, []); + + const createQuoteAndEscrow = useCallback(async (listing: Listing) => { + const api = apiRef.current; + if (!api) { + throw new Error('API client not initialized'); + } + + const quoteResponse = await api.quotes.create({ + listingId: listing.id, + asset: listing.asset, + amount: listing.amount, + price: listing.price, + side: 'buy', + }); + setQuote(quoteResponse.quote); + + const escrowResponse = await api.escrows.create({ quoteId: quoteResponse.quote.id }); + setEscrow(escrowResponse.escrow); + escrowRef.current = escrowResponse.escrow; + setStep('deposit'); + }, []); + + const initialize = useCallback(async () => { + setStep('loading'); + setError(null); + setMilestones([]); + eventsBoundRef.current = false; + + try { + const client = Pacto.init({ + publishableKey: options.publishableKey, + gatewayUrl: options.gatewayUrl, + }); + clientRef.current = client; + + const mode = options.mode ?? 'buy'; + const session = options.listingId + ? await client.createCheckoutSession({ listingId: options.listingId, mode }) + : await client.createCheckoutSession({ quote: { browse: true }, mode }); + + sessionRef.current = session; + const api = client.api(session); + apiRef.current = api; + + if (options.listingId) { + const listingResponse = await api.listings.retrieve(options.listingId); + setSelectedListing(listingResponse.listing); + await createQuoteAndEscrow(listingResponse.listing); + return; + } + + const listingsResponse = await api.listings.list(); + setListings(listingsResponse.listings); + setStep('selectListing'); + } catch (err) { + handleError(err); + } + }, [ + createQuoteAndEscrow, + handleError, + options.gatewayUrl, + options.listingId, + options.mode, + options.publishableKey, + ]); + + const selectListing = useCallback( + async (listing: Listing) => { + setSelectedListing(listing); + setStep('loading'); + try { + await createQuoteAndEscrow(listing); + } catch (err) { + handleError(err); + } + }, + [createQuoteAndEscrow, handleError], + ); + + const confirmDeposit = useCallback(async () => { + const api = apiRef.current; + const currentEscrow = escrowRef.current; + if (!api || !currentEscrow) { + return; + } + + setStep('loading'); + try { + const response = await api.escrows.deposit(currentEscrow.id, { + testMode: options.testMode ?? true, + }); + setEscrow(response.escrow); + escrowRef.current = response.escrow; + setStep('uploadReceipt'); + } catch (err) { + handleError(err); + } + }, [handleError, options.testMode]); + + const submitReceipt = useCallback( + async (method: 'SINPE' | 'SPEI', reference: string, receipt?: string) => { + const api = apiRef.current; + const currentEscrow = escrowRef.current; + if (!api || !currentEscrow) { + return; + } + + setStep('loading'); + try { + const response = await api.escrows.reportFiatPayment(currentEscrow.id, { + method, + reference, + receipt, + }); + setEscrow(response.escrow); + escrowRef.current = response.escrow; + bindEscrowEvents(); + setStep('tracking'); + } catch (err) { + handleError(err); + } + }, + [bindEscrowEvents, handleError], + ); + + const retry = useCallback(() => { + initialize(); + }, [initialize]); + + useEffect(() => { + if (!options.enabled) { + return; + } + + initialize(); + + return () => { + sessionRef.current?.closeEvents(); + sessionRef.current = null; + clientRef.current = null; + apiRef.current = null; + escrowRef.current = null; + eventsBoundRef.current = false; + }; + }, [initialize, options.enabled]); + + return { + step, + listings, + selectedListing, + escrow, + quote, + error, + milestones, + selectListing, + confirmDeposit, + submitReceipt, + retry, + }; +} diff --git a/packages/connect-react/src/hooks/useFocusTrap.ts b/packages/connect-react/src/hooks/useFocusTrap.ts new file mode 100644 index 0000000..d0173a5 --- /dev/null +++ b/packages/connect-react/src/hooks/useFocusTrap.ts @@ -0,0 +1,72 @@ +import { type RefObject, useEffect } from 'react'; + +const FOCUSABLE_SELECTOR = + 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])'; + +function getFocusableElements(container: HTMLElement): HTMLElement[] { + return Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)).filter( + (el) => !el.hasAttribute('disabled') && el.tabIndex !== -1, + ); +} + +export function useFocusTrap( + containerRef: RefObject, + active: boolean, + onEscape?: () => void, +): void { + useEffect(() => { + if (!active || !containerRef.current) { + return; + } + + const container = containerRef.current; + const previouslyFocused = document.activeElement as HTMLElement | null; + + const focusable = getFocusableElements(container); + const initialFocus = focusable.at(0); + if (initialFocus) { + initialFocus.focus(); + } else { + container.focus(); + } + + function handleKeyDown(event: KeyboardEvent): void { + if (event.key === 'Escape') { + event.preventDefault(); + onEscape?.(); + return; + } + + if (event.key !== 'Tab') { + return; + } + + const elements = getFocusableElements(container); + const first = elements.at(0); + const last = elements.at(-1); + if (!first || !last) { + event.preventDefault(); + return; + } + + const activeEl = document.activeElement as HTMLElement; + + if (event.shiftKey) { + if (activeEl === first || !container.contains(activeEl)) { + event.preventDefault(); + last.focus(); + } + } else if (activeEl === last) { + event.preventDefault(); + first.focus(); + } + } + + document.addEventListener('keydown', handleKeyDown); + + return () => { + document.removeEventListener('keydown', handleKeyDown); + previouslyFocused?.focus(); + }; + }, [active, containerRef, onEscape]); +} diff --git a/packages/connect-react/src/index.ts b/packages/connect-react/src/index.ts index 0e4ff3c..a5edc1b 100644 --- a/packages/connect-react/src/index.ts +++ b/packages/connect-react/src/index.ts @@ -1,14 +1,16 @@ /** * @pacto-connect/react * - * React bindings. Scaffolding only — the widget is issue #5. + * React bindings for Pacto Connect — embeddable checkout widget and hooks. */ export const VERSION = '0.0.0'; + export type { CheckoutMode, CreateCheckoutSessionParams, CreateEscrowParams, CreateQuoteParams, + DepositParams, Escrow, EscrowEvent, EscrowEventHandler, @@ -17,6 +19,8 @@ export type { EscrowStatus, EscrowStatusResponse, EscrowSubscribeOptions, + FiatPaymentMethod, + FiatReceiptParams, Listing, PactoApiClient, PactoClient, @@ -26,6 +30,7 @@ export type { } from '@pacto-connect/core'; export { ESCROW_EVENT_NAMES, + Pacto, PactoApiError, PactoAuthError, PactoError, @@ -34,3 +39,11 @@ export { PactoSession, PactoSessionError, } from '@pacto-connect/core'; +export type { + CheckoutStep, + UseCheckoutFlowOptions, + UseCheckoutFlowResult, +} from './hooks/useCheckoutFlow.js'; +export { useCheckoutFlow } from './hooks/useCheckoutFlow.js'; +export type { PactoCheckoutProps } from './PactoCheckout.js'; +export { PactoCheckout } from './PactoCheckout.js'; diff --git a/packages/connect-react/src/test/setup.ts b/packages/connect-react/src/test/setup.ts new file mode 100644 index 0000000..66668d9 --- /dev/null +++ b/packages/connect-react/src/test/setup.ts @@ -0,0 +1,5 @@ +import '@testing-library/jest-dom/vitest'; + +if (!globalThis.crypto.randomUUID) { + globalThis.crypto.randomUUID = () => 'test-uuid-0000-0000-0000-000000000000'; +} diff --git a/packages/connect-react/vitest.config.ts b/packages/connect-react/vitest.config.ts index c7f1a76..a21737e 100644 --- a/packages/connect-react/vitest.config.ts +++ b/packages/connect-react/vitest.config.ts @@ -2,7 +2,8 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { - environment: 'node', + environment: 'jsdom', + setupFiles: ['./src/test/setup.ts'], include: ['src/**/*.test.ts', 'src/**/*.test.tsx'], }, });