Compare commits
1 Commits
develop-ki
...
develop-ki
| Author | SHA1 | Date | |
|---|---|---|---|
| d0176fb48b |
273
bun.lock
273
bun.lock
@@ -5,37 +5,36 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "holistream",
|
"name": "holistream",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.983.0",
|
"@aws-sdk/client-s3": "^3.971.0",
|
||||||
"@aws-sdk/s3-presigned-post": "^3.983.0",
|
"@aws-sdk/s3-presigned-post": "^3.971.0",
|
||||||
"@aws-sdk/s3-request-presigner": "^3.983.0",
|
"@aws-sdk/s3-request-presigner": "^3.971.0",
|
||||||
|
"@hiogawa/tiny-rpc": "^0.2.3-pre.18",
|
||||||
"@hiogawa/utils": "^1.7.0",
|
"@hiogawa/utils": "^1.7.0",
|
||||||
"@pinia/colada": "^0.21.2",
|
|
||||||
"@tanstack/vue-form": "^1.28.0",
|
"@tanstack/vue-form": "^1.28.0",
|
||||||
"@tanstack/vue-table": "^8.21.3",
|
"@tanstack/vue-table": "^8.21.3",
|
||||||
"@tanstack/zod-form-adapter": "^0.42.1",
|
|
||||||
"@unhead/vue": "^2.1.2",
|
"@unhead/vue": "^2.1.2",
|
||||||
"@vueuse/core": "^14.2.0",
|
"@vueuse/core": "^14.1.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"hono": "^4.11.7",
|
"hono": "^4.11.4",
|
||||||
"is-mobile": "^5.0.0",
|
"is-mobile": "^5.0.0",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"vue": "^3.5.27",
|
"vue": "^3.5.27",
|
||||||
"vue-router": "^5.0.2",
|
"vue-router": "^5.0.2",
|
||||||
"zod": "^3.25.76",
|
"zod": "^4.3.5",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cloudflare/vite-plugin": "^1.23.0",
|
"@cloudflare/vite-plugin": "^1.21.0",
|
||||||
"@types/node": "^25.2.0",
|
"@primevue/auto-import-resolver": "^4.5.4",
|
||||||
"@vitejs/plugin-vue": "^6.0.4",
|
"@types/node": "^25.0.9",
|
||||||
"@vitejs/plugin-vue-jsx": "^5.1.4",
|
"@vitejs/plugin-vue": "^6.0.3",
|
||||||
|
"@vitejs/plugin-vue-jsx": "^5.1.3",
|
||||||
"unocss": "^66.6.0",
|
"unocss": "^66.6.0",
|
||||||
"unplugin-auto-import": "^21.0.0",
|
"unplugin-auto-import": "^21.0.0",
|
||||||
"unplugin-vue-components": "^31.0.0",
|
"unplugin-vue-components": "^31.0.0",
|
||||||
"vite": "^7.3.1",
|
"vite": "^7.3.1",
|
||||||
"vite-ssr-components": "^0.5.2",
|
"vite-ssr-components": "^0.5.2",
|
||||||
"wrangler": "^4.62.0",
|
"wrangler": "^4.59.2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -56,87 +55,87 @@
|
|||||||
|
|
||||||
"@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="],
|
"@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="],
|
||||||
|
|
||||||
"@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.983.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.6", "@aws-sdk/credential-provider-node": "^3.972.5", "@aws-sdk/middleware-bucket-endpoint": "^3.972.3", "@aws-sdk/middleware-expect-continue": "^3.972.3", "@aws-sdk/middleware-flexible-checksums": "^3.972.4", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-location-constraint": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-sdk-s3": "^3.972.6", "@aws-sdk/middleware-ssec": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.6", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/signature-v4-multi-region": "3.983.0", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.983.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.4", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.22.0", "@smithy/eventstream-serde-browser": "^4.2.8", "@smithy/eventstream-serde-config-resolver": "^4.3.8", "@smithy/eventstream-serde-node": "^4.2.8", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-blob-browser": "^4.2.9", "@smithy/hash-node": "^4.2.8", "@smithy/hash-stream-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/md5-js": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.12", "@smithy/middleware-retry": "^4.4.29", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.28", "@smithy/util-defaults-mode-node": "^4.2.31", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-stream": "^4.5.10", "@smithy/util-utf8": "^4.2.0", "@smithy/util-waiter": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-V40PT2irPh3lj+Z95tZI6batVrjaTrWEOXRNVBuoZSgpM3Ak1jiE9ZXwVLkMcbb9/GH4xVpB3EsGM7gbxmgFLQ=="],
|
"@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.971.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.970.0", "@aws-sdk/credential-provider-node": "3.971.0", "@aws-sdk/middleware-bucket-endpoint": "3.969.0", "@aws-sdk/middleware-expect-continue": "3.969.0", "@aws-sdk/middleware-flexible-checksums": "3.971.0", "@aws-sdk/middleware-host-header": "3.969.0", "@aws-sdk/middleware-location-constraint": "3.969.0", "@aws-sdk/middleware-logger": "3.969.0", "@aws-sdk/middleware-recursion-detection": "3.969.0", "@aws-sdk/middleware-sdk-s3": "3.970.0", "@aws-sdk/middleware-ssec": "3.971.0", "@aws-sdk/middleware-user-agent": "3.970.0", "@aws-sdk/region-config-resolver": "3.969.0", "@aws-sdk/signature-v4-multi-region": "3.970.0", "@aws-sdk/types": "3.969.0", "@aws-sdk/util-endpoints": "3.970.0", "@aws-sdk/util-user-agent-browser": "3.969.0", "@aws-sdk/util-user-agent-node": "3.971.0", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.20.6", "@smithy/eventstream-serde-browser": "^4.2.8", "@smithy/eventstream-serde-config-resolver": "^4.3.8", "@smithy/eventstream-serde-node": "^4.2.8", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-blob-browser": "^4.2.9", "@smithy/hash-node": "^4.2.8", "@smithy/hash-stream-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/md5-js": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.7", "@smithy/middleware-retry": "^4.4.23", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.10.8", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.22", "@smithy/util-defaults-mode-node": "^4.2.25", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-stream": "^4.5.10", "@smithy/util-utf8": "^4.2.0", "@smithy/util-waiter": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-BBUne390fKa4C4QvZlUZ5gKcu+Uyid4IyQ20N4jl0vS7SK2xpfXlJcgKqPW5ts6kx6hWTQBk6sH5Lf12RvuJxg=="],
|
||||||
|
|
||||||
"@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.982.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.6", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.6", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.982.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.4", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.22.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.12", "@smithy/middleware-retry": "^4.4.29", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.28", "@smithy/util-defaults-mode-node": "^4.2.31", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-qJrIiivmvujdGqJ0ldSUvhN3k3N7GtPesoOI1BSt0fNXovVnMz4C/JmnkhZihU7hJhDvxJaBROLYTU+lpild4w=="],
|
"@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.971.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.970.0", "@aws-sdk/middleware-host-header": "3.969.0", "@aws-sdk/middleware-logger": "3.969.0", "@aws-sdk/middleware-recursion-detection": "3.969.0", "@aws-sdk/middleware-user-agent": "3.970.0", "@aws-sdk/region-config-resolver": "3.969.0", "@aws-sdk/types": "3.969.0", "@aws-sdk/util-endpoints": "3.970.0", "@aws-sdk/util-user-agent-browser": "3.969.0", "@aws-sdk/util-user-agent-node": "3.971.0", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.20.6", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.7", "@smithy/middleware-retry": "^4.4.23", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.10.8", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.22", "@smithy/util-defaults-mode-node": "^4.2.25", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Xx+w6DQqJxDdymYyIxyKJnRzPvVJ4e/Aw0czO7aC9L/iraaV7AG8QtRe93OGW6aoHSh72CIiinnpJJfLsQqP4g=="],
|
||||||
|
|
||||||
"@aws-sdk/core": ["@aws-sdk/core@3.973.6", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.4", "@smithy/core": "^3.22.0", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-pz4ZOw3BLG0NdF25HoB9ymSYyPbMiIjwQJ2aROXRhAzt+b+EOxStfFv8s5iZyP6Kiw7aYhyWxj5G3NhmkoOTKw=="],
|
"@aws-sdk/core": ["@aws-sdk/core@3.970.0", "", { "dependencies": { "@aws-sdk/types": "3.969.0", "@aws-sdk/xml-builder": "3.969.0", "@smithy/core": "^3.20.6", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.10.8", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-klpzObldOq8HXzDjDlY6K8rMhYZU6mXRz6P9F9N+tWnjoYFfeBMra8wYApydElTUYQKP1O7RLHwH1OKFfKcqIA=="],
|
||||||
|
|
||||||
"@aws-sdk/crc64-nvme": ["@aws-sdk/crc64-nvme@3.972.0", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-ThlLhTqX68jvoIVv+pryOdb5coP1cX1/MaTbB9xkGDCbWbsqQcLqzPxuSoW1DCnAAIacmXCWpzUNOB9pv+xXQw=="],
|
"@aws-sdk/crc64-nvme": ["@aws-sdk/crc64-nvme@3.969.0", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-IGNkP54HD3uuLnrPCYsv3ZD478UYq+9WwKrIVJ9Pdi3hxPg8562CH3ZHf8hEgfePN31P9Kj+Zu9kq2Qcjjt61A=="],
|
||||||
|
|
||||||
"@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.4", "", { "dependencies": { "@aws-sdk/core": "^3.973.6", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-/8dnc7+XNMmViEom2xsNdArQxQPSgy4Z/lm6qaFPTrMFesT1bV3PsBhb19n09nmxHdrtQskYmViddUIjUQElXg=="],
|
"@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.970.0", "", { "dependencies": { "@aws-sdk/core": "3.970.0", "@aws-sdk/types": "3.969.0", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-rtVzXzEtAfZBfh+lq3DAvRar4c3jyptweOAJR2DweyXx71QSMY+O879hjpMwES7jl07a3O1zlnFIDo4KP/96kQ=="],
|
||||||
|
|
||||||
"@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.6", "", { "dependencies": { "@aws-sdk/core": "^3.973.6", "@aws-sdk/types": "^3.973.1", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/node-http-handler": "^4.4.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/util-stream": "^4.5.10", "tslib": "^2.6.2" } }, "sha512-5ERWqRljiZv44AIdvIRQ3k+EAV0Sq2WeJHvXuK7gL7bovSxOf8Al7MLH7Eh3rdovH4KHFnlIty7J71mzvQBl5Q=="],
|
"@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.970.0", "", { "dependencies": { "@aws-sdk/core": "3.970.0", "@aws-sdk/types": "3.969.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/node-http-handler": "^4.4.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.10.8", "@smithy/types": "^4.12.0", "@smithy/util-stream": "^4.5.10", "tslib": "^2.6.2" } }, "sha512-CjDbWL7JxjLc9ZxQilMusWSw05yRvUJKRpz59IxDpWUnSMHC9JMMUUkOy5Izk8UAtzi6gupRWArp4NG4labt9Q=="],
|
||||||
|
|
||||||
"@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.4", "", { "dependencies": { "@aws-sdk/core": "^3.973.6", "@aws-sdk/credential-provider-env": "^3.972.4", "@aws-sdk/credential-provider-http": "^3.972.6", "@aws-sdk/credential-provider-login": "^3.972.4", "@aws-sdk/credential-provider-process": "^3.972.4", "@aws-sdk/credential-provider-sso": "^3.972.4", "@aws-sdk/credential-provider-web-identity": "^3.972.4", "@aws-sdk/nested-clients": "3.982.0", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-eRUg+3HaUKuXWn/lEMirdiA5HOKmEl8hEHVuszIDt2MMBUKgVX5XNGmb3XmbgU17h6DZ+RtjbxQpjhz3SbTjZg=="],
|
"@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.971.0", "", { "dependencies": { "@aws-sdk/core": "3.970.0", "@aws-sdk/credential-provider-env": "3.970.0", "@aws-sdk/credential-provider-http": "3.970.0", "@aws-sdk/credential-provider-login": "3.971.0", "@aws-sdk/credential-provider-process": "3.970.0", "@aws-sdk/credential-provider-sso": "3.971.0", "@aws-sdk/credential-provider-web-identity": "3.971.0", "@aws-sdk/nested-clients": "3.971.0", "@aws-sdk/types": "3.969.0", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-c0TGJG4xyfTZz3SInXfGU8i5iOFRrLmy4Bo7lMyH+IpngohYMYGYl61omXqf2zdwMbDv+YJ9AviQTcCaEUKi8w=="],
|
||||||
|
|
||||||
"@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.4", "", { "dependencies": { "@aws-sdk/core": "^3.973.6", "@aws-sdk/nested-clients": "3.982.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-nLGjXuvWWDlQAp505xIONI7Gam0vw2p7Qu3P6on/W2q7rjJXtYjtpHbcsaOjJ/pAju3eTvEQuSuRedcRHVQIAQ=="],
|
"@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.971.0", "", { "dependencies": { "@aws-sdk/core": "3.970.0", "@aws-sdk/nested-clients": "3.971.0", "@aws-sdk/types": "3.969.0", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-yhbzmDOsk0RXD3rTPhZra4AWVnVAC4nFWbTp+sUty1hrOPurUmhuz8bjpLqYTHGnlMbJp+UqkQONhS2+2LzW2g=="],
|
||||||
|
|
||||||
"@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.5", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.4", "@aws-sdk/credential-provider-http": "^3.972.6", "@aws-sdk/credential-provider-ini": "^3.972.4", "@aws-sdk/credential-provider-process": "^3.972.4", "@aws-sdk/credential-provider-sso": "^3.972.4", "@aws-sdk/credential-provider-web-identity": "^3.972.4", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-VWXKgSISQCI2GKN3zakTNHSiZ0+mux7v6YHmmbLQp/o3fvYUQJmKGcLZZzg2GFA+tGGBStplra9VFNf/WwxpYg=="],
|
"@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.971.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.970.0", "@aws-sdk/credential-provider-http": "3.970.0", "@aws-sdk/credential-provider-ini": "3.971.0", "@aws-sdk/credential-provider-process": "3.970.0", "@aws-sdk/credential-provider-sso": "3.971.0", "@aws-sdk/credential-provider-web-identity": "3.971.0", "@aws-sdk/types": "3.969.0", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-epUJBAKivtJqalnEBRsYIULKYV063o/5mXNJshZfyvkAgNIzc27CmmKRXTN4zaNOZg8g/UprFp25BGsi19x3nQ=="],
|
||||||
|
|
||||||
"@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.4", "", { "dependencies": { "@aws-sdk/core": "^3.973.6", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-TCZpWUnBQN1YPk6grvd5x419OfXjHvhj5Oj44GYb84dOVChpg/+2VoEj+YVA4F4E/6huQPNnX7UYbTtxJqgihw=="],
|
"@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.970.0", "", { "dependencies": { "@aws-sdk/core": "3.970.0", "@aws-sdk/types": "3.969.0", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-0XeT8OaT9iMA62DFV9+m6mZfJhrD0WNKf4IvsIpj2Z7XbaYfz3CoDDvNoALf3rPY9NzyMHgDxOspmqdvXP00mw=="],
|
||||||
|
|
||||||
"@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.4", "", { "dependencies": { "@aws-sdk/client-sso": "3.982.0", "@aws-sdk/core": "^3.973.6", "@aws-sdk/token-providers": "3.982.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-wzsGwv9mKlwJ3vHLyembBvGE/5nPUIwRR2I51B1cBV4Cb4ql9nIIfpmHzm050XYTY5fqTOKJQnhLj7zj89VG8g=="],
|
"@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.971.0", "", { "dependencies": { "@aws-sdk/client-sso": "3.971.0", "@aws-sdk/core": "3.970.0", "@aws-sdk/token-providers": "3.971.0", "@aws-sdk/types": "3.969.0", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-dY0hMQ7dLVPQNJ8GyqXADxa9w5wNfmukgQniLxGVn+dMRx3YLViMp5ZpTSQpFhCWNF0oKQrYAI5cHhUJU1hETw=="],
|
||||||
|
|
||||||
"@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.4", "", { "dependencies": { "@aws-sdk/core": "^3.973.6", "@aws-sdk/nested-clients": "3.982.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-hIzw2XzrG8jzsUSEatehmpkd5rWzASg5IHUfA+m01k/RtvfAML7ZJVVohuKdhAYx+wV2AThLiQJVzqn7F0khrw=="],
|
"@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.971.0", "", { "dependencies": { "@aws-sdk/core": "3.970.0", "@aws-sdk/nested-clients": "3.971.0", "@aws-sdk/types": "3.969.0", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-F1AwfNLr7H52T640LNON/h34YDiMuIqW/ZreGzhRR6vnFGaSPtNSKAKB2ssAMkLM8EVg8MjEAYD3NCUiEo+t/w=="],
|
||||||
|
|
||||||
"@aws-sdk/middleware-bucket-endpoint": ["@aws-sdk/middleware-bucket-endpoint@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-arn-parser": "^3.972.2", "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-config-provider": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-fmbgWYirF67YF1GfD7cg5N6HHQ96EyRNx/rDIrTF277/zTWVuPI2qS/ZHgofwR1NZPe/NWvoppflQY01LrbVLg=="],
|
"@aws-sdk/middleware-bucket-endpoint": ["@aws-sdk/middleware-bucket-endpoint@3.969.0", "", { "dependencies": { "@aws-sdk/types": "3.969.0", "@aws-sdk/util-arn-parser": "3.968.0", "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-config-provider": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-MlbrlixtkTVhYhoasblKOkr7n2yydvUZjjxTnBhIuHmkyBS1619oGnTfq/uLeGYb4NYXdeQ5OYcqsRGvmWSuTw=="],
|
||||||
|
|
||||||
"@aws-sdk/middleware-expect-continue": ["@aws-sdk/middleware-expect-continue@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-4msC33RZsXQpUKR5QR4HnvBSNCPLGHmB55oDiROqqgyOc+TOfVu2xgi5goA7ms6MdZLeEh2905UfWMnMMF4mRg=="],
|
"@aws-sdk/middleware-expect-continue": ["@aws-sdk/middleware-expect-continue@3.969.0", "", { "dependencies": { "@aws-sdk/types": "3.969.0", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-qXygzSi8osok7tH9oeuS3HoKw6jRfbvg5Me/X5RlHOvSSqQz8c5O9f3MjUApaCUSwbAU92KrbZWasw2PKiaVHg=="],
|
||||||
|
|
||||||
"@aws-sdk/middleware-flexible-checksums": ["@aws-sdk/middleware-flexible-checksums@3.972.4", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", "@aws-sdk/core": "^3.973.6", "@aws-sdk/crc64-nvme": "3.972.0", "@aws-sdk/types": "^3.973.1", "@smithy/is-array-buffer": "^4.2.0", "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-stream": "^4.5.10", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-xOxsUkF3O3BtIe3tf54OpPo94eZepjFm3z0Dd2TZKbsPxMiRTFXurC04wJ58o/wPW9YHVO9VqZik3MfoPfrKlw=="],
|
"@aws-sdk/middleware-flexible-checksums": ["@aws-sdk/middleware-flexible-checksums@3.971.0", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", "@aws-sdk/core": "3.970.0", "@aws-sdk/crc64-nvme": "3.969.0", "@aws-sdk/types": "3.969.0", "@smithy/is-array-buffer": "^4.2.0", "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-stream": "^4.5.10", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-+hGUDUxeIw8s2kkjfeXym0XZxdh0cqkHkDpEanWYdS1gnWkIR+gf9u/DKbKqGHXILPaqHXhWpLTQTVlaB4sI7Q=="],
|
||||||
|
|
||||||
"@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-aknPTb2M+G3s+0qLCx4Li/qGZH8IIYjugHMv15JTYMe6mgZO8VBpYgeGYsNMGCqCZOcWzuf900jFBG5bopfzmA=="],
|
"@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.969.0", "", { "dependencies": { "@aws-sdk/types": "3.969.0", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-AWa4rVsAfBR4xqm7pybQ8sUNJYnjyP/bJjfAw34qPuh3M9XrfGbAHG0aiAfQGrBnmS28jlO6Kz69o+c6PRw1dw=="],
|
||||||
|
|
||||||
"@aws-sdk/middleware-location-constraint": ["@aws-sdk/middleware-location-constraint@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-nIg64CVrsXp67vbK0U1/Is8rik3huS3QkRHn2DRDx4NldrEFMgdkZGI/+cZMKD9k4YOS110Dfu21KZLHrFA/1g=="],
|
"@aws-sdk/middleware-location-constraint": ["@aws-sdk/middleware-location-constraint@3.969.0", "", { "dependencies": { "@aws-sdk/types": "3.969.0", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-zH7pDfMLG/C4GWMOpvJEoYcSpj7XsNP9+irlgqwi667sUQ6doHQJ3yyDut3yiTk0maq1VgmriPFELyI9lrvH/g=="],
|
||||||
|
|
||||||
"@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-Ftg09xNNRqaz9QNzlfdQWfpqMCJbsQdnZVJP55jfhbKi1+FTWxGuvfPoBhDHIovqWKjqbuiew3HuhxbJ0+OjgA=="],
|
"@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.969.0", "", { "dependencies": { "@aws-sdk/types": "3.969.0", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-xwrxfip7Y2iTtCMJ+iifN1E1XMOuhxIHY9DreMCvgdl4r7+48x2S1bCYPWH3eNY85/7CapBWdJ8cerpEl12sQQ=="],
|
||||||
|
|
||||||
"@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-PY57QhzNuXHnwbJgbWYTrqIDHYSeOlhfYERTAuc16LKZpTZRJUjzBFokp9hF7u1fuGeE3D70ERXzdbMBOqQz7Q=="],
|
"@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.969.0", "", { "dependencies": { "@aws-sdk/types": "3.969.0", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-2r3PuNquU3CcS1Am4vn/KHFwLi8QFjMdA/R+CRDXT4AFO/0qxevF/YStW3gAKntQIgWgQV8ZdEtKAoJvLI4UWg=="],
|
||||||
|
|
||||||
"@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.972.6", "", { "dependencies": { "@aws-sdk/core": "^3.973.6", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-arn-parser": "^3.972.2", "@smithy/core": "^3.22.0", "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-stream": "^4.5.10", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Xq7wM6kbgJN1UO++8dvH/efPb1nTwWqFCpZCR7RCLOETP7xAUAhVo7JmsCnML5Di/iC4Oo5VrJ4QmkYcMZniLw=="],
|
"@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.970.0", "", { "dependencies": { "@aws-sdk/core": "3.970.0", "@aws-sdk/types": "3.969.0", "@aws-sdk/util-arn-parser": "3.968.0", "@smithy/core": "^3.20.6", "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.10.8", "@smithy/types": "^4.12.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-stream": "^4.5.10", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-v/Y5F1lbFFY7vMeG5yYxuhnn0CAshz6KMxkz1pDyPxejNE9HtA0w8R6OTBh/bVdIm44QpjhbI7qeLdOE/PLzXQ=="],
|
||||||
|
|
||||||
"@aws-sdk/middleware-ssec": ["@aws-sdk/middleware-ssec@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-dU6kDuULN3o3jEHcjm0c4zWJlY1zWVkjG9NPe9qxYLLpcbdj5kRYBS2DdWYD+1B9f910DezRuws7xDEqKkHQIg=="],
|
"@aws-sdk/middleware-ssec": ["@aws-sdk/middleware-ssec@3.971.0", "", { "dependencies": { "@aws-sdk/types": "3.969.0", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-QGVhvRveYG64ZhnS/b971PxXM6N2NU79Fxck4EfQ7am8v1Br0ctoeDDAn9nXNblLGw87we9Z65F7hMxxiFHd3w=="],
|
||||||
|
|
||||||
"@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.6", "", { "dependencies": { "@aws-sdk/core": "^3.973.6", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.982.0", "@smithy/core": "^3.22.0", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-TehLN8W/kivl0U9HcS+keryElEWORROpghDXZBLfnb40DXM7hx/i+7OOjkogXQOF3QtUraJVRkHQ07bPhrWKlw=="],
|
"@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.970.0", "", { "dependencies": { "@aws-sdk/core": "3.970.0", "@aws-sdk/types": "3.969.0", "@aws-sdk/util-endpoints": "3.970.0", "@smithy/core": "^3.20.6", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-dnSJGGUGSFGEX2NzvjwSefH+hmZQ347AwbLhAsi0cdnISSge+pcGfOFrJt2XfBIypwFe27chQhlfuf/gWdzpZg=="],
|
||||||
|
|
||||||
"@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.982.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.6", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.6", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.982.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.4", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.22.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.12", "@smithy/middleware-retry": "^4.4.29", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.28", "@smithy/util-defaults-mode-node": "^4.2.31", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-VVkaH27digrJfdVrT64rjkllvOp4oRiZuuJvrylLXAKl18ujToJR7AqpDldL/LS63RVne3QWIpkygIymxFtliQ=="],
|
"@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.971.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.970.0", "@aws-sdk/middleware-host-header": "3.969.0", "@aws-sdk/middleware-logger": "3.969.0", "@aws-sdk/middleware-recursion-detection": "3.969.0", "@aws-sdk/middleware-user-agent": "3.970.0", "@aws-sdk/region-config-resolver": "3.969.0", "@aws-sdk/types": "3.969.0", "@aws-sdk/util-endpoints": "3.970.0", "@aws-sdk/util-user-agent-browser": "3.969.0", "@aws-sdk/util-user-agent-node": "3.971.0", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.20.6", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.7", "@smithy/middleware-retry": "^4.4.23", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.10.8", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.22", "@smithy/util-defaults-mode-node": "^4.2.25", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-TWaILL8GyYlhGrxxnmbkazM4QsXatwQgoWUvo251FXmUOsiXDFDVX3hoGIfB3CaJhV2pJPfebHUNJtY6TjZ11g=="],
|
||||||
|
|
||||||
"@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/config-resolver": "^4.4.6", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-v4J8qYAWfOMcZ4MJUyatntOicTzEMaU7j3OpkRCGGFSL2NgXQ5VbxauIyORA+pxdKZ0qQG2tCQjQjZDlXEC3Ow=="],
|
"@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.969.0", "", { "dependencies": { "@aws-sdk/types": "3.969.0", "@smithy/config-resolver": "^4.4.6", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-scj9OXqKpcjJ4jsFLtqYWz3IaNvNOQTFFvEY8XMJXTv+3qF5I7/x9SJtKzTRJEBF3spjzBUYPtGFbs9sj4fisQ=="],
|
||||||
|
|
||||||
"@aws-sdk/s3-presigned-post": ["@aws-sdk/s3-presigned-post@3.983.0", "", { "dependencies": { "@aws-sdk/client-s3": "3.983.0", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-format-url": "^3.972.3", "@smithy/middleware-endpoint": "^4.4.12", "@smithy/signature-v4": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-bzJJMLleqsCAOXCTldBweB2+crpfeTies0lwrTjX9q6o23rbfkmsp/OYzWGolWS+y/c2MqAp07f27NCeFY3Ayw=="],
|
"@aws-sdk/s3-presigned-post": ["@aws-sdk/s3-presigned-post@3.971.0", "", { "dependencies": { "@aws-sdk/client-s3": "3.971.0", "@aws-sdk/types": "3.969.0", "@aws-sdk/util-format-url": "3.969.0", "@smithy/middleware-endpoint": "^4.4.7", "@smithy/signature-v4": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Ww5DGQKa/LCIP2q8icpnKBXjAYpHvvhoj2vdETqfSLKBBytAy76OSwsaAKUNFDnHJiSEs4+XSCcvxntC6c/hIQ=="],
|
||||||
|
|
||||||
"@aws-sdk/s3-request-presigner": ["@aws-sdk/s3-request-presigner@3.983.0", "", { "dependencies": { "@aws-sdk/signature-v4-multi-region": "3.983.0", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-format-url": "^3.972.3", "@smithy/middleware-endpoint": "^4.4.12", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-O/PMdYcCVuBnOyuYglNvaNbh8RWMSE0zBwA8H6m+2VZbtLdALvyXgW5m6Q7wbnKdj+a4bJXh3AStDq6a82TA6Q=="],
|
"@aws-sdk/s3-request-presigner": ["@aws-sdk/s3-request-presigner@3.971.0", "", { "dependencies": { "@aws-sdk/signature-v4-multi-region": "3.970.0", "@aws-sdk/types": "3.969.0", "@aws-sdk/util-format-url": "3.969.0", "@smithy/middleware-endpoint": "^4.4.7", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.10.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-j4wCCoQ//xm03JQn7/Jq6BJ0HV3VzlI/HrIQSQupWWjZTrdxyqa9PXBhcYNNtvZtF1adA/cRpYTMS+2SUsZGRg=="],
|
||||||
|
|
||||||
"@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.983.0", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "^3.972.6", "@aws-sdk/types": "^3.973.1", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-11FCcxI/WKRufKDdPgKPXtrhjDArhkOPb4mf66rICZUnPHlD8Cb7cjZZS/eFC+iuwoHBosrxo0hYsvK3s7DxGw=="],
|
"@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.970.0", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "3.970.0", "@aws-sdk/types": "3.969.0", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-z3syXfuK/x/IsKf/AeYmgc2NT7fcJ+3fHaGO+fkghkV9WEba3fPyOwtTBX4KpFMNb2t50zDGZwbzW1/5ighcUQ=="],
|
||||||
|
|
||||||
"@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.982.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.6", "@aws-sdk/nested-clients": "3.982.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-v3M0KYp2TVHYHNBT7jHD9lLTWAdS9CaWJ2jboRKt0WAB65bA7iUEpR+k4VqKYtpQN4+8kKSc4w+K6kUNZkHKQw=="],
|
"@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.971.0", "", { "dependencies": { "@aws-sdk/core": "3.970.0", "@aws-sdk/nested-clients": "3.971.0", "@aws-sdk/types": "3.969.0", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-4hKGWZbmuDdONMJV0HJ+9jwTDb0zLfKxcCLx2GEnBY31Gt9GeyIQ+DZ97Bb++0voawj6pnZToFikXTyrEq2x+w=="],
|
||||||
|
|
||||||
"@aws-sdk/types": ["@aws-sdk/types@3.973.1", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg=="],
|
"@aws-sdk/types": ["@aws-sdk/types@3.969.0", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-7IIzM5TdiXn+VtgPdVLjmE6uUBUtnga0f4RiSEI1WW10RPuNvZ9U+pL3SwDiRDAdoGrOF9tSLJOFZmfuwYuVYQ=="],
|
||||||
|
|
||||||
"@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.972.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-VkykWbqMjlSgBFDyrY3nOSqupMc6ivXuGmvci6Q3NnLq5kC+mKQe2QBZ4nrWRE/jqOxeFP2uYzLtwncYYcvQDg=="],
|
"@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.968.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-gqqvYcitIIM2K4lrDX9de9YvOfXBcVdxfT/iLnvHJd4YHvSXlt+gs+AsL4FfPCxG4IG9A+FyulP9Sb1MEA75vw=="],
|
||||||
|
|
||||||
"@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.983.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-t/VbL2X3gvDEjC4gdySOeFFOZGQEBKwa23pRHeB7hBLBZ119BB/2OEFtTFWKyp3bnMQgxpeVeGS7/hxk6wpKJw=="],
|
"@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.970.0", "", { "dependencies": { "@aws-sdk/types": "3.969.0", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-TZNZqFcMUtjvhZoZRtpEGQAdULYiy6rcGiXAbLU7e9LSpIYlRqpLa207oMNfgbzlL2PnHko+eVg8rajDiSOYCg=="],
|
||||||
|
|
||||||
"@aws-sdk/util-format-url": ["@aws-sdk/util-format-url@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/querystring-builder": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-n7F2ycckcKFXa01vAsT/SJdjFHfKH9s96QHcs5gn8AaaigASICeME8WdUL9uBp8XV/OVwEt8+6gzn6KFUgQa8g=="],
|
"@aws-sdk/util-format-url": ["@aws-sdk/util-format-url@3.969.0", "", { "dependencies": { "@aws-sdk/types": "3.969.0", "@smithy/querystring-builder": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-C7ZiE8orcrEF9In+XDlIKrZhMjp0HCPUH6u74pgadE3T2LRre5TmOQcTt785/wVS2G0we9cxkjlzMrfDsfPvFw=="],
|
||||||
|
|
||||||
"@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.4", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-H1onv5SkgPBK2P6JR2MjGgbOnttoNzSPIRoeZTNPZYyaplwGg50zS3amXvXqF0/qfXpWEC9rLWU564QTB9bSog=="],
|
"@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-qKgO7wAYsXzhwCHhdbaKFyxd83Fgs8/1Ka+jjSPrv2Ll7mB55Wbwlo0kkfMLh993/yEc8aoDIAc1Fz9h4Spi4Q=="],
|
||||||
|
|
||||||
"@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-JurOwkRUcXD/5MTDBcqdyQ9eVedtAsZgw5rBwktsPTN7QtPiS2Ld1jkJepNgYoCufz1Wcut9iup7GJDoIHp8Fw=="],
|
"@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.969.0", "", { "dependencies": { "@aws-sdk/types": "3.969.0", "@smithy/types": "^4.12.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-bpJGjuKmFr0rA6UKUCmN8D19HQFMLXMx5hKBXqBlPFdalMhxJSjcxzX9DbQh0Fn6bJtxCguFmRGOBdQqNOt49g=="],
|
||||||
|
|
||||||
"@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.972.4", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.6", "@aws-sdk/types": "^3.973.1", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-3WFCBLiM8QiHDfosQq3Py+lIMgWlFWwFQliUHUqwEiRqLnKyhgbU3AKa7AWJF7lW2Oc/2kFNY4MlAYVnVc0i8A=="],
|
"@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.971.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.970.0", "@aws-sdk/types": "3.969.0", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-Eygjo9mFzQYjbGY3MYO6CsIhnTwAMd3WmuFalCykqEmj2r5zf0leWrhPaqvA5P68V5JdGfPYgj7vhNOd6CtRBQ=="],
|
||||||
|
|
||||||
"@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.4", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.3.4", "tslib": "^2.6.2" } }, "sha512-0zJ05ANfYqI6+rGqj8samZBFod0dPPousBjLEqg8WdxSgbMAkRgLyn81lP215Do0rFJ/17LIXwr7q0yK24mP6Q=="],
|
"@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.969.0", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" } }, "sha512-BSe4Lx/qdRQQdX8cSSI7Et20vqBspzAjBy8ZmXVoyLkol3y4sXBXzn+BiLtR+oh60ExQn6o2DU4QjdOZbXaKIQ=="],
|
||||||
|
|
||||||
"@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.3", "", {}, "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw=="],
|
"@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.3", "", {}, "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw=="],
|
||||||
|
|
||||||
"@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
|
"@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="],
|
||||||
|
|
||||||
"@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="],
|
"@babel/compat-data": ["@babel/compat-data@7.28.6", "", {}, "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg=="],
|
||||||
|
|
||||||
"@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="],
|
"@babel/core": ["@babel/core@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw=="],
|
||||||
|
|
||||||
"@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="],
|
"@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="],
|
||||||
|
|
||||||
"@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="],
|
"@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="],
|
||||||
|
|
||||||
@@ -168,7 +167,7 @@
|
|||||||
|
|
||||||
"@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="],
|
"@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="],
|
||||||
|
|
||||||
"@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
|
"@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="],
|
||||||
|
|
||||||
"@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w=="],
|
"@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w=="],
|
||||||
|
|
||||||
@@ -178,25 +177,25 @@
|
|||||||
|
|
||||||
"@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
|
"@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
|
||||||
|
|
||||||
"@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="],
|
"@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="],
|
||||||
|
|
||||||
"@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
|
"@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="],
|
||||||
|
|
||||||
"@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.2", "", {}, "sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ=="],
|
"@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.2", "", {}, "sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ=="],
|
||||||
|
|
||||||
"@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.12.0", "", { "peerDependencies": { "unenv": "2.0.0-rc.24", "workerd": "^1.20260115.0" }, "optionalPeers": ["workerd"] }, "sha512-NK4vN+2Z/GbfGS4BamtbbVk1rcu5RmqaYGiyHJQrA09AoxdZPHDF3W/EhgI0YSK8p3vRo/VNCtbSJFPON7FWMQ=="],
|
"@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.10.0", "", { "peerDependencies": { "unenv": "2.0.0-rc.24", "workerd": "^1.20251221.0" }, "optionalPeers": ["workerd"] }, "sha512-/uII4vLQXhzCAZzEVeYAjFLBNg2nqTJ1JGzd2lRF6ItYe6U2zVoYGfeKpGx/EkBF6euiU+cyBXgMdtJih+nQ6g=="],
|
||||||
|
|
||||||
"@cloudflare/vite-plugin": ["@cloudflare/vite-plugin@1.23.0", "", { "dependencies": { "@cloudflare/unenv-preset": "2.12.0", "miniflare": "4.20260131.0", "unenv": "2.0.0-rc.24", "wrangler": "4.62.0", "ws": "8.18.0" }, "peerDependencies": { "vite": "^6.1.0 || ^7.0.0" } }, "sha512-Pz3kF5wxUx99NOOYPq/jgaknKQuamN52FQkc8WBmLfbzBd9fWu+4NaJeZjDtFTXUBA0FEA7bOROuV52YFOA2TA=="],
|
"@cloudflare/vite-plugin": ["@cloudflare/vite-plugin@1.21.0", "", { "dependencies": { "@cloudflare/unenv-preset": "2.10.0", "miniflare": "4.20260114.0", "unenv": "2.0.0-rc.24", "wrangler": "4.59.2", "ws": "8.18.0" }, "peerDependencies": { "vite": "^6.1.0 || ^7.0.0" } }, "sha512-3VXtkfjOQL+k3Plj+t0BHRyw8iIIRBQ8RJU6KJHJQKdYHA6rJE/WlSa/lRd0A8MMhvP8e8QiMLuDqveEN8gCZg=="],
|
||||||
|
|
||||||
"@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20260131.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-+1X4qErc715NUhJZNhtlpuCxajhD5YNre7Cz50WPMmj+BMUrh9h7fntKEadtrUo5SM2YONY7CDzK7wdWbJJBVA=="],
|
"@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20260114.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-HNlsRkfNgardCig2P/5bp/dqDECsZ4+NU5XewqArWxMseqt3C5daSuptI620s4pn7Wr0ZKg7jVLH0PDEBkA+aA=="],
|
||||||
|
|
||||||
"@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20260131.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-M84mXR8WEMEBuX4/dL2IQ4wHV/ALwYjx9if5ePZR8rdbD7if/fkEEoMBq0bGS/1gMLRqqCZLstabxHV+g92NNg=="],
|
"@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20260114.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-qyE1UdFnAlxzb+uCfN/d9c8icch7XRiH49/DjoqEa+bCDihTuRS7GL1RmhVIqHJhb3pX3DzxmKgQZBDBL83Inw=="],
|
||||||
|
|
||||||
"@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20260131.0", "", { "os": "linux", "cpu": "x64" }, "sha512-SWzr48bCL9y5wjkj23tXS6t/6us99EAH9T5TAscMV0hfJFZQt97RY/gaHKyRRjFv6jfJZvk7d4g+OmGeYBnwcg=="],
|
"@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20260114.0", "", { "os": "linux", "cpu": "x64" }, "sha512-Z0BLvAj/JPOabzads2ddDEfgExWTlD22pnwsuNbPwZAGTSZeQa3Y47eGUWyHk+rSGngknk++S7zHTGbKuG7RRg=="],
|
||||||
|
|
||||||
"@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20260131.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-mL0kLPGIBJRPeHS3+erJ2t5dJT3ODhsKvR9aA4BcsY7M30/QhlgJIF6wsgwNisTJ23q8PbobZNHBUKIe8l/E9A=="],
|
"@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20260114.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-kPUmEtUxUWlr9PQ64kuhdK0qyo8idPe5IIXUgi7xCD7mDd6EOe5J7ugDpbfvfbYKEjx4DpLvN2t45izyI/Sodw=="],
|
||||||
|
|
||||||
"@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20260131.0", "", { "os": "win32", "cpu": "x64" }, "sha512-hoQqTFBpP1zntP2OQSpt5dEWbd9vSBliK+G7LmDXjKitPkmkRFo2PB4P9aBRE1edPAIO/fpdoJv928k2HaAn4A=="],
|
"@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20260114.0", "", { "os": "win32", "cpu": "x64" }, "sha512-MJnKgm6i1jZGyt2ZHQYCnRlpFTEZcK2rv9y7asS3KdVEXaDgGF8kOns5u6YL6/+eMogfZuHRjfDS+UqRTUYIFA=="],
|
||||||
|
|
||||||
"@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="],
|
"@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="],
|
||||||
|
|
||||||
@@ -254,6 +253,8 @@
|
|||||||
|
|
||||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.2", "", { "os": "win32", "cpu": "x64" }, "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ=="],
|
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.2", "", { "os": "win32", "cpu": "x64" }, "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ=="],
|
||||||
|
|
||||||
|
"@hiogawa/tiny-rpc": ["@hiogawa/tiny-rpc@0.2.3-pre.18", "", {}, "sha512-BiNHrutG9G9yV622QvkxZxF+PhkaH2Aspp4/X1KYTfnaQTcg4fFUTBWf5Kf533swon2SuVJwi6U6H1LQbhVOQQ=="],
|
||||||
|
|
||||||
"@hiogawa/utils": ["@hiogawa/utils@1.7.0", "", {}, "sha512-ghiEFWBR1NENoHn+lSuW7liicTIzVPN+8Srm5UedCTw43gus0mlse6Wp2lz6GmbOXJ/CalMPp/0Tz2X8tajkAg=="],
|
"@hiogawa/utils": ["@hiogawa/utils@1.7.0", "", {}, "sha512-ghiEFWBR1NENoHn+lSuW7liicTIzVPN+8Srm5UedCTw43gus0mlse6Wp2lz6GmbOXJ/CalMPp/0Tz2X8tajkAg=="],
|
||||||
|
|
||||||
"@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="],
|
"@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="],
|
||||||
@@ -320,8 +321,6 @@
|
|||||||
|
|
||||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||||
|
|
||||||
"@pinia/colada": ["@pinia/colada@0.21.2", "", { "peerDependencies": { "pinia": "^2.2.6 || ^3.0.0", "vue": "^3.5.17" } }, "sha512-k2epk1jed5cTmNA7l00UtsFRyqw9HfyU6WO4cV0BMUT3sSE4CMLCilprbLAL5h2bxD76WSiglciI/6o+Uh7Vzw=="],
|
|
||||||
|
|
||||||
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
|
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
|
||||||
|
|
||||||
"@poppinss/colors": ["@poppinss/colors@4.1.6", "", { "dependencies": { "kleur": "^4.1.5" } }, "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg=="],
|
"@poppinss/colors": ["@poppinss/colors@4.1.6", "", { "dependencies": { "kleur": "^4.1.5" } }, "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg=="],
|
||||||
@@ -330,59 +329,63 @@
|
|||||||
|
|
||||||
"@poppinss/exception": ["@poppinss/exception@1.2.3", "", {}, "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw=="],
|
"@poppinss/exception": ["@poppinss/exception@1.2.3", "", {}, "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw=="],
|
||||||
|
|
||||||
|
"@primevue/auto-import-resolver": ["@primevue/auto-import-resolver@4.5.4", "", { "dependencies": { "@primevue/metadata": "4.5.4" } }, "sha512-YQHrZ9PQSG/4K2BwthA2Xuna4WyS0JMHajiHD9PljaDyQtBVwCadX5ZpKcrAUWR8E/1gjva8x/si0RYxxYrRJw=="],
|
||||||
|
|
||||||
|
"@primevue/metadata": ["@primevue/metadata@4.5.4", "", {}, "sha512-jJFD0KYm8bPYgFo0JP3Dc2RkyXzrMI1XHQGsEKTysx9Jx2d1XdxtFji/ZsQeoo/RmwUNof5ciZ72URq37rnK+g=="],
|
||||||
|
|
||||||
"@quansync/fs": ["@quansync/fs@1.0.0", "", { "dependencies": { "quansync": "^1.0.0" } }, "sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ=="],
|
"@quansync/fs": ["@quansync/fs@1.0.0", "", { "dependencies": { "quansync": "^1.0.0" } }, "sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ=="],
|
||||||
|
|
||||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.2", "", {}, "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw=="],
|
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.53", "", {}, "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ=="],
|
||||||
|
|
||||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.1", "", { "os": "android", "cpu": "arm" }, "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg=="],
|
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.55.1", "", { "os": "android", "cpu": "arm" }, "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg=="],
|
||||||
|
|
||||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.57.1", "", { "os": "android", "cpu": "arm64" }, "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w=="],
|
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.55.1", "", { "os": "android", "cpu": "arm64" }, "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg=="],
|
||||||
|
|
||||||
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.57.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg=="],
|
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.55.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg=="],
|
||||||
|
|
||||||
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.57.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w=="],
|
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.55.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ=="],
|
||||||
|
|
||||||
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.57.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug=="],
|
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.55.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg=="],
|
||||||
|
|
||||||
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.57.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q=="],
|
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.55.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw=="],
|
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.55.1", "", { "os": "linux", "cpu": "arm" }, "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw=="],
|
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.55.1", "", { "os": "linux", "cpu": "arm" }, "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g=="],
|
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.55.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q=="],
|
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.55.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA=="],
|
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.55.1", "", { "os": "linux", "cpu": "none" }, "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw=="],
|
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.55.1", "", { "os": "linux", "cpu": "none" }, "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w=="],
|
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.55.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw=="],
|
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.55.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A=="],
|
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.55.1", "", { "os": "linux", "cpu": "none" }, "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw=="],
|
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.55.1", "", { "os": "linux", "cpu": "none" }, "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.57.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg=="],
|
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.55.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg=="],
|
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.55.1", "", { "os": "linux", "cpu": "x64" }, "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw=="],
|
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.55.1", "", { "os": "linux", "cpu": "x64" }, "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w=="],
|
||||||
|
|
||||||
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.57.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw=="],
|
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.55.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg=="],
|
||||||
|
|
||||||
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.57.1", "", { "os": "none", "cpu": "arm64" }, "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ=="],
|
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.55.1", "", { "os": "none", "cpu": "arm64" }, "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw=="],
|
||||||
|
|
||||||
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.57.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ=="],
|
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.55.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g=="],
|
||||||
|
|
||||||
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.57.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew=="],
|
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.55.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA=="],
|
||||||
|
|
||||||
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ=="],
|
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.55.1", "", { "os": "win32", "cpu": "x64" }, "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg=="],
|
||||||
|
|
||||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA=="],
|
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.55.1", "", { "os": "win32", "cpu": "x64" }, "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw=="],
|
||||||
|
|
||||||
"@sindresorhus/is": ["@sindresorhus/is@7.2.0", "", {}, "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw=="],
|
"@sindresorhus/is": ["@sindresorhus/is@7.2.0", "", {}, "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw=="],
|
||||||
|
|
||||||
@@ -394,7 +397,7 @@
|
|||||||
|
|
||||||
"@smithy/config-resolver": ["@smithy/config-resolver@4.4.6", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ=="],
|
"@smithy/config-resolver": ["@smithy/config-resolver@4.4.6", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ=="],
|
||||||
|
|
||||||
"@smithy/core": ["@smithy/core@3.22.1", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.9", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-stream": "^4.5.11", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-x3ie6Crr58MWrm4viHqqy2Du2rHYZjwu8BekasrQx4ca+Y24dzVAwq3yErdqIbc2G3I0kLQA13PQ+/rde+u65g=="],
|
"@smithy/core": ["@smithy/core@3.20.6", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.9", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-stream": "^4.5.10", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-BpAffW1mIyRZongoKBbh3RgHG+JDHJek/8hjA/9LnPunM+ejorO6axkxCgwxCe4K//g/JdPeR9vROHDYr/hfnQ=="],
|
||||||
|
|
||||||
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.8", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw=="],
|
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.8", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw=="],
|
||||||
|
|
||||||
@@ -424,9 +427,9 @@
|
|||||||
|
|
||||||
"@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.8", "", { "dependencies": { "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A=="],
|
"@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.8", "", { "dependencies": { "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A=="],
|
||||||
|
|
||||||
"@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.13", "", { "dependencies": { "@smithy/core": "^3.22.1", "@smithy/middleware-serde": "^4.2.9", "@smithy/node-config-provider": "^4.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-x6vn0PjYmGdNuKh/juUJJewZh7MoQ46jYaJ2mvekF4EesMuFfrl4LaW/k97Zjf8PTCPQmPgMvwewg7eNoH9n5w=="],
|
"@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.7", "", { "dependencies": { "@smithy/core": "^3.20.6", "@smithy/middleware-serde": "^4.2.9", "@smithy/node-config-provider": "^4.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-SCmhUG1UwtnEhF5Sxd8qk7bJwkj1BpFzFlHkXqKCEmDPLrRjJyTGM0EhqT7XBtDaDJjCfjRJQodgZcKDR843qg=="],
|
||||||
|
|
||||||
"@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.30", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/service-error-classification": "^4.2.8", "@smithy/smithy-client": "^4.11.2", "@smithy/types": "^4.12.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-CBGyFvN0f8hlnqKH/jckRDz78Snrp345+PVk8Ux7pnkUCW97Iinse59lY78hBt04h1GZ6hjBN94BRwZy1xC8Bg=="],
|
"@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.23", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/service-error-classification": "^4.2.8", "@smithy/smithy-client": "^4.10.8", "@smithy/types": "^4.12.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-lLEmkQj7I7oKfvZ1wsnToGJouLOtfkMXDKRA1Hi6F+mMp5O1N8GcVWmVeNgTtgZtd0OTXDTI2vpVQmeutydGew=="],
|
||||||
|
|
||||||
"@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.9", "", { "dependencies": { "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ=="],
|
"@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.9", "", { "dependencies": { "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ=="],
|
||||||
|
|
||||||
@@ -434,7 +437,7 @@
|
|||||||
|
|
||||||
"@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.8", "", { "dependencies": { "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg=="],
|
"@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.8", "", { "dependencies": { "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg=="],
|
||||||
|
|
||||||
"@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.9", "", { "dependencies": { "@smithy/abort-controller": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/querystring-builder": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-KX5Wml5mF+luxm1szW4QDz32e3NObgJ4Fyw+irhph4I/2geXwUy4jkIMUs5ZPGflRBeR6BUkC2wqIab4Llgm3w=="],
|
"@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.8", "", { "dependencies": { "@smithy/abort-controller": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/querystring-builder": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-q9u+MSbJVIJ1QmJ4+1u+cERXkrhuILCBDsJUBAW1MPE6sFonbCNaegFuwW9ll8kh5UdyY3jOkoOGlc7BesoLpg=="],
|
||||||
|
|
||||||
"@smithy/property-provider": ["@smithy/property-provider@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w=="],
|
"@smithy/property-provider": ["@smithy/property-provider@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w=="],
|
||||||
|
|
||||||
@@ -450,7 +453,7 @@
|
|||||||
|
|
||||||
"@smithy/signature-v4": ["@smithy/signature-v4@5.3.8", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg=="],
|
"@smithy/signature-v4": ["@smithy/signature-v4@5.3.8", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg=="],
|
||||||
|
|
||||||
"@smithy/smithy-client": ["@smithy/smithy-client@4.11.2", "", { "dependencies": { "@smithy/core": "^3.22.1", "@smithy/middleware-endpoint": "^4.4.13", "@smithy/middleware-stack": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-stream": "^4.5.11", "tslib": "^2.6.2" } }, "sha512-SCkGmFak/xC1n7hKRsUr6wOnBTJ3L22Qd4e8H1fQIuKTAjntwgU8lrdMe7uHdiT2mJAOWA/60qaW9tiMu69n1A=="],
|
"@smithy/smithy-client": ["@smithy/smithy-client@4.10.8", "", { "dependencies": { "@smithy/core": "^3.20.6", "@smithy/middleware-endpoint": "^4.4.7", "@smithy/middleware-stack": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-stream": "^4.5.10", "tslib": "^2.6.2" } }, "sha512-wcr3UEL26k7lLoyf9eVDZoD1nNY3Fa1gbNuOXvfxvVWLGkOVW+RYZgUUp/bXHryJfycIOQnBq9o1JAE00ax8HQ=="],
|
||||||
|
|
||||||
"@smithy/types": ["@smithy/types@4.12.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw=="],
|
"@smithy/types": ["@smithy/types@4.12.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw=="],
|
||||||
|
|
||||||
@@ -466,9 +469,9 @@
|
|||||||
|
|
||||||
"@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q=="],
|
"@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q=="],
|
||||||
|
|
||||||
"@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.29", "", { "dependencies": { "@smithy/property-provider": "^4.2.8", "@smithy/smithy-client": "^4.11.2", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-nIGy3DNRmOjaYaaKcQDzmWsro9uxlaqUOhZDHQed9MW/GmkBZPtnU70Pu1+GT9IBmUXwRdDuiyaeiy9Xtpn3+Q=="],
|
"@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.22", "", { "dependencies": { "@smithy/property-provider": "^4.2.8", "@smithy/smithy-client": "^4.10.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-O2WXr6ZRqPnbyoepb7pKcLt1QL6uRfFzGYJ9sGb5hMJQi7v/4RjRmCQa9mNjA0YiXqsc5lBmLXqJPhjM1Vjv5A=="],
|
||||||
|
|
||||||
"@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.32", "", { "dependencies": { "@smithy/config-resolver": "^4.4.6", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/smithy-client": "^4.11.2", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-7dtFff6pu5fsjqrVve0YMhrnzJtccCWDacNKOkiZjJ++fmjGExmmSu341x+WU6Oc1IccL7lDuaUj7SfrHpWc5Q=="],
|
"@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.25", "", { "dependencies": { "@smithy/config-resolver": "^4.4.6", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/smithy-client": "^4.10.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-7uMhppVNRbgNIpyUBVRfjGHxygP85wpXalRvn9DvUlCx4qgy1AB/uxOPSiDx/jFyrwD3/BypQhx1JK7f3yxrAw=="],
|
||||||
|
|
||||||
"@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.8", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw=="],
|
"@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.8", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw=="],
|
||||||
|
|
||||||
@@ -478,7 +481,7 @@
|
|||||||
|
|
||||||
"@smithy/util-retry": ["@smithy/util-retry@4.2.8", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-CfJqwvoRY0kTGe5AkQokpURNCT1u/MkRzMTASWMPPo2hNSnKtF1D45dQl3DE2LKLr4m+PW9mCeBMJr5mCAVThg=="],
|
"@smithy/util-retry": ["@smithy/util-retry@4.2.8", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-CfJqwvoRY0kTGe5AkQokpURNCT1u/MkRzMTASWMPPo2hNSnKtF1D45dQl3DE2LKLr4m+PW9mCeBMJr5mCAVThg=="],
|
||||||
|
|
||||||
"@smithy/util-stream": ["@smithy/util-stream@4.5.11", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.9", "@smithy/node-http-handler": "^4.4.9", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-lKmZ0S/3Qj2OF5H1+VzvDLb6kRxGzZHq6f3rAsoSu5cTLGsn3v3VQBA8czkNNXlLjoFEtVu3OQT2jEeOtOE2CA=="],
|
"@smithy/util-stream": ["@smithy/util-stream@4.5.10", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.9", "@smithy/node-http-handler": "^4.4.8", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-jbqemy51UFSZSp2y0ZmRfckmrzuKww95zT9BYMmuJ8v3altGcqjwoV1tzpOwuHaKrwQrCjIzOib499ymr2f98g=="],
|
||||||
|
|
||||||
"@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="],
|
"@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="],
|
||||||
|
|
||||||
@@ -506,11 +509,9 @@
|
|||||||
|
|
||||||
"@tanstack/vue-table": ["@tanstack/vue-table@8.21.3", "", { "dependencies": { "@tanstack/table-core": "8.21.3" }, "peerDependencies": { "vue": ">=3.2" } }, "sha512-rusRyd77c5tDPloPskctMyPLFEQUeBzxdQ+2Eow4F7gDPlPOB1UnnhzfpdvqZ8ZyX2rRNGmqNnQWm87OI2OQPw=="],
|
"@tanstack/vue-table": ["@tanstack/vue-table@8.21.3", "", { "dependencies": { "@tanstack/table-core": "8.21.3" }, "peerDependencies": { "vue": ">=3.2" } }, "sha512-rusRyd77c5tDPloPskctMyPLFEQUeBzxdQ+2Eow4F7gDPlPOB1UnnhzfpdvqZ8ZyX2rRNGmqNnQWm87OI2OQPw=="],
|
||||||
|
|
||||||
"@tanstack/zod-form-adapter": ["@tanstack/zod-form-adapter@0.42.1", "", { "dependencies": { "@tanstack/form-core": "0.42.1" }, "peerDependencies": { "zod": "^3.x" } }, "sha512-hPRM0lawVKP64yurW4c6KHZH6altMo2MQN14hfi+GMBTKjO9S7bW1x5LPZ5cayoJE3mBvdlahpSGT5rYZtSbXQ=="],
|
|
||||||
|
|
||||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||||
|
|
||||||
"@types/node": ["@types/node@25.2.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w=="],
|
"@types/node": ["@types/node@25.0.9", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw=="],
|
||||||
|
|
||||||
"@types/web-bluetooth": ["@types/web-bluetooth@0.0.21", "", {}, "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="],
|
"@types/web-bluetooth": ["@types/web-bluetooth@0.0.21", "", {}, "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="],
|
||||||
|
|
||||||
@@ -564,9 +565,9 @@
|
|||||||
|
|
||||||
"@unocss/vite": ["@unocss/vite@66.6.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "@unocss/config": "66.6.0", "@unocss/core": "66.6.0", "@unocss/inspector": "66.6.0", "chokidar": "^5.0.0", "magic-string": "^0.30.21", "pathe": "^2.0.3", "tinyglobby": "^0.2.15", "unplugin-utils": "^0.3.1" }, "peerDependencies": { "vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 || ^8.0.0-0" } }, "sha512-SC0/rX0xSjdu8Jaj98XztHOuvXHWDVk0YaHKRAQks2Oj3yyqAOrhzhDUH0zzFaQWf5bsKVYK40H+h4rMk9vm5Q=="],
|
"@unocss/vite": ["@unocss/vite@66.6.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "@unocss/config": "66.6.0", "@unocss/core": "66.6.0", "@unocss/inspector": "66.6.0", "chokidar": "^5.0.0", "magic-string": "^0.30.21", "pathe": "^2.0.3", "tinyglobby": "^0.2.15", "unplugin-utils": "^0.3.1" }, "peerDependencies": { "vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 || ^8.0.0-0" } }, "sha512-SC0/rX0xSjdu8Jaj98XztHOuvXHWDVk0YaHKRAQks2Oj3yyqAOrhzhDUH0zzFaQWf5bsKVYK40H+h4rMk9vm5Q=="],
|
||||||
|
|
||||||
"@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.4", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.2" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "vue": "^3.2.25" } }, "sha512-uM5iXipgYIn13UUQCZNdWkYk+sysBeA97d5mHsAoAt1u/wpN3+zxOmsVJWosuzX+IMGRzeYUNytztrYznboIkQ=="],
|
"@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.3", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-beta.53" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "vue": "^3.2.25" } }, "sha512-TlGPkLFLVOY3T7fZrwdvKpjprR3s4fxRln0ORDo1VQ7HHyxJwTlrjKU3kpVWTlaAjIEuCTokmjkZnr8Tpc925w=="],
|
||||||
|
|
||||||
"@vitejs/plugin-vue-jsx": ["@vitejs/plugin-vue-jsx@5.1.4", "", { "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-syntax-typescript": "^7.28.6", "@babel/plugin-transform-typescript": "^7.28.6", "@rolldown/pluginutils": "^1.0.0-rc.2", "@vue/babel-plugin-jsx": "^2.0.1" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "vue": "^3.0.0" } }, "sha512-70LmoVk9riR7qc4W2CpjsbNMWTPnuZb9dpFKX1emru0yP57nsc9k8nhLA6U93ngQapv5VDIUq2JatNfLbBIkrA=="],
|
"@vitejs/plugin-vue-jsx": ["@vitejs/plugin-vue-jsx@5.1.3", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/plugin-transform-typescript": "^7.28.5", "@rolldown/pluginutils": "^1.0.0-beta.56", "@vue/babel-plugin-jsx": "^2.0.1" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "vue": "^3.0.0" } }, "sha512-I6Zr8cYVr5WHMW5gNOP09DNqW9rgO8RX73Wa6Czgq/0ndpTfJM4vfDChfOT1+3KtdrNqilNBtNlFwVeB02ZzGw=="],
|
||||||
|
|
||||||
"@vue-macros/common": ["@vue-macros/common@3.1.2", "", { "dependencies": { "@vue/compiler-sfc": "^3.5.22", "ast-kit": "^2.1.2", "local-pkg": "^1.1.2", "magic-string-ast": "^1.0.2", "unplugin-utils": "^0.3.0" }, "peerDependencies": { "vue": "^2.7.0 || ^3.2.25" }, "optionalPeers": ["vue"] }, "sha512-h9t4ArDdniO9ekYHAD95t9AZcAbb19lEGK+26iAjUODOIJKmObDNBSe4+6ELQAA3vtYiFPPBtHh7+cQCKi3Dng=="],
|
"@vue-macros/common": ["@vue-macros/common@3.1.2", "", { "dependencies": { "@vue/compiler-sfc": "^3.5.22", "ast-kit": "^2.1.2", "local-pkg": "^1.1.2", "magic-string-ast": "^1.0.2", "unplugin-utils": "^0.3.0" }, "peerDependencies": { "vue": "^2.7.0 || ^3.2.25" }, "optionalPeers": ["vue"] }, "sha512-h9t4ArDdniO9ekYHAD95t9AZcAbb19lEGK+26iAjUODOIJKmObDNBSe4+6ELQAA3vtYiFPPBtHh7+cQCKi3Dng=="],
|
||||||
|
|
||||||
@@ -600,11 +601,11 @@
|
|||||||
|
|
||||||
"@vue/shared": ["@vue/shared@3.5.27", "", {}, "sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ=="],
|
"@vue/shared": ["@vue/shared@3.5.27", "", {}, "sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ=="],
|
||||||
|
|
||||||
"@vueuse/core": ["@vueuse/core@14.2.0", "", { "dependencies": { "@types/web-bluetooth": "^0.0.21", "@vueuse/metadata": "14.2.0", "@vueuse/shared": "14.2.0" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-tpjzVl7KCQNVd/qcaCE9XbejL38V6KJAEq/tVXj7mDPtl6JtzmUdnXelSS+ULRkkrDgzYVK7EerQJvd2jR794Q=="],
|
"@vueuse/core": ["@vueuse/core@14.1.0", "", { "dependencies": { "@types/web-bluetooth": "^0.0.21", "@vueuse/metadata": "14.1.0", "@vueuse/shared": "14.1.0" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-rgBinKs07hAYyPF834mDTigH7BtPqvZ3Pryuzt1SD/lg5wEcWqvwzXXYGEDb2/cP0Sj5zSvHl3WkmMELr5kfWw=="],
|
||||||
|
|
||||||
"@vueuse/metadata": ["@vueuse/metadata@14.2.0", "", {}, "sha512-i3axTGjU8b13FtyR4Keeama+43iD+BwX9C2TmzBVKqjSHArF03hjkp2SBZ1m72Jk2UtrX0aYCugBq2R1fhkuAQ=="],
|
"@vueuse/metadata": ["@vueuse/metadata@14.1.0", "", {}, "sha512-7hK4g015rWn2PhKcZ99NyT+ZD9sbwm7SGvp7k+k+rKGWnLjS/oQozoIZzWfCewSUeBmnJkIb+CNr7Zc/EyRnnA=="],
|
||||||
|
|
||||||
"@vueuse/shared": ["@vueuse/shared@14.2.0", "", { "peerDependencies": { "vue": "^3.5.0" } }, "sha512-Z0bmluZTlAXgUcJ4uAFaML16JcD8V0QG00Db3quR642I99JXIDRa2MI2LGxiLVhcBjVnL1jOzIvT5TT2lqJlkA=="],
|
"@vueuse/shared": ["@vueuse/shared@14.1.0", "", { "peerDependencies": { "vue": "^3.5.0" } }, "sha512-EcKxtYvn6gx1F8z9J5/rsg3+lTQnvOruQd8fUecW99DCK04BkWD7z5KQ/wTAx+DazyoEE9dJt/zV8OIEQbM6kw=="],
|
||||||
|
|
||||||
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
||||||
|
|
||||||
@@ -612,7 +613,7 @@
|
|||||||
|
|
||||||
"ast-walker-scope": ["ast-walker-scope@0.8.3", "", { "dependencies": { "@babel/parser": "^7.28.4", "ast-kit": "^2.1.3" } }, "sha512-cbdCP0PGOBq0ASG+sjnKIoYkWMKhhz+F/h9pRexUdX2Hd38+WOlBkRKlqkGOSm0YQpcFMQBJeK4WspUAkwsEdg=="],
|
"ast-walker-scope": ["ast-walker-scope@0.8.3", "", { "dependencies": { "@babel/parser": "^7.28.4", "ast-kit": "^2.1.3" } }, "sha512-cbdCP0PGOBq0ASG+sjnKIoYkWMKhhz+F/h9pRexUdX2Hd38+WOlBkRKlqkGOSm0YQpcFMQBJeK4WspUAkwsEdg=="],
|
||||||
|
|
||||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.9.19", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg=="],
|
"baseline-browser-mapping": ["baseline-browser-mapping@2.9.14", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg=="],
|
||||||
|
|
||||||
"birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="],
|
"birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="],
|
||||||
|
|
||||||
@@ -624,12 +625,10 @@
|
|||||||
|
|
||||||
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
|
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
|
||||||
|
|
||||||
"caniuse-lite": ["caniuse-lite@1.0.30001767", "", {}, "sha512-34+zUAMhSH+r+9eKmYG+k2Rpt8XttfE4yXAjoZvkAPs15xcYQhyBYdalJ65BzivAvGRMViEjy6oKr/S91loekQ=="],
|
"caniuse-lite": ["caniuse-lite@1.0.30001764", "", {}, "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g=="],
|
||||||
|
|
||||||
"chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
|
"chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
|
||||||
|
|
||||||
"class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="],
|
|
||||||
|
|
||||||
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
||||||
|
|
||||||
"colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="],
|
"colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="],
|
||||||
@@ -658,9 +657,9 @@
|
|||||||
|
|
||||||
"duplexer": ["duplexer@0.1.2", "", {}, "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg=="],
|
"duplexer": ["duplexer@0.1.2", "", {}, "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg=="],
|
||||||
|
|
||||||
"electron-to-chromium": ["electron-to-chromium@1.5.286", "", {}, "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A=="],
|
"electron-to-chromium": ["electron-to-chromium@1.5.267", "", {}, "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw=="],
|
||||||
|
|
||||||
"entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
|
"entities": ["entities@7.0.0", "", {}, "sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ=="],
|
||||||
|
|
||||||
"error-stack-parser-es": ["error-stack-parser-es@1.0.5", "", {}, "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA=="],
|
"error-stack-parser-es": ["error-stack-parser-es@1.0.5", "", {}, "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA=="],
|
||||||
|
|
||||||
@@ -674,7 +673,7 @@
|
|||||||
|
|
||||||
"exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="],
|
"exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="],
|
||||||
|
|
||||||
"fast-xml-parser": ["fast-xml-parser@5.3.4", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-EFd6afGmXlCx8H8WTZHhAoDaWaGyuIBoZJ2mknrNxug+aZKjkp0a0dlars9Izl+jF+7Gu1/5f/2h68cQpe0IiA=="],
|
"fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="],
|
||||||
|
|
||||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||||
|
|
||||||
@@ -686,7 +685,7 @@
|
|||||||
|
|
||||||
"gzip-size": ["gzip-size@6.0.0", "", { "dependencies": { "duplexer": "^0.1.2" } }, "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q=="],
|
"gzip-size": ["gzip-size@6.0.0", "", { "dependencies": { "duplexer": "^0.1.2" } }, "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q=="],
|
||||||
|
|
||||||
"hono": ["hono@4.11.7", "", {}, "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw=="],
|
"hono": ["hono@4.11.4", "", {}, "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA=="],
|
||||||
|
|
||||||
"hookable": ["hookable@6.0.1", "", {}, "sha512-uKGyY8BuzN/a5gvzvA+3FVWo0+wUjgtfSdnmjtrOVwQCZPHpHDH2WRO3VZSOeluYrHoDCiXFffZXs8Dj1ULWtw=="],
|
"hookable": ["hookable@6.0.1", "", {}, "sha512-uKGyY8BuzN/a5gvzvA+3FVWo0+wUjgtfSdnmjtrOVwQCZPHpHDH2WRO3VZSOeluYrHoDCiXFffZXs8Dj1ULWtw=="],
|
||||||
|
|
||||||
@@ -714,7 +713,7 @@
|
|||||||
|
|
||||||
"mdn-data": ["mdn-data@2.12.2", "", {}, "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA=="],
|
"mdn-data": ["mdn-data@2.12.2", "", {}, "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA=="],
|
||||||
|
|
||||||
"miniflare": ["miniflare@4.20260131.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "^0.34.5", "undici": "7.18.2", "workerd": "1.20260131.0", "ws": "8.18.0", "youch": "4.1.0-beta.10" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-CtObRzlAzOUpCFH+MgImykxmDNKthrgIYtC+oLC3UGpve6bGLomKUW4u4EorTvzlQFHe66/9m/+AYbBbpzG0mQ=="],
|
"miniflare": ["miniflare@4.20260114.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "^0.34.5", "undici": "7.14.0", "workerd": "1.20260114.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "^3.25.76" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-QwHT7S6XqGdQxIvql1uirH/7/i3zDEt0B/YBXTYzMfJtVCR4+ue3KPkU+Bl0zMxvpgkvjh9+eCHhJbKEqya70A=="],
|
||||||
|
|
||||||
"mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="],
|
"mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="],
|
||||||
|
|
||||||
@@ -742,7 +741,7 @@
|
|||||||
|
|
||||||
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||||
|
|
||||||
"perfect-debounce": ["perfect-debounce@2.1.0", "", {}, "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g=="],
|
"perfect-debounce": ["perfect-debounce@2.0.0", "", {}, "sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow=="],
|
||||||
|
|
||||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||||
|
|
||||||
@@ -760,7 +759,7 @@
|
|||||||
|
|
||||||
"rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
|
"rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
|
||||||
|
|
||||||
"rollup": ["rollup@4.57.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.1", "@rollup/rollup-android-arm64": "4.57.1", "@rollup/rollup-darwin-arm64": "4.57.1", "@rollup/rollup-darwin-x64": "4.57.1", "@rollup/rollup-freebsd-arm64": "4.57.1", "@rollup/rollup-freebsd-x64": "4.57.1", "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", "@rollup/rollup-linux-arm-musleabihf": "4.57.1", "@rollup/rollup-linux-arm64-gnu": "4.57.1", "@rollup/rollup-linux-arm64-musl": "4.57.1", "@rollup/rollup-linux-loong64-gnu": "4.57.1", "@rollup/rollup-linux-loong64-musl": "4.57.1", "@rollup/rollup-linux-ppc64-gnu": "4.57.1", "@rollup/rollup-linux-ppc64-musl": "4.57.1", "@rollup/rollup-linux-riscv64-gnu": "4.57.1", "@rollup/rollup-linux-riscv64-musl": "4.57.1", "@rollup/rollup-linux-s390x-gnu": "4.57.1", "@rollup/rollup-linux-x64-gnu": "4.57.1", "@rollup/rollup-linux-x64-musl": "4.57.1", "@rollup/rollup-openbsd-x64": "4.57.1", "@rollup/rollup-openharmony-arm64": "4.57.1", "@rollup/rollup-win32-arm64-msvc": "4.57.1", "@rollup/rollup-win32-ia32-msvc": "4.57.1", "@rollup/rollup-win32-x64-gnu": "4.57.1", "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A=="],
|
"rollup": ["rollup@4.55.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.55.1", "@rollup/rollup-android-arm64": "4.55.1", "@rollup/rollup-darwin-arm64": "4.55.1", "@rollup/rollup-darwin-x64": "4.55.1", "@rollup/rollup-freebsd-arm64": "4.55.1", "@rollup/rollup-freebsd-x64": "4.55.1", "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", "@rollup/rollup-linux-arm-musleabihf": "4.55.1", "@rollup/rollup-linux-arm64-gnu": "4.55.1", "@rollup/rollup-linux-arm64-musl": "4.55.1", "@rollup/rollup-linux-loong64-gnu": "4.55.1", "@rollup/rollup-linux-loong64-musl": "4.55.1", "@rollup/rollup-linux-ppc64-gnu": "4.55.1", "@rollup/rollup-linux-ppc64-musl": "4.55.1", "@rollup/rollup-linux-riscv64-gnu": "4.55.1", "@rollup/rollup-linux-riscv64-musl": "4.55.1", "@rollup/rollup-linux-s390x-gnu": "4.55.1", "@rollup/rollup-linux-x64-gnu": "4.55.1", "@rollup/rollup-linux-x64-musl": "4.55.1", "@rollup/rollup-openbsd-x64": "4.55.1", "@rollup/rollup-openharmony-arm64": "4.55.1", "@rollup/rollup-win32-arm64-msvc": "4.55.1", "@rollup/rollup-win32-ia32-msvc": "4.55.1", "@rollup/rollup-win32-x64-gnu": "4.55.1", "@rollup/rollup-win32-x64-msvc": "4.55.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A=="],
|
||||||
|
|
||||||
"scule": ["scule@1.3.0", "", {}, "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g=="],
|
"scule": ["scule@1.3.0", "", {}, "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g=="],
|
||||||
|
|
||||||
@@ -798,7 +797,7 @@
|
|||||||
|
|
||||||
"unconfig-core": ["unconfig-core@7.4.2", "", { "dependencies": { "@quansync/fs": "^1.0.0", "quansync": "^1.0.0" } }, "sha512-VgPCvLWugINbXvMQDf8Jh0mlbvNjNC6eSUziHsBCMpxR05OPrNrvDnyatdMjRgcHaaNsCqz+wjNXxNw1kRLHUg=="],
|
"unconfig-core": ["unconfig-core@7.4.2", "", { "dependencies": { "@quansync/fs": "^1.0.0", "quansync": "^1.0.0" } }, "sha512-VgPCvLWugINbXvMQDf8Jh0mlbvNjNC6eSUziHsBCMpxR05OPrNrvDnyatdMjRgcHaaNsCqz+wjNXxNw1kRLHUg=="],
|
||||||
|
|
||||||
"undici": ["undici@7.18.2", "", {}, "sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw=="],
|
"undici": ["undici@7.14.0", "", {}, "sha512-Vqs8HTzjpQXZeXdpsfChQTlafcMQaaIwnGwLam1wudSSjlJeQ3bw1j+TLPePgrCnCpUXx7Ba5Pdpf5OBih62NQ=="],
|
||||||
|
|
||||||
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||||
|
|
||||||
@@ -834,9 +833,9 @@
|
|||||||
|
|
||||||
"webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],
|
"webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],
|
||||||
|
|
||||||
"workerd": ["workerd@1.20260131.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260131.0", "@cloudflare/workerd-darwin-arm64": "1.20260131.0", "@cloudflare/workerd-linux-64": "1.20260131.0", "@cloudflare/workerd-linux-arm64": "1.20260131.0", "@cloudflare/workerd-windows-64": "1.20260131.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-4zZxOdWeActbRfydQQlj7vZ2ay01AjjNC4K3stjmWC3xZHeXeN3EAROwsWE83SZHhtw4rn18srrhtXoQvQMw3Q=="],
|
"workerd": ["workerd@1.20260114.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260114.0", "@cloudflare/workerd-darwin-arm64": "1.20260114.0", "@cloudflare/workerd-linux-64": "1.20260114.0", "@cloudflare/workerd-linux-arm64": "1.20260114.0", "@cloudflare/workerd-windows-64": "1.20260114.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-kTJ+jNdIllOzWuVA3NRQRvywP0T135zdCjAE2dAUY1BFbxM6fmMZV8BbskEoQ4hAODVQUfZQmyGctcwvVCKxFA=="],
|
||||||
|
|
||||||
"wrangler": ["wrangler@4.62.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.2", "@cloudflare/unenv-preset": "2.12.0", "blake3-wasm": "2.1.5", "esbuild": "0.27.0", "miniflare": "4.20260131.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20260131.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20260131.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-DogP9jifqw85g33BqwF6m21YBW5J7+Ep9IJLgr6oqHU0RkA79JMN5baeWXdmnIWZl+VZh6bmtNtR+5/Djd32tg=="],
|
"wrangler": ["wrangler@4.59.2", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.2", "@cloudflare/unenv-preset": "2.10.0", "blake3-wasm": "2.1.5", "esbuild": "0.27.0", "miniflare": "4.20260114.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20260114.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20260114.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-Z4xn6jFZTaugcOKz42xvRAYKgkVUERHVbuCJ5+f+gK+R6k12L02unakPGOA0L0ejhUl16dqDjKe4tmL9sedHcw=="],
|
||||||
|
|
||||||
"ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="],
|
"ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="],
|
||||||
|
|
||||||
@@ -848,7 +847,7 @@
|
|||||||
|
|
||||||
"youch-core": ["youch-core@0.3.3", "", { "dependencies": { "@poppinss/exception": "^1.2.2", "error-stack-parser-es": "^1.0.5" } }, "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA=="],
|
"youch-core": ["youch-core@0.3.3", "", { "dependencies": { "@poppinss/exception": "^1.2.2", "error-stack-parser-es": "^1.0.5" } }, "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA=="],
|
||||||
|
|
||||||
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
"zod": ["zod@4.3.5", "", {}, "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g=="],
|
||||||
|
|
||||||
"@aws-crypto/sha1-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
|
"@aws-crypto/sha1-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
|
||||||
|
|
||||||
@@ -856,22 +855,20 @@
|
|||||||
|
|
||||||
"@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
|
"@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
|
||||||
|
|
||||||
"@aws-sdk/client-sso/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.982.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ=="],
|
|
||||||
|
|
||||||
"@aws-sdk/middleware-user-agent/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.982.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ=="],
|
|
||||||
|
|
||||||
"@aws-sdk/nested-clients/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.982.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ=="],
|
|
||||||
|
|
||||||
"@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="],
|
"@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="],
|
||||||
|
|
||||||
"@quansync/fs/quansync": ["quansync@1.0.0", "", {}, "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA=="],
|
"@quansync/fs/quansync": ["quansync@1.0.0", "", {}, "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA=="],
|
||||||
|
|
||||||
"@tanstack/zod-form-adapter/@tanstack/form-core": ["@tanstack/form-core@0.42.1", "", { "dependencies": { "@tanstack/store": "^0.7.0" } }, "sha512-jTU0jyHqFceujdtPNv3jPVej1dTqBwa8TYdIyWB5BCwRVUBZEp1PiYEBkC9r92xu5fMpBiKc+JKud3eeVjuMiA=="],
|
|
||||||
|
|
||||||
"@unocss/transformer-attributify-jsx/@babel/parser": ["@babel/parser@7.27.7", "", { "dependencies": { "@babel/types": "^7.27.7" }, "bin": "./bin/babel-parser.js" }, "sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q=="],
|
"@unocss/transformer-attributify-jsx/@babel/parser": ["@babel/parser@7.27.7", "", { "dependencies": { "@babel/types": "^7.27.7" }, "bin": "./bin/babel-parser.js" }, "sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q=="],
|
||||||
|
|
||||||
"@unocss/transformer-attributify-jsx/@babel/traverse": ["@babel/traverse@7.27.7", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.27.5", "@babel/parser": "^7.27.7", "@babel/template": "^7.27.2", "@babel/types": "^7.27.7", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-X6ZlfR/O/s5EQ/SnUSLzr+6kGnkg8HXGMzpgsMsrJVcfDtH1vIp6ctCN4eZ1LS5c0+te5Cb6Y514fASjMRJ1nw=="],
|
"@unocss/transformer-attributify-jsx/@babel/traverse": ["@babel/traverse@7.27.7", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.27.5", "@babel/parser": "^7.27.7", "@babel/template": "^7.27.2", "@babel/types": "^7.27.7", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-X6ZlfR/O/s5EQ/SnUSLzr+6kGnkg8HXGMzpgsMsrJVcfDtH1vIp6ctCN4eZ1LS5c0+te5Cb6Y514fASjMRJ1nw=="],
|
||||||
|
|
||||||
|
"@vitejs/plugin-vue-jsx/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.9-commit.d91dfb5", "", {}, "sha512-8sExkWRK+zVybw3+2/kBkYBFeLnEUWz1fT7BLHplpzmtqkOfTbAQ9gkt4pzwGIIZmg4Qn5US5ACjUBenrhezwQ=="],
|
||||||
|
|
||||||
|
"@vue/babel-plugin-jsx/@vue/shared": ["@vue/shared@3.5.26", "", {}, "sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A=="],
|
||||||
|
|
||||||
|
"@vue/babel-plugin-resolve-type/@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.26", "", { "dependencies": { "@babel/parser": "^7.28.5", "@vue/compiler-core": "3.5.26", "@vue/compiler-dom": "3.5.26", "@vue/compiler-ssr": "3.5.26", "@vue/shared": "3.5.26", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.6", "source-map-js": "^1.2.1" } }, "sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA=="],
|
||||||
|
|
||||||
"@vue/compiler-core/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
"@vue/compiler-core/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||||
|
|
||||||
"@vue/compiler-sfc/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
"@vue/compiler-sfc/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||||
@@ -880,6 +877,8 @@
|
|||||||
|
|
||||||
"@vue/devtools-kit/perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="],
|
"@vue/devtools-kit/perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="],
|
||||||
|
|
||||||
|
"miniflare/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||||
|
|
||||||
"mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
|
"mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
|
||||||
|
|
||||||
"sharp/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
|
"sharp/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
|
||||||
@@ -902,7 +901,17 @@
|
|||||||
|
|
||||||
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
|
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
|
||||||
|
|
||||||
"@unocss/transformer-attributify-jsx/@babel/traverse/@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
|
"@unocss/transformer-attributify-jsx/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="],
|
||||||
|
|
||||||
|
"@vue/babel-plugin-resolve-type/@vue/compiler-sfc/@vue/compiler-core": ["@vue/compiler-core@3.5.26", "", { "dependencies": { "@babel/parser": "^7.28.5", "@vue/shared": "3.5.26", "entities": "^7.0.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w=="],
|
||||||
|
|
||||||
|
"@vue/babel-plugin-resolve-type/@vue/compiler-sfc/@vue/compiler-dom": ["@vue/compiler-dom@3.5.26", "", { "dependencies": { "@vue/compiler-core": "3.5.26", "@vue/shared": "3.5.26" } }, "sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A=="],
|
||||||
|
|
||||||
|
"@vue/babel-plugin-resolve-type/@vue/compiler-sfc/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.26", "", { "dependencies": { "@vue/compiler-dom": "3.5.26", "@vue/shared": "3.5.26" } }, "sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw=="],
|
||||||
|
|
||||||
|
"@vue/babel-plugin-resolve-type/@vue/compiler-sfc/@vue/shared": ["@vue/shared@3.5.26", "", {}, "sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A=="],
|
||||||
|
|
||||||
|
"@vue/babel-plugin-resolve-type/@vue/compiler-sfc/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||||
|
|
||||||
"mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
|
"mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
|
||||||
|
|
||||||
|
|||||||
60
components.d.ts
vendored
60
components.d.ts
vendored
@@ -16,12 +16,12 @@ declare module 'vue' {
|
|||||||
AlertTriangleIcon: typeof import('./src/components/icons/AlertTriangleIcon.vue')['default']
|
AlertTriangleIcon: typeof import('./src/components/icons/AlertTriangleIcon.vue')['default']
|
||||||
ArrowDownTray: typeof import('./src/components/icons/ArrowDownTray.vue')['default']
|
ArrowDownTray: typeof import('./src/components/icons/ArrowDownTray.vue')['default']
|
||||||
ArrowRightIcon: typeof import('./src/components/icons/ArrowRightIcon.vue')['default']
|
ArrowRightIcon: typeof import('./src/components/icons/ArrowRightIcon.vue')['default']
|
||||||
Avatar: typeof import('./src/components/ui/form/Avatar.vue')['default']
|
Avatar: typeof import('./src/components/ui/Avatar.vue')['default']
|
||||||
Bell: typeof import('./src/components/icons/Bell.vue')['default']
|
Bell: typeof import('./src/components/icons/Bell.vue')['default']
|
||||||
Button: typeof import('./src/components/ui/form/Button.vue')['default']
|
Button: typeof import('./src/components/ui/Button.vue')['default']
|
||||||
Card: typeof import('./src/components/ui/form/Card.vue')['default']
|
Card: typeof import('./src/components/ui/Card.vue')['default']
|
||||||
Chart: typeof import('./src/components/icons/Chart.vue')['default']
|
Chart: typeof import('./src/components/icons/Chart.vue')['default']
|
||||||
Checkbox: typeof import('./src/components/ui/form/Checkbox.vue')['default']
|
Checkbox: typeof import('./src/components/ui/Checkbox.vue')['default']
|
||||||
CheckCircleIcon: typeof import('./src/components/icons/CheckCircleIcon.vue')['default']
|
CheckCircleIcon: typeof import('./src/components/icons/CheckCircleIcon.vue')['default']
|
||||||
CheckIcon: typeof import('./src/components/icons/CheckIcon.vue')['default']
|
CheckIcon: typeof import('./src/components/icons/CheckIcon.vue')['default']
|
||||||
CheckMarkIcon: typeof import('./src/components/icons/CheckMarkIcon.vue')['default']
|
CheckMarkIcon: typeof import('./src/components/icons/CheckMarkIcon.vue')['default']
|
||||||
@@ -30,33 +30,33 @@ declare module 'vue' {
|
|||||||
CreditCardIcon: typeof import('./src/components/icons/CreditCardIcon.vue')['default']
|
CreditCardIcon: typeof import('./src/components/icons/CreditCardIcon.vue')['default']
|
||||||
DashboardLayout: typeof import('./src/components/DashboardLayout.vue')['default']
|
DashboardLayout: typeof import('./src/components/DashboardLayout.vue')['default']
|
||||||
DashboardNav: typeof import('./src/components/DashboardNav.vue')['default']
|
DashboardNav: typeof import('./src/components/DashboardNav.vue')['default']
|
||||||
Dialog: typeof import('./src/components/ui/form/Dialog.vue')['default']
|
DataTable: typeof import('./src/components/table/DataTable.vue')['default']
|
||||||
|
Dialog: typeof import('./src/components/ui/Dialog.vue')['default']
|
||||||
EmptyState: typeof import('./src/components/dashboard/EmptyState.vue')['default']
|
EmptyState: typeof import('./src/components/dashboard/EmptyState.vue')['default']
|
||||||
Field: typeof import('./src/components/ui/form/Field.vue')['default']
|
Field: typeof import('./src/components/form/Field.vue')['default']
|
||||||
Form: typeof import('./src/components/ui/form/Form.vue')['default']
|
Form: typeof import('./src/components/form/Form.vue')['default']
|
||||||
GlobalUploadIndicator: typeof import('./src/components/GlobalUploadIndicator.vue')['default']
|
GlobalUploadIndicator: typeof import('./src/components/GlobalUploadIndicator.vue')['default']
|
||||||
HardDriveUpload: typeof import('./src/components/icons/HardDriveUpload.vue')['default']
|
HardDriveUpload: typeof import('./src/components/icons/HardDriveUpload.vue')['default']
|
||||||
Home: typeof import('./src/components/icons/Home.vue')['default']
|
Home: typeof import('./src/components/icons/Home.vue')['default']
|
||||||
InfoIcon: typeof import('./src/components/icons/InfoIcon.vue')['default']
|
InfoIcon: typeof import('./src/components/icons/InfoIcon.vue')['default']
|
||||||
Input: typeof import('./src/components/ui/form/Input.vue')['default']
|
Input: typeof import('./src/components/ui/Input.vue')['default']
|
||||||
|
InputPassword: typeof import('./src/components/ui/InputPassword.vue')['default']
|
||||||
Layout: typeof import('./src/components/icons/Layout.vue')['default']
|
Layout: typeof import('./src/components/icons/Layout.vue')['default']
|
||||||
LinkIcon: typeof import('./src/components/icons/LinkIcon.vue')['default']
|
LinkIcon: typeof import('./src/components/icons/LinkIcon.vue')['default']
|
||||||
|
Message: typeof import('./src/components/form/Message.vue')['default']
|
||||||
NotificationDrawer: typeof import('./src/components/NotificationDrawer.vue')['default']
|
NotificationDrawer: typeof import('./src/components/NotificationDrawer.vue')['default']
|
||||||
PageHeader: typeof import('./src/components/dashboard/PageHeader.vue')['default']
|
PageHeader: typeof import('./src/components/dashboard/PageHeader.vue')['default']
|
||||||
PanelLeft: typeof import('./src/components/icons/PanelLeft.vue')['default']
|
PanelLeft: typeof import('./src/components/icons/PanelLeft.vue')['default']
|
||||||
ProgressBar: typeof import('./src/components/ui/form/ProgressBar.vue')['default']
|
ProgressBar: typeof import('./src/components/ui/ProgressBar.vue')['default']
|
||||||
RootLayout: typeof import('./src/components/RootLayout.vue')['default']
|
RootLayout: typeof import('./src/components/RootLayout.vue')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
SettingsIcon: typeof import('./src/components/icons/SettingsIcon.vue')['default']
|
SettingsIcon: typeof import('./src/components/icons/SettingsIcon.vue')['default']
|
||||||
Skeleton: typeof import('./src/components/ui/form/Skeleton.vue')['default']
|
Skeleton: typeof import('./src/components/ui/Skeleton.vue')['default']
|
||||||
StatsCard: typeof import('./src/components/dashboard/StatsCard.vue')['default']
|
StatsCard: typeof import('./src/components/dashboard/StatsCard.vue')['default']
|
||||||
Table: typeof import('./src/components/ui/form/Table.vue')['default']
|
Tag: typeof import('./src/components/ui/Tag.vue')['default']
|
||||||
Tag: typeof import('./src/components/ui/form/Tag.vue')['default']
|
|
||||||
TanStackForm: typeof import('./src/components/ui/form/TanStackForm.vue')['default']
|
|
||||||
TestIcon: typeof import('./src/components/icons/TestIcon.vue')['default']
|
TestIcon: typeof import('./src/components/icons/TestIcon.vue')['default']
|
||||||
Textarea: typeof import('./src/components/ui/form/Textarea.vue')['default']
|
Toast: typeof import('./src/components/ui/Toast.vue')['default']
|
||||||
Toast: typeof import('./src/components/ui/form/Toast.vue')['default']
|
|
||||||
TrashIcon: typeof import('./src/components/icons/TrashIcon.vue')['default']
|
TrashIcon: typeof import('./src/components/icons/TrashIcon.vue')['default']
|
||||||
Upload: typeof import('./src/components/icons/Upload.vue')['default']
|
Upload: typeof import('./src/components/icons/Upload.vue')['default']
|
||||||
Video: typeof import('./src/components/icons/Video.vue')['default']
|
Video: typeof import('./src/components/icons/Video.vue')['default']
|
||||||
@@ -72,12 +72,12 @@ declare global {
|
|||||||
const AlertTriangleIcon: typeof import('./src/components/icons/AlertTriangleIcon.vue')['default']
|
const AlertTriangleIcon: typeof import('./src/components/icons/AlertTriangleIcon.vue')['default']
|
||||||
const ArrowDownTray: typeof import('./src/components/icons/ArrowDownTray.vue')['default']
|
const ArrowDownTray: typeof import('./src/components/icons/ArrowDownTray.vue')['default']
|
||||||
const ArrowRightIcon: typeof import('./src/components/icons/ArrowRightIcon.vue')['default']
|
const ArrowRightIcon: typeof import('./src/components/icons/ArrowRightIcon.vue')['default']
|
||||||
const Avatar: typeof import('./src/components/ui/form/Avatar.vue')['default']
|
const Avatar: typeof import('./src/components/ui/Avatar.vue')['default']
|
||||||
const Bell: typeof import('./src/components/icons/Bell.vue')['default']
|
const Bell: typeof import('./src/components/icons/Bell.vue')['default']
|
||||||
const Button: typeof import('./src/components/ui/form/Button.vue')['default']
|
const Button: typeof import('./src/components/ui/Button.vue')['default']
|
||||||
const Card: typeof import('./src/components/ui/form/Card.vue')['default']
|
const Card: typeof import('./src/components/ui/Card.vue')['default']
|
||||||
const Chart: typeof import('./src/components/icons/Chart.vue')['default']
|
const Chart: typeof import('./src/components/icons/Chart.vue')['default']
|
||||||
const Checkbox: typeof import('./src/components/ui/form/Checkbox.vue')['default']
|
const Checkbox: typeof import('./src/components/ui/Checkbox.vue')['default']
|
||||||
const CheckCircleIcon: typeof import('./src/components/icons/CheckCircleIcon.vue')['default']
|
const CheckCircleIcon: typeof import('./src/components/icons/CheckCircleIcon.vue')['default']
|
||||||
const CheckIcon: typeof import('./src/components/icons/CheckIcon.vue')['default']
|
const CheckIcon: typeof import('./src/components/icons/CheckIcon.vue')['default']
|
||||||
const CheckMarkIcon: typeof import('./src/components/icons/CheckMarkIcon.vue')['default']
|
const CheckMarkIcon: typeof import('./src/components/icons/CheckMarkIcon.vue')['default']
|
||||||
@@ -86,33 +86,33 @@ declare global {
|
|||||||
const CreditCardIcon: typeof import('./src/components/icons/CreditCardIcon.vue')['default']
|
const CreditCardIcon: typeof import('./src/components/icons/CreditCardIcon.vue')['default']
|
||||||
const DashboardLayout: typeof import('./src/components/DashboardLayout.vue')['default']
|
const DashboardLayout: typeof import('./src/components/DashboardLayout.vue')['default']
|
||||||
const DashboardNav: typeof import('./src/components/DashboardNav.vue')['default']
|
const DashboardNav: typeof import('./src/components/DashboardNav.vue')['default']
|
||||||
const Dialog: typeof import('./src/components/ui/form/Dialog.vue')['default']
|
const DataTable: typeof import('./src/components/table/DataTable.vue')['default']
|
||||||
|
const Dialog: typeof import('./src/components/ui/Dialog.vue')['default']
|
||||||
const EmptyState: typeof import('./src/components/dashboard/EmptyState.vue')['default']
|
const EmptyState: typeof import('./src/components/dashboard/EmptyState.vue')['default']
|
||||||
const Field: typeof import('./src/components/ui/form/Field.vue')['default']
|
const Field: typeof import('./src/components/form/Field.vue')['default']
|
||||||
const Form: typeof import('./src/components/ui/form/Form.vue')['default']
|
const Form: typeof import('./src/components/form/Form.vue')['default']
|
||||||
const GlobalUploadIndicator: typeof import('./src/components/GlobalUploadIndicator.vue')['default']
|
const GlobalUploadIndicator: typeof import('./src/components/GlobalUploadIndicator.vue')['default']
|
||||||
const HardDriveUpload: typeof import('./src/components/icons/HardDriveUpload.vue')['default']
|
const HardDriveUpload: typeof import('./src/components/icons/HardDriveUpload.vue')['default']
|
||||||
const Home: typeof import('./src/components/icons/Home.vue')['default']
|
const Home: typeof import('./src/components/icons/Home.vue')['default']
|
||||||
const InfoIcon: typeof import('./src/components/icons/InfoIcon.vue')['default']
|
const InfoIcon: typeof import('./src/components/icons/InfoIcon.vue')['default']
|
||||||
const Input: typeof import('./src/components/ui/form/Input.vue')['default']
|
const Input: typeof import('./src/components/ui/Input.vue')['default']
|
||||||
|
const InputPassword: typeof import('./src/components/ui/InputPassword.vue')['default']
|
||||||
const Layout: typeof import('./src/components/icons/Layout.vue')['default']
|
const Layout: typeof import('./src/components/icons/Layout.vue')['default']
|
||||||
const LinkIcon: typeof import('./src/components/icons/LinkIcon.vue')['default']
|
const LinkIcon: typeof import('./src/components/icons/LinkIcon.vue')['default']
|
||||||
|
const Message: typeof import('./src/components/form/Message.vue')['default']
|
||||||
const NotificationDrawer: typeof import('./src/components/NotificationDrawer.vue')['default']
|
const NotificationDrawer: typeof import('./src/components/NotificationDrawer.vue')['default']
|
||||||
const PageHeader: typeof import('./src/components/dashboard/PageHeader.vue')['default']
|
const PageHeader: typeof import('./src/components/dashboard/PageHeader.vue')['default']
|
||||||
const PanelLeft: typeof import('./src/components/icons/PanelLeft.vue')['default']
|
const PanelLeft: typeof import('./src/components/icons/PanelLeft.vue')['default']
|
||||||
const ProgressBar: typeof import('./src/components/ui/form/ProgressBar.vue')['default']
|
const ProgressBar: typeof import('./src/components/ui/ProgressBar.vue')['default']
|
||||||
const RootLayout: typeof import('./src/components/RootLayout.vue')['default']
|
const RootLayout: typeof import('./src/components/RootLayout.vue')['default']
|
||||||
const RouterLink: typeof import('vue-router')['RouterLink']
|
const RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
const RouterView: typeof import('vue-router')['RouterView']
|
const RouterView: typeof import('vue-router')['RouterView']
|
||||||
const SettingsIcon: typeof import('./src/components/icons/SettingsIcon.vue')['default']
|
const SettingsIcon: typeof import('./src/components/icons/SettingsIcon.vue')['default']
|
||||||
const Skeleton: typeof import('./src/components/ui/form/Skeleton.vue')['default']
|
const Skeleton: typeof import('./src/components/ui/Skeleton.vue')['default']
|
||||||
const StatsCard: typeof import('./src/components/dashboard/StatsCard.vue')['default']
|
const StatsCard: typeof import('./src/components/dashboard/StatsCard.vue')['default']
|
||||||
const Table: typeof import('./src/components/ui/form/Table.vue')['default']
|
const Tag: typeof import('./src/components/ui/Tag.vue')['default']
|
||||||
const Tag: typeof import('./src/components/ui/form/Tag.vue')['default']
|
|
||||||
const TanStackForm: typeof import('./src/components/ui/form/TanStackForm.vue')['default']
|
|
||||||
const TestIcon: typeof import('./src/components/icons/TestIcon.vue')['default']
|
const TestIcon: typeof import('./src/components/icons/TestIcon.vue')['default']
|
||||||
const Textarea: typeof import('./src/components/ui/form/Textarea.vue')['default']
|
const Toast: typeof import('./src/components/ui/Toast.vue')['default']
|
||||||
const Toast: typeof import('./src/components/ui/form/Toast.vue')['default']
|
|
||||||
const TrashIcon: typeof import('./src/components/icons/TrashIcon.vue')['default']
|
const TrashIcon: typeof import('./src/components/icons/TrashIcon.vue')['default']
|
||||||
const Upload: typeof import('./src/components/icons/Upload.vue')['default']
|
const Upload: typeof import('./src/components/icons/Upload.vue')['default']
|
||||||
const Video: typeof import('./src/components/icons/Video.vue')['default']
|
const Video: typeof import('./src/components/icons/Video.vue')['default']
|
||||||
|
|||||||
25
package.json
25
package.json
@@ -10,35 +10,34 @@
|
|||||||
"tail": "wrangler tail"
|
"tail": "wrangler tail"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.983.0",
|
"@aws-sdk/client-s3": "^3.971.0",
|
||||||
"@aws-sdk/s3-presigned-post": "^3.983.0",
|
"@aws-sdk/s3-presigned-post": "^3.971.0",
|
||||||
"@aws-sdk/s3-request-presigner": "^3.983.0",
|
"@aws-sdk/s3-request-presigner": "^3.971.0",
|
||||||
|
"@hiogawa/tiny-rpc": "^0.2.3-pre.18",
|
||||||
"@hiogawa/utils": "^1.7.0",
|
"@hiogawa/utils": "^1.7.0",
|
||||||
"@pinia/colada": "^0.21.2",
|
|
||||||
"@tanstack/vue-form": "^1.28.0",
|
"@tanstack/vue-form": "^1.28.0",
|
||||||
"@tanstack/vue-table": "^8.21.3",
|
"@tanstack/vue-table": "^8.21.3",
|
||||||
"@unhead/vue": "^2.1.2",
|
"@unhead/vue": "^2.1.2",
|
||||||
"@vueuse/core": "^14.2.0",
|
"@vueuse/core": "^14.1.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"hono": "^4.11.7",
|
"hono": "^4.11.4",
|
||||||
"is-mobile": "^5.0.0",
|
"is-mobile": "^5.0.0",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"vue": "^3.5.27",
|
"vue": "^3.5.27",
|
||||||
"vue-router": "^5.0.2",
|
"vue-router": "^5.0.2",
|
||||||
"zod": "^3.25.76"
|
"zod": "^4.3.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cloudflare/vite-plugin": "^1.23.0",
|
"@cloudflare/vite-plugin": "^1.21.0",
|
||||||
"@types/node": "^25.2.0",
|
"@types/node": "^25.0.9",
|
||||||
"@vitejs/plugin-vue": "^6.0.4",
|
"@vitejs/plugin-vue": "^6.0.3",
|
||||||
"@vitejs/plugin-vue-jsx": "^5.1.4",
|
"@vitejs/plugin-vue-jsx": "^5.1.3",
|
||||||
"unocss": "^66.6.0",
|
"unocss": "^66.6.0",
|
||||||
"unplugin-auto-import": "^21.0.0",
|
"unplugin-auto-import": "^21.0.0",
|
||||||
"unplugin-vue-components": "^31.0.0",
|
"unplugin-vue-components": "^31.0.0",
|
||||||
"vite": "^7.3.1",
|
"vite": "^7.3.1",
|
||||||
"vite-ssr-components": "^0.5.2",
|
"vite-ssr-components": "^0.5.2",
|
||||||
"wrangler": "^4.62.0"
|
"wrangler": "^4.59.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import Bell from "@/components/icons/Bell.vue";
|
import Bell from "@/components/icons/Bell.vue";
|
||||||
import Home from "@/components/icons/Home.vue";
|
|
||||||
import Video from "@/components/icons/Video.vue";
|
|
||||||
import Credit from "@/components/icons/Credit.vue";
|
import Credit from "@/components/icons/Credit.vue";
|
||||||
|
import Home from "@/components/icons/Home.vue";
|
||||||
import Upload from "@/components/icons/Upload.vue";
|
import Upload from "@/components/icons/Upload.vue";
|
||||||
import NotificationDrawer from "./NotificationDrawer.vue";
|
import Video from "@/components/icons/Video.vue";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { createStaticVNode, ref } from "vue";
|
import { createStaticVNode, ref } from "vue";
|
||||||
|
import NotificationDrawer from "./NotificationDrawer.vue";
|
||||||
|
|
||||||
const className = ":uno: w-12 h-12 p-2 rounded-2xl hover:bg-primary/15 flex press-animated items-center justify-center shrink-0";
|
const className = ":uno: w-12 h-12 p-2 rounded-2xl hover:bg-primary/15 flex press-animated items-center justify-center shrink-0";
|
||||||
const homeHoist = createStaticVNode(`<img class="h-8 w-8" src="/apple-touch-icon.png" alt="Logo" />`, 1);
|
const homeHoist = createStaticVNode(`<img class="h-8 w-8" src="/apple-touch-icon.png" alt="Logo" />`, 1);
|
||||||
@@ -40,7 +40,7 @@ const links = [
|
|||||||
|
|
||||||
<template v-for="i in links" :key="i.label">
|
<template v-for="i in links" :key="i.label">
|
||||||
<component :name="i.label" :is="i.type === 'a' ? 'router-link' : 'div'"
|
<component :name="i.label" :is="i.type === 'a' ? 'router-link' : 'div'"
|
||||||
v-bind="i.type === 'a' ? { to: i.href } : {}" v-tooltip="i.label" @click="i.action && i.action($event)"
|
v-bind="i.type === 'a' ? { to: i.href } : {}" :title="i.label" @click="i.action && i.action($event)"
|
||||||
:class="cn(
|
:class="cn(
|
||||||
i.className,
|
i.className,
|
||||||
($route.path === i.href || i.isActive?.value) && 'bg-primary/15'
|
($route.path === i.href || i.isActive?.value) && 'bg-primary/15'
|
||||||
|
|||||||
@@ -1,9 +1,3 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import Toast from './ui/form/Toast.vue';
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Toast />
|
|
||||||
<router-view/>
|
<router-view/>
|
||||||
</template>
|
</template>
|
||||||
22
src/components/form/Field.vue
Normal file
22
src/components/form/Field.vue
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useField } from '@tanstack/vue-form';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
name: string
|
||||||
|
form?: any
|
||||||
|
class?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const field = useField({
|
||||||
|
name: props.name,
|
||||||
|
form: props.form
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="props.class">
|
||||||
|
<slot :field="field" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
55
src/components/form/Form.vue
Normal file
55
src/components/form/Form.vue
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useForm } from '@tanstack/vue-form'
|
||||||
|
import { type ZodType } from 'zod'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
initialValues?: Record<string, any>
|
||||||
|
onSubmit?: (values: any) => void | Promise<void>
|
||||||
|
resolver?: ZodType<any>
|
||||||
|
class?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
const emit = defineEmits<{
|
||||||
|
submit: [values: any]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const form = useForm({
|
||||||
|
defaultValues: props.initialValues || {},
|
||||||
|
onSubmit: async ({ value }) => {
|
||||||
|
if (props.onSubmit) {
|
||||||
|
await props.onSubmit(value as Record<string, any>)
|
||||||
|
}
|
||||||
|
emit('submit', value)
|
||||||
|
},
|
||||||
|
validators: props.resolver
|
||||||
|
? {
|
||||||
|
onChange: ({ value }) => {
|
||||||
|
const result = props.resolver!.safeParse(value)
|
||||||
|
if (result.success) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
return result.error.issues.map(issue => ({
|
||||||
|
path: issue.path.join('.'),
|
||||||
|
message: issue.message
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleSubmit = (e: Event) => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
form.handleSubmit()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<form
|
||||||
|
:class="props.class"
|
||||||
|
@submit="handleSubmit"
|
||||||
|
>
|
||||||
|
<slot :form="form" />
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
36
src/components/form/Message.vue
Normal file
36
src/components/form/Message.vue
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
interface Props {
|
||||||
|
severity?: 'error' | 'success' | 'info' | 'warn'
|
||||||
|
size?: 'sm' | 'md'
|
||||||
|
class?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
severity: 'error',
|
||||||
|
size: 'sm'
|
||||||
|
})
|
||||||
|
|
||||||
|
const severityClasses = {
|
||||||
|
error: 'text-red-600',
|
||||||
|
success: 'text-green-600',
|
||||||
|
info: 'text-blue-600',
|
||||||
|
warn: 'text-yellow-600'
|
||||||
|
}
|
||||||
|
|
||||||
|
const sizeClasses = {
|
||||||
|
sm: 'text-xs',
|
||||||
|
md: 'text-sm'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<span
|
||||||
|
:class="[
|
||||||
|
severityClasses[severity],
|
||||||
|
sizeClasses[size],
|
||||||
|
props.class
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
36
src/components/table/Column.ts
Normal file
36
src/components/table/Column.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { createColumnHelper, type ColumnDef } from '@tanstack/vue-table'
|
||||||
|
|
||||||
|
export { createColumnHelper }
|
||||||
|
export type { ColumnDef }
|
||||||
|
|
||||||
|
// Helper function to create a simple column
|
||||||
|
export function createColumn<T>(
|
||||||
|
accessorKey: keyof T,
|
||||||
|
header: string,
|
||||||
|
options?: {
|
||||||
|
cell?: (value: any, row: T) => any
|
||||||
|
enableSorting?: boolean
|
||||||
|
size?: number
|
||||||
|
}
|
||||||
|
): ColumnDef<T, any> {
|
||||||
|
return {
|
||||||
|
accessorKey: accessorKey as string,
|
||||||
|
header,
|
||||||
|
enableSorting: options?.enableSorting ?? true,
|
||||||
|
size: options?.size,
|
||||||
|
cell: options?.cell
|
||||||
|
? ({ getValue, row }) => options.cell!(getValue(), row.original)
|
||||||
|
: ({ getValue }) => getValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper for selection column
|
||||||
|
export function createSelectionColumn<T>(): ColumnDef<T, any> {
|
||||||
|
return {
|
||||||
|
id: 'select',
|
||||||
|
header: ({ table }) => null,
|
||||||
|
cell: () => null,
|
||||||
|
size: 50,
|
||||||
|
enableSorting: false
|
||||||
|
}
|
||||||
|
}
|
||||||
116
src/components/table/DataTable.vue
Normal file
116
src/components/table/DataTable.vue
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
FlexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
getSortedRowModel,
|
||||||
|
useVueTable,
|
||||||
|
type ColumnDef,
|
||||||
|
type SortingState
|
||||||
|
} from '@tanstack/vue-table'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
interface Props<T> {
|
||||||
|
data: T[]
|
||||||
|
columns: ColumnDef<T, any>[]
|
||||||
|
sorting?: SortingState
|
||||||
|
enableSorting?: boolean
|
||||||
|
class?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props<any>>(), {
|
||||||
|
sorting: () => [],
|
||||||
|
enableSorting: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'update:sorting': [value: SortingState]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const sortingState = ref<SortingState>(props.sorting)
|
||||||
|
|
||||||
|
const table = useVueTable({
|
||||||
|
get data() {
|
||||||
|
return props.data
|
||||||
|
},
|
||||||
|
get columns() {
|
||||||
|
return props.columns
|
||||||
|
},
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getSortedRowModel: props.enableSorting ? getSortedRowModel() : undefined,
|
||||||
|
onSortingChange: (updater) => {
|
||||||
|
if (typeof updater === 'function') {
|
||||||
|
sortingState.value = updater(sortingState.value)
|
||||||
|
} else {
|
||||||
|
sortingState.value = updater
|
||||||
|
}
|
||||||
|
emit('update:sorting', sortingState.value)
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
get sorting() {
|
||||||
|
return sortingState.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="['overflow-x-auto', props.class]">
|
||||||
|
<table class="w-full text-sm text-left">
|
||||||
|
<thead class="text-xs text-gray-500 uppercase bg-gray-50 border-b border-gray-200">
|
||||||
|
<tr
|
||||||
|
v-for="headerGroup in table.getHeaderGroups()"
|
||||||
|
:key="headerGroup.id"
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
v-for="header in headerGroup.headers"
|
||||||
|
:key="header.id"
|
||||||
|
:colSpan="header.colSpan"
|
||||||
|
:class="[
|
||||||
|
'px-6 py-3 font-medium',
|
||||||
|
header.column.getCanSort() ? 'cursor-pointer select-none hover:bg-gray-100' : ''
|
||||||
|
]"
|
||||||
|
@click="header.column.getToggleSortingHandler()?.($event)"
|
||||||
|
>
|
||||||
|
<FlexRender
|
||||||
|
v-if="!header.isPlaceholder"
|
||||||
|
:render="header.column.columnDef.header"
|
||||||
|
:props="header.getContext()"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
v-if="header.column.getIsSorted()"
|
||||||
|
class="ml-1"
|
||||||
|
>
|
||||||
|
{{ header.column.getIsSorted() === 'asc' ? '↑' : '↓' }}
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="divide-y divide-gray-200 bg-white">
|
||||||
|
<tr
|
||||||
|
v-for="row in table.getRowModel().rows"
|
||||||
|
:key="row.id"
|
||||||
|
class="hover:bg-gray-50"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
v-for="cell in row.getVisibleCells()"
|
||||||
|
:key="cell.id"
|
||||||
|
class="px-6 py-4"
|
||||||
|
>
|
||||||
|
<FlexRender
|
||||||
|
:render="cell.column.columnDef.cell"
|
||||||
|
:props="cell.getContext()"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="table.getRowModel().rows.length === 0">
|
||||||
|
<td
|
||||||
|
:colSpan="table.getAllColumns().length"
|
||||||
|
class="px-6 py-8 text-center text-gray-500"
|
||||||
|
>
|
||||||
|
No data available
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
71
src/components/ui/Avatar.vue
Normal file
71
src/components/ui/Avatar.vue
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
image?: string
|
||||||
|
label?: string
|
||||||
|
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
||||||
|
shape?: 'circle' | 'square'
|
||||||
|
class?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
size: 'md',
|
||||||
|
shape: 'circle'
|
||||||
|
})
|
||||||
|
|
||||||
|
const sizeClasses = {
|
||||||
|
xs: 'w-6 h-6 text-xs',
|
||||||
|
sm: 'w-8 h-8 text-sm',
|
||||||
|
md: 'w-10 h-10 text-base',
|
||||||
|
lg: 'w-12 h-12 text-lg',
|
||||||
|
xl: 'w-16 h-16 text-xl'
|
||||||
|
}
|
||||||
|
|
||||||
|
const initials = computed(() => {
|
||||||
|
if (!props.label) return ''
|
||||||
|
return props.label
|
||||||
|
.split(' ')
|
||||||
|
.map(n => n[0])
|
||||||
|
.join('')
|
||||||
|
.toUpperCase()
|
||||||
|
.slice(0, 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
const bgColor = computed(() => {
|
||||||
|
const colors = [
|
||||||
|
'bg-red-500', 'bg-orange-500', 'bg-amber-500', 'bg-yellow-500',
|
||||||
|
'bg-lime-500', 'bg-green-500', 'bg-emerald-500', 'bg-teal-500',
|
||||||
|
'bg-cyan-500', 'bg-sky-500', 'bg-blue-500', 'bg-indigo-500',
|
||||||
|
'bg-violet-500', 'bg-purple-500', 'bg-fuchsia-500', 'bg-pink-500',
|
||||||
|
'bg-rose-500'
|
||||||
|
]
|
||||||
|
if (!props.label) return 'bg-gray-400'
|
||||||
|
let hash = 0
|
||||||
|
for (let i = 0; i < props.label.length; i++) {
|
||||||
|
hash = props.label.charCodeAt(i) + ((hash << 5) - hash)
|
||||||
|
}
|
||||||
|
return colors[Math.abs(hash) % colors.length]
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:class="[
|
||||||
|
'inline-flex items-center justify-center overflow-hidden font-medium text-white',
|
||||||
|
sizeClasses[size],
|
||||||
|
shape === 'circle' ? 'rounded-full' : 'rounded-lg',
|
||||||
|
!image ? bgColor : '',
|
||||||
|
props.class
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-if="image"
|
||||||
|
:src="image"
|
||||||
|
:alt="label || 'Avatar'"
|
||||||
|
class="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
<span v-else-if="initials">{{ initials }}</span>
|
||||||
|
<span v-else class="i-heroicons-user w-1/2 h-1/2" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
59
src/components/ui/Button.vue
Normal file
59
src/components/ui/Button.vue
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
interface Props {
|
||||||
|
type?: 'button' | 'submit' | 'reset'
|
||||||
|
variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger'
|
||||||
|
size?: 'sm' | 'md' | 'lg'
|
||||||
|
disabled?: boolean
|
||||||
|
loading?: boolean
|
||||||
|
fluid?: boolean
|
||||||
|
class?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
type: 'button',
|
||||||
|
variant: 'primary',
|
||||||
|
size: 'md',
|
||||||
|
disabled: false,
|
||||||
|
loading: false,
|
||||||
|
fluid: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
click: [event: MouseEvent]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const variantClasses = {
|
||||||
|
primary: 'bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500',
|
||||||
|
secondary: 'bg-gray-600 text-white hover:bg-gray-700 focus:ring-gray-500',
|
||||||
|
outline: 'border-2 border-gray-300 bg-transparent text-gray-700 hover:bg-gray-50 focus:ring-gray-500',
|
||||||
|
ghost: 'bg-transparent text-gray-700 hover:bg-gray-100 focus:ring-gray-500',
|
||||||
|
danger: 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500'
|
||||||
|
}
|
||||||
|
|
||||||
|
const sizeClasses = {
|
||||||
|
sm: 'px-3 py-1.5 text-sm',
|
||||||
|
md: 'px-4 py-2 text-sm',
|
||||||
|
lg: 'px-6 py-3 text-base'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<button
|
||||||
|
:type="type"
|
||||||
|
:disabled="disabled || loading"
|
||||||
|
:class="[
|
||||||
|
'inline-flex items-center justify-center font-medium rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed',
|
||||||
|
variantClasses[variant],
|
||||||
|
sizeClasses[size],
|
||||||
|
fluid ? 'w-full' : '',
|
||||||
|
props.class
|
||||||
|
]"
|
||||||
|
@click="emit('click', $event)"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-if="loading"
|
||||||
|
class="i-heroicons-arrow-path mr-2 animate-spin w-4 h-4"
|
||||||
|
/>
|
||||||
|
<slot />
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
28
src/components/ui/Card.vue
Normal file
28
src/components/ui/Card.vue
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
interface Props {
|
||||||
|
class?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="bg-white rounded-xl border border-gray-200 overflow-hidden" :class="props.class">
|
||||||
|
<!-- Header slot -->
|
||||||
|
<div v-if="$slots.header" class="border-b border-gray-100">
|
||||||
|
<slot name="header" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
|
<div>
|
||||||
|
<slot name="content">
|
||||||
|
<slot />
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer slot -->
|
||||||
|
<div v-if="$slots.footer" class="border-t border-gray-100">
|
||||||
|
<slot name="footer" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
87
src/components/ui/Checkbox.vue
Normal file
87
src/components/ui/Checkbox.vue
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
interface Props {
|
||||||
|
modelValue: any[] | boolean | undefined
|
||||||
|
value?: any
|
||||||
|
name?: string
|
||||||
|
disabled?: boolean
|
||||||
|
size?: 'sm' | 'md'
|
||||||
|
binary?: boolean
|
||||||
|
inputId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
disabled: false,
|
||||||
|
size: 'md',
|
||||||
|
binary: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'update:modelValue': [value: any[] | boolean]
|
||||||
|
click: [event: MouseEvent]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const sizeClasses = {
|
||||||
|
sm: 'w-4 h-4',
|
||||||
|
md: 'w-5 h-5'
|
||||||
|
}
|
||||||
|
|
||||||
|
const isChecked = (): boolean => {
|
||||||
|
if (props.binary) {
|
||||||
|
return !!(props.modelValue as boolean)
|
||||||
|
}
|
||||||
|
return Array.isArray(props.modelValue) && props.value !== undefined
|
||||||
|
? props.modelValue.includes(props.value)
|
||||||
|
: false
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggle = (event?: MouseEvent) => {
|
||||||
|
if (props.binary) {
|
||||||
|
emit('update:modelValue', !props.modelValue)
|
||||||
|
} else {
|
||||||
|
const currentValue = Array.isArray(props.modelValue) ? props.modelValue : []
|
||||||
|
if (props.value !== undefined) {
|
||||||
|
if (currentValue.includes(props.value)) {
|
||||||
|
emit('update:modelValue', currentValue.filter(v => v !== props.value))
|
||||||
|
} else {
|
||||||
|
emit('update:modelValue', [...currentValue, props.value])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (event) {
|
||||||
|
emit('click', event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="inline-flex items-center"
|
||||||
|
:class="disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'"
|
||||||
|
@click="!disabled && toggle($event)"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:class="[
|
||||||
|
sizeClasses[size],
|
||||||
|
'rounded border-2 flex items-center justify-center transition-colors',
|
||||||
|
isChecked()
|
||||||
|
? 'bg-blue-600 border-blue-600'
|
||||||
|
: 'bg-white border-gray-300 hover:border-gray-400'
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-if="isChecked()"
|
||||||
|
class="i-heroicons-check text-white"
|
||||||
|
:class="size === 'sm' ? 'w-3 h-3' : 'w-4 h-4'"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:name="name"
|
||||||
|
:id="inputId"
|
||||||
|
:checked="isChecked()"
|
||||||
|
:disabled="disabled"
|
||||||
|
class="sr-only"
|
||||||
|
@change="toggle()"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
124
src/components/ui/Dialog.vue
Normal file
124
src/components/ui/Dialog.vue
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, onUnmounted, watch } from 'vue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
visible: boolean
|
||||||
|
header?: string
|
||||||
|
width?: string
|
||||||
|
closable?: boolean
|
||||||
|
draggable?: boolean
|
||||||
|
modal?: boolean
|
||||||
|
class?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
header: '',
|
||||||
|
width: '28rem',
|
||||||
|
closable: true,
|
||||||
|
draggable: false,
|
||||||
|
modal: true
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'update:visible': [value: boolean]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
emit('update:visible', false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBackdropClick = () => {
|
||||||
|
if (props.closable) {
|
||||||
|
handleClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleKeydown = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Escape' && props.visible && props.closable) {
|
||||||
|
handleClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
document.addEventListener('keydown', handleKeydown)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
document.removeEventListener('keydown', handleKeydown)
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.visible, (visible) => {
|
||||||
|
if (visible) {
|
||||||
|
document.body.style.overflow = 'hidden'
|
||||||
|
} else {
|
||||||
|
document.body.style.overflow = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Teleport to="body">
|
||||||
|
<Transition
|
||||||
|
enter-active-class="transition ease-out duration-200"
|
||||||
|
enter-from-class="opacity-0"
|
||||||
|
enter-to-class="opacity-100"
|
||||||
|
leave-active-class="transition ease-in duration-150"
|
||||||
|
leave-from-class="opacity-100"
|
||||||
|
leave-to-class="opacity-0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="visible"
|
||||||
|
class="fixed inset-0 z-50"
|
||||||
|
:class="[modal ? 'bg-black/50' : '']"
|
||||||
|
@click="handleBackdropClick"
|
||||||
|
>
|
||||||
|
<div class="flex min-h-full items-center justify-center p-4">
|
||||||
|
<Transition
|
||||||
|
enter-active-class="transition ease-out duration-200"
|
||||||
|
enter-from-class="opacity-0 scale-95"
|
||||||
|
enter-to-class="opacity-100 scale-100"
|
||||||
|
leave-active-class="transition ease-in duration-150"
|
||||||
|
leave-from-class="opacity-100 scale-100"
|
||||||
|
leave-to-class="opacity-0 scale-95"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="visible"
|
||||||
|
class="relative bg-white rounded-xl shadow-xl"
|
||||||
|
:style="{ width, maxWidth: 'calc(100vw - 2rem)' }"
|
||||||
|
:class="props.class"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<!-- Header -->
|
||||||
|
<div
|
||||||
|
v-if="header || $slots.header || closable"
|
||||||
|
class="flex items-center justify-between px-6 py-4 border-b border-gray-200"
|
||||||
|
>
|
||||||
|
<slot name="header">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900">{{ header }}</h3>
|
||||||
|
</slot>
|
||||||
|
<button
|
||||||
|
v-if="closable"
|
||||||
|
type="button"
|
||||||
|
class="text-gray-400 hover:text-gray-600 transition-colors"
|
||||||
|
@click="handleClose"
|
||||||
|
>
|
||||||
|
<span class="i-heroicons-x-mark w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
|
<div class="px-6 py-4">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<div v-if="$slots.footer" class="px-6 py-4 border-t border-gray-200">
|
||||||
|
<slot name="footer" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</Teleport>
|
||||||
|
</template>
|
||||||
75
src/components/ui/Input.vue
Normal file
75
src/components/ui/Input.vue
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
modelValue?: string | number
|
||||||
|
name?: string
|
||||||
|
type?: 'text' | 'email' | 'password' | 'number' | 'tel' | 'url'
|
||||||
|
placeholder?: string
|
||||||
|
disabled?: boolean
|
||||||
|
fluid?: boolean
|
||||||
|
size?: 'sm' | 'md' | 'lg'
|
||||||
|
invalid?: boolean
|
||||||
|
class?: string | Record<string, boolean>
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
type: 'text',
|
||||||
|
disabled: false,
|
||||||
|
fluid: false,
|
||||||
|
size: 'md',
|
||||||
|
invalid: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'update:modelValue': [value: string | number]
|
||||||
|
blur: [event: FocusEvent]
|
||||||
|
focus: [event: FocusEvent]
|
||||||
|
input: [event: Event]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const sizeClasses = {
|
||||||
|
sm: 'px-3 py-1.5 text-sm',
|
||||||
|
md: 'px-4 py-2 text-sm',
|
||||||
|
lg: 'px-4 py-3 text-base'
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseClasses = computed(() => [
|
||||||
|
'block w-full rounded-lg border border-gray-300 bg-white text-gray-900 placeholder-gray-400',
|
||||||
|
'focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500',
|
||||||
|
'disabled:bg-gray-100 disabled:cursor-not-allowed',
|
||||||
|
props.invalid ? 'border-red-500 focus:ring-red-500 focus:border-red-500' : '',
|
||||||
|
sizeClasses[props.size]
|
||||||
|
])
|
||||||
|
|
||||||
|
const inputClasses = computed(() => {
|
||||||
|
if (typeof props.class === 'string') {
|
||||||
|
return twMerge(baseClasses.value.join(' '), props.class)
|
||||||
|
}
|
||||||
|
return twMerge(baseClasses.value.join(' '))
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleInput = (event: Event) => {
|
||||||
|
const target = event.target as HTMLInputElement
|
||||||
|
const value = props.type === 'number'
|
||||||
|
? (target.valueAsNumber || 0)
|
||||||
|
: target.value
|
||||||
|
emit('update:modelValue', value)
|
||||||
|
emit('input', event)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<input
|
||||||
|
:name="name"
|
||||||
|
:type="type"
|
||||||
|
:value="modelValue"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:disabled="disabled"
|
||||||
|
:class="inputClasses"
|
||||||
|
@input="handleInput"
|
||||||
|
@blur="emit('blur', $event)"
|
||||||
|
@focus="emit('focus', $event)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
105
src/components/ui/InputPassword.vue
Normal file
105
src/components/ui/InputPassword.vue
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
modelValue?: string
|
||||||
|
name?: string
|
||||||
|
placeholder?: string
|
||||||
|
disabled?: boolean
|
||||||
|
fluid?: boolean
|
||||||
|
size?: 'sm' | 'md' | 'lg'
|
||||||
|
invalid?: boolean
|
||||||
|
feedback?: boolean
|
||||||
|
class?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
disabled: false,
|
||||||
|
fluid: false,
|
||||||
|
size: 'md',
|
||||||
|
invalid: false,
|
||||||
|
feedback: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'update:modelValue': [value: string]
|
||||||
|
blur: [event: FocusEvent]
|
||||||
|
focus: [event: FocusEvent]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const showPassword = ref(false)
|
||||||
|
|
||||||
|
const sizeClasses = {
|
||||||
|
sm: 'px-3 py-1.5 text-sm',
|
||||||
|
md: 'px-4 py-2 text-sm',
|
||||||
|
lg: 'px-4 py-3 text-base'
|
||||||
|
}
|
||||||
|
|
||||||
|
const inputClasses = computed(() => [
|
||||||
|
'block w-full rounded-lg border border-gray-300 bg-white text-gray-900 placeholder-gray-400',
|
||||||
|
'focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500',
|
||||||
|
'disabled:bg-gray-100 disabled:cursor-not-allowed pr-10',
|
||||||
|
props.invalid ? 'border-red-500 focus:ring-red-500 focus:border-red-500' : '',
|
||||||
|
sizeClasses[props.size]
|
||||||
|
])
|
||||||
|
|
||||||
|
const passwordStrength = computed(() => {
|
||||||
|
if (!props.modelValue) return 0
|
||||||
|
let strength = 0
|
||||||
|
if (props.modelValue.length >= 8) strength++
|
||||||
|
if (/[A-Z]/.test(props.modelValue)) strength++
|
||||||
|
if (/[0-9]/.test(props.modelValue)) strength++
|
||||||
|
if (/[^A-Za-z0-9]/.test(props.modelValue)) strength++
|
||||||
|
return strength
|
||||||
|
})
|
||||||
|
|
||||||
|
const strengthText = computed(() => {
|
||||||
|
const texts = ['Very Weak', 'Weak', 'Fair', 'Good', 'Strong']
|
||||||
|
return texts[passwordStrength.value]
|
||||||
|
})
|
||||||
|
|
||||||
|
const strengthColor = computed(() => {
|
||||||
|
const colors = ['bg-red-500', 'bg-red-400', 'bg-yellow-400', 'bg-blue-400', 'bg-green-500']
|
||||||
|
return colors[passwordStrength.value]
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="[fluid ? 'w-full' : '', props.class]">
|
||||||
|
<div class="relative">
|
||||||
|
<input
|
||||||
|
:name="name"
|
||||||
|
:type="showPassword ? 'text' : 'password'"
|
||||||
|
:value="modelValue"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:disabled="disabled"
|
||||||
|
:class="inputClasses"
|
||||||
|
@input="emit('update:modelValue', ($event.target as HTMLInputElement).value)"
|
||||||
|
@blur="emit('blur', $event)"
|
||||||
|
@focus="emit('focus', $event)"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-400 hover:text-gray-600"
|
||||||
|
@click="showPassword = !showPassword"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
:class="showPassword ? 'i-heroicons-eye-slash' : 'i-heroicons-eye'"
|
||||||
|
class="w-5 h-5"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="feedback && modelValue" class="mt-2">
|
||||||
|
<div class="flex gap-1 h-1 mb-1">
|
||||||
|
<div
|
||||||
|
v-for="i in 4"
|
||||||
|
:key="i"
|
||||||
|
class="flex-1 rounded-full transition-colors"
|
||||||
|
:class="i <= passwordStrength ? strengthColor : 'bg-gray-200'"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-gray-500">{{ strengthText }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
53
src/components/ui/ProgressBar.vue
Normal file
53
src/components/ui/ProgressBar.vue
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
value?: number
|
||||||
|
showValue?: boolean
|
||||||
|
unit?: string
|
||||||
|
mode?: 'determinate' | 'indeterminate'
|
||||||
|
color?: 'primary' | 'success' | 'warning' | 'danger'
|
||||||
|
class?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
value: 0,
|
||||||
|
showValue: true,
|
||||||
|
unit: '%',
|
||||||
|
mode: 'determinate',
|
||||||
|
color: 'primary'
|
||||||
|
})
|
||||||
|
|
||||||
|
const normalizedValue = computed(() => {
|
||||||
|
return Math.max(0, Math.min(100, props.value))
|
||||||
|
})
|
||||||
|
|
||||||
|
const colorClasses = {
|
||||||
|
primary: 'bg-blue-600',
|
||||||
|
success: 'bg-green-500',
|
||||||
|
warning: 'bg-yellow-500',
|
||||||
|
danger: 'bg-red-500'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="['w-full', props.class]">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<div class="flex-1 h-2 bg-gray-200 rounded-full overflow-hidden">
|
||||||
|
<div
|
||||||
|
v-if="mode === 'determinate'"
|
||||||
|
:class="['h-full rounded-full transition-all duration-300 ease-out', colorClasses[color]]"
|
||||||
|
:style="{ width: `${normalizedValue}%` }"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
:class="['h-full rounded-full animate-pulse', colorClasses[color]]"
|
||||||
|
style="width: 50%"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span v-if="showValue && mode === 'determinate'" class="text-xs font-medium text-gray-600 min-w-[3rem] text-right">
|
||||||
|
{{ normalizedValue }}{{ unit }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
27
src/components/ui/Skeleton.vue
Normal file
27
src/components/ui/Skeleton.vue
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
interface Props {
|
||||||
|
width?: string
|
||||||
|
height?: string
|
||||||
|
borderRadius?: string
|
||||||
|
circle?: boolean
|
||||||
|
class?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
width: '100%',
|
||||||
|
height: '1rem',
|
||||||
|
borderRadius: '0.375rem',
|
||||||
|
circle: false
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:class="['animate-pulse bg-gray-200', props.class]"
|
||||||
|
:style="{
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
borderRadius: circle ? '50%' : borderRadius
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
34
src/components/ui/Tag.vue
Normal file
34
src/components/ui/Tag.vue
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
interface Props {
|
||||||
|
value?: string
|
||||||
|
severity?: 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'danger'
|
||||||
|
icon?: string
|
||||||
|
class?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
severity: 'primary'
|
||||||
|
})
|
||||||
|
|
||||||
|
const severityClasses = {
|
||||||
|
primary: 'bg-blue-100 text-blue-800 border-blue-200',
|
||||||
|
secondary: 'bg-gray-100 text-gray-800 border-gray-200',
|
||||||
|
success: 'bg-green-100 text-green-800 border-green-200',
|
||||||
|
info: 'bg-cyan-100 text-cyan-800 border-cyan-200',
|
||||||
|
warning: 'bg-yellow-100 text-yellow-800 border-yellow-200',
|
||||||
|
danger: 'bg-red-100 text-red-800 border-red-200'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<span
|
||||||
|
:class="[
|
||||||
|
'inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-medium border',
|
||||||
|
severityClasses[severity],
|
||||||
|
props.class
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<span v-if="icon" :class="[icon, 'w-3 h-3']" />
|
||||||
|
<slot>{{ value }}</slot>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
73
src/components/ui/Toast.vue
Normal file
73
src/components/ui/Toast.vue
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useToast, type ToastSeverity } from '@/composables/useToast'
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
const severityIcons: Record<ToastSeverity, string> = {
|
||||||
|
success: 'i-heroicons-check-circle',
|
||||||
|
error: 'i-heroicons-x-circle',
|
||||||
|
info: 'i-heroicons-information-circle',
|
||||||
|
warn: 'i-heroicons-exclamation-triangle'
|
||||||
|
}
|
||||||
|
|
||||||
|
const severityClasses: Record<ToastSeverity, string> = {
|
||||||
|
success: 'bg-green-50 border-green-200 text-green-800',
|
||||||
|
error: 'bg-red-50 border-red-200 text-red-800',
|
||||||
|
info: 'bg-blue-50 border-blue-200 text-blue-800',
|
||||||
|
warn: 'bg-yellow-50 border-yellow-200 text-yellow-800'
|
||||||
|
}
|
||||||
|
|
||||||
|
const severityIconColors: Record<ToastSeverity, string> = {
|
||||||
|
success: 'text-green-500',
|
||||||
|
error: 'text-red-500',
|
||||||
|
info: 'text-blue-500',
|
||||||
|
warn: 'text-yellow-500'
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClose = (id: string) => {
|
||||||
|
toast.remove(id)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Teleport to="body">
|
||||||
|
<div class="fixed top-4 right-4 z-50 flex flex-col gap-2 max-w-sm">
|
||||||
|
<TransitionGroup
|
||||||
|
enter-active-class="transition ease-out duration-300"
|
||||||
|
enter-from-class="translate-x-full opacity-0"
|
||||||
|
enter-to-class="translate-x-0 opacity-100"
|
||||||
|
leave-active-class="transition ease-in duration-200"
|
||||||
|
leave-from-class="translate-x-0 opacity-100"
|
||||||
|
leave-to-class="translate-x-full opacity-0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="message in toast.messages"
|
||||||
|
:key="message.id"
|
||||||
|
:class="[
|
||||||
|
'flex items-start gap-3 p-4 rounded-lg border shadow-lg min-w-[300px]',
|
||||||
|
severityClasses[message.severity]
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
:class="[
|
||||||
|
severityIcons[message.severity],
|
||||||
|
severityIconColors[message.severity],
|
||||||
|
'w-5 h-5 flex-shrink-0 mt-0.5'
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<p class="font-medium text-sm">{{ message.summary }}</p>
|
||||||
|
<p v-if="message.detail" class="text-sm opacity-90 mt-0.5">{{ message.detail }}</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="flex-shrink-0 opacity-60 hover:opacity-100 transition-opacity"
|
||||||
|
@click="handleClose(message.id)"
|
||||||
|
>
|
||||||
|
<span class="i-heroicons-x-mark w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</TransitionGroup>
|
||||||
|
</div>
|
||||||
|
</Teleport>
|
||||||
|
</template>
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
interface AvatarProps {
|
|
||||||
label?: string;
|
|
||||||
shape?: 'circle' | 'square';
|
|
||||||
size?: 'small' | 'medium' | 'large';
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<AvatarProps>(), {
|
|
||||||
shape: 'circle',
|
|
||||||
size: 'medium',
|
|
||||||
});
|
|
||||||
|
|
||||||
const sizeClasses = {
|
|
||||||
small: 'w-8 h-8 text-xs',
|
|
||||||
medium: 'w-10 h-10 text-sm',
|
|
||||||
large: 'w-12 h-12 text-base',
|
|
||||||
};
|
|
||||||
|
|
||||||
const shapeClasses = {
|
|
||||||
circle: 'rounded-full',
|
|
||||||
square: 'rounded-lg',
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
:class="[
|
|
||||||
'inline-flex items-center justify-center font-medium bg-gray-200 text-gray-600',
|
|
||||||
sizeClasses[size],
|
|
||||||
shapeClasses[shape],
|
|
||||||
]"
|
|
||||||
>
|
|
||||||
<slot>
|
|
||||||
{{ label?.charAt(0).toUpperCase() || '?' }}
|
|
||||||
</slot>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
interface ButtonProps {
|
|
||||||
type?: 'button' | 'submit' | 'reset';
|
|
||||||
variant?: 'primary' | 'secondary' | 'outlined' | 'text';
|
|
||||||
size?: 'small' | 'medium' | 'large';
|
|
||||||
disabled?: boolean;
|
|
||||||
loading?: boolean;
|
|
||||||
label?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<ButtonProps>(), {
|
|
||||||
type: 'button',
|
|
||||||
variant: 'primary',
|
|
||||||
size: 'medium',
|
|
||||||
disabled: false,
|
|
||||||
loading: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const variantClasses = {
|
|
||||||
primary: 'bg-primary text-white hover:opacity-90',
|
|
||||||
secondary: 'bg-gray-600 text-white hover:bg-gray-700',
|
|
||||||
outlined: 'border border-gray-300 bg-transparent hover:bg-gray-50',
|
|
||||||
text: 'bg-transparent hover:bg-gray-100',
|
|
||||||
};
|
|
||||||
|
|
||||||
const sizeClasses = {
|
|
||||||
small: 'px-3 py-1.5 text-xs',
|
|
||||||
medium: 'px-4 py-2 text-sm',
|
|
||||||
large: 'px-6 py-3 text-base',
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<button
|
|
||||||
:type="type"
|
|
||||||
:disabled="disabled || loading"
|
|
||||||
:class="[
|
|
||||||
'inline-flex items-center justify-center font-medium rounded-lg transition-colors',
|
|
||||||
'focus:outline-none focus:ring-2 focus:ring-primary/20',
|
|
||||||
'disabled:opacity-50 disabled:cursor-not-allowed',
|
|
||||||
variantClasses[variant],
|
|
||||||
sizeClasses[size],
|
|
||||||
loading ? 'cursor-wait' : '',
|
|
||||||
]"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
v-if="loading"
|
|
||||||
class="animate-spin -ml-1 mr-2 h-4 w-4"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
|
|
||||||
<path
|
|
||||||
class="opacity-75"
|
|
||||||
fill="currentColor"
|
|
||||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<slot>{{ label }}</slot>
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
interface CardProps {
|
|
||||||
cardClass?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
defineProps<CardProps>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div :class="['bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden', cardClass]">
|
|
||||||
<slot name="header" />
|
|
||||||
<div>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { computed, inject } from 'vue';
|
|
||||||
|
|
||||||
interface CheckboxProps {
|
|
||||||
name: string;
|
|
||||||
value?: any;
|
|
||||||
binary?: boolean;
|
|
||||||
disabled?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<CheckboxProps>(), {
|
|
||||||
binary: false,
|
|
||||||
disabled: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const formContext = inject<{
|
|
||||||
values: Record<string, any>;
|
|
||||||
errors: Record<string, string>;
|
|
||||||
touched: Record<string, boolean>;
|
|
||||||
handleBlur: (name: string) => void;
|
|
||||||
handleChange: (name: string, value: any) => void;
|
|
||||||
} | null>('form-context', null);
|
|
||||||
|
|
||||||
const error = computed(() => formContext?.errors[props.name]);
|
|
||||||
const isInvalid = computed(() => formContext?.touched[props.name] && error.value);
|
|
||||||
|
|
||||||
const modelValue = computed({
|
|
||||||
get: () => {
|
|
||||||
const val = formContext?.values[props.name];
|
|
||||||
if (props.binary) return !!val;
|
|
||||||
return val?.includes(props.value);
|
|
||||||
},
|
|
||||||
set: (val) => {
|
|
||||||
if (props.binary) {
|
|
||||||
formContext?.handleChange(props.name, val);
|
|
||||||
} else {
|
|
||||||
const current = formContext?.values[props.name] || [];
|
|
||||||
if (val) {
|
|
||||||
formContext?.handleChange(props.name, [...current, props.value]);
|
|
||||||
} else {
|
|
||||||
formContext?.handleChange(props.name, current.filter((v: any) => v !== props.value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<input
|
|
||||||
:id="name + '-' + (value ?? 'binary')"
|
|
||||||
type="checkbox"
|
|
||||||
v-model="modelValue"
|
|
||||||
:disabled="disabled"
|
|
||||||
:class="[
|
|
||||||
'w-4 h-4 rounded border-gray-300 text-primary focus:ring-primary/20',
|
|
||||||
'disabled:opacity-50 disabled:cursor-not-allowed',
|
|
||||||
isInvalid ? 'border-red-500' : '',
|
|
||||||
]"
|
|
||||||
@blur="formContext?.handleBlur(name)"
|
|
||||||
/>
|
|
||||||
<label v-if="$slots.default" :for="name + '-' + (value ?? 'binary')" class="text-sm text-gray-700">
|
|
||||||
<slot />
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { onMounted, onUnmounted, watch } from 'vue';
|
|
||||||
|
|
||||||
interface DialogProps {
|
|
||||||
visible: boolean;
|
|
||||||
header?: string;
|
|
||||||
style?: Record<string, string>;
|
|
||||||
closable?: boolean;
|
|
||||||
modal?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<DialogProps>(), {
|
|
||||||
closable: true,
|
|
||||||
modal: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
'update:visible': [value: boolean];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const handleClose = () => {
|
|
||||||
if (props.closable) {
|
|
||||||
emit('update:visible', false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleBackdropClick = () => {
|
|
||||||
if (props.modal) {
|
|
||||||
handleClose();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEscape = (e: KeyboardEvent) => {
|
|
||||||
if (e.key === 'Escape' && props.visible) {
|
|
||||||
handleClose();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
document.addEventListener('keydown', handleEscape);
|
|
||||||
});
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
document.removeEventListener('keydown', handleEscape);
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(() => props.visible, (val) => {
|
|
||||||
if (val) {
|
|
||||||
document.body.style.overflow = 'hidden';
|
|
||||||
} else {
|
|
||||||
document.body.style.overflow = '';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Teleport to="body">
|
|
||||||
<Transition name="dialog">
|
|
||||||
<div v-if="visible" class="fixed inset-0 z-50 flex items-center justify-center">
|
|
||||||
<!-- Backdrop -->
|
|
||||||
<div
|
|
||||||
class="fixed inset-0 bg-black/50"
|
|
||||||
@click="handleBackdropClick"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Dialog -->
|
|
||||||
<div
|
|
||||||
class="relative bg-white rounded-xl shadow-xl w-full max-h-[90vh] overflow-auto"
|
|
||||||
:style="style || { width: '28rem' }"
|
|
||||||
>
|
|
||||||
<!-- Header -->
|
|
||||||
<div v-if="header" class="flex items-center justify-between px-6 py-4 border-b">
|
|
||||||
<h3 class="text-lg font-semibold text-gray-900">{{ header }}</h3>
|
|
||||||
<button
|
|
||||||
v-if="closable"
|
|
||||||
@click="handleClose"
|
|
||||||
class="p-1 rounded-lg hover:bg-gray-100 transition-colors"
|
|
||||||
>
|
|
||||||
<svg class="w-5 h-5 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Content -->
|
|
||||||
<div class="p-6 pt-4">
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Transition>
|
|
||||||
</Teleport>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.dialog-enter-active,
|
|
||||||
.dialog-leave-active {
|
|
||||||
transition: opacity 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-enter-from,
|
|
||||||
.dialog-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-enter-active .relative,
|
|
||||||
.dialog-leave-active .relative {
|
|
||||||
transition: transform 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-enter-from .relative,
|
|
||||||
.dialog-leave-to .relative {
|
|
||||||
transform: scale(0.95);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { computed, inject } from 'vue';
|
|
||||||
|
|
||||||
interface FieldProps {
|
|
||||||
name: string;
|
|
||||||
label?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<FieldProps>();
|
|
||||||
|
|
||||||
const formContext = inject<{
|
|
||||||
values: Record<string, any>;
|
|
||||||
errors: Record<string, string>;
|
|
||||||
touched: Record<string, boolean>;
|
|
||||||
validators: Record<string, ((value: any) => string | undefined)[]>;
|
|
||||||
handleBlur: (name: string) => void;
|
|
||||||
handleChange: (name: string, value: any) => void;
|
|
||||||
} | null>('form-context', null);
|
|
||||||
|
|
||||||
const error = computed(() => props.name ? formContext?.errors[props.name] : undefined);
|
|
||||||
const isInvalid = computed(() => props.name ? formContext?.touched[props.name] && !!error.value : false);
|
|
||||||
const fieldValue = computed(() => props.name ? formContext?.values[props.name] ?? '' : '');
|
|
||||||
|
|
||||||
const onChange = (value: any) => {
|
|
||||||
if (props.name && formContext) {
|
|
||||||
formContext.handleChange(props.name, value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onBlur = () => {
|
|
||||||
if (props.name && formContext) {
|
|
||||||
formContext.handleBlur(props.name);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Provide values to slot
|
|
||||||
const slotProps = {
|
|
||||||
value: fieldValue,
|
|
||||||
error: error,
|
|
||||||
errorMessage: error,
|
|
||||||
isInvalid,
|
|
||||||
name: props.name,
|
|
||||||
onChange,
|
|
||||||
onBlur,
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="field flex flex-col gap-1">
|
|
||||||
<label v-if="label" :for="name" class="text-sm font-medium text-gray-700">
|
|
||||||
{{ label }}
|
|
||||||
</label>
|
|
||||||
<slot v-bind="slotProps" />
|
|
||||||
<div v-if="isInvalid && error" class="text-xs text-red-600 mt-1">
|
|
||||||
{{ error }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { provide, reactive } from 'vue';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
initialValues?: Record<string, any>;
|
|
||||||
validators?: Record<string, ((value: any) => string | undefined)[]>;
|
|
||||||
formClass?: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
submit: [values: Record<string, any>];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const errors = reactive<Record<string, string>>({});
|
|
||||||
const touched = reactive<Record<string, boolean>>({});
|
|
||||||
const values = reactive<Record<string, any>>({...props.initialValues});
|
|
||||||
|
|
||||||
// Initialize values
|
|
||||||
if (props.initialValues) {
|
|
||||||
Object.assign(values, props.initialValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
const validateField = (name: string) => {
|
|
||||||
const value = values[name];
|
|
||||||
const fieldValidators = props.validators?.[name] || [];
|
|
||||||
|
|
||||||
for (const validator of fieldValidators) {
|
|
||||||
const error = validator(value);
|
|
||||||
if (error) {
|
|
||||||
errors[name] = error;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete errors[name];
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const validateAll = () => {
|
|
||||||
const fieldNames = Object.keys(props.validators || {});
|
|
||||||
let isValid = true;
|
|
||||||
|
|
||||||
for (const name of fieldNames) {
|
|
||||||
if (!validateField(name)) {
|
|
||||||
isValid = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return isValid;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = () => {
|
|
||||||
// Mark all fields as touched
|
|
||||||
const fieldNames = Object.keys(props.validators || {});
|
|
||||||
for (const name of fieldNames) {
|
|
||||||
touched[name] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (validateAll()) {
|
|
||||||
emit('submit', {...values});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleBlur = (name: string) => {
|
|
||||||
touched[name] = true;
|
|
||||||
validateField(name);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChange = (name: string, value: any) => {
|
|
||||||
values[name] = value;
|
|
||||||
if (touched[name]) {
|
|
||||||
validateField(name);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Provide form context to child components
|
|
||||||
provide('form-context', {
|
|
||||||
values,
|
|
||||||
errors,
|
|
||||||
touched,
|
|
||||||
validators: props.validators || {},
|
|
||||||
handleBlur,
|
|
||||||
handleChange,
|
|
||||||
validateField,
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<form @submit.prevent="handleSubmit" :class="[formClass, 'flex flex-col gap-4 w-full']">
|
|
||||||
<slot />
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { computed } from 'vue';
|
|
||||||
|
|
||||||
interface InputProps {
|
|
||||||
name?: string;
|
|
||||||
type?: string;
|
|
||||||
placeholder?: string;
|
|
||||||
disabled?: boolean;
|
|
||||||
fluid?: boolean;
|
|
||||||
modelValue?: string | any;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<InputProps>(), {
|
|
||||||
type: 'text',
|
|
||||||
disabled: false,
|
|
||||||
fluid: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
'update:modelValue': [value: string];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
// Handle the v-model binding - support both string and computed ref
|
|
||||||
const inputValue = computed(() => {
|
|
||||||
const val = props.modelValue;
|
|
||||||
// Check if it's a ref/computed
|
|
||||||
if (val && typeof val === 'object' && 'value' in val) {
|
|
||||||
return val.value;
|
|
||||||
}
|
|
||||||
return val ?? '';
|
|
||||||
});
|
|
||||||
|
|
||||||
const onInput = (event: Event) => {
|
|
||||||
const target = event.target as HTMLInputElement;
|
|
||||||
emit('update:modelValue', target.value);
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<input
|
|
||||||
:id="name"
|
|
||||||
:value="inputValue"
|
|
||||||
@input="onInput"
|
|
||||||
:type="type"
|
|
||||||
:placeholder="placeholder"
|
|
||||||
:disabled="disabled"
|
|
||||||
:class="[
|
|
||||||
'px-3 py-2 text-sm border rounded-lg outline-none transition-colors',
|
|
||||||
'focus:ring-2 focus:ring-primary/20 focus:border-primary',
|
|
||||||
'disabled:bg-gray-100 disabled:text-gray-500 disabled:cursor-not-allowed',
|
|
||||||
fluid ? 'w-full' : '',
|
|
||||||
'border-gray-300',
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
interface ProgressBarProps {
|
|
||||||
value?: number;
|
|
||||||
showValue?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<ProgressBarProps>(), {
|
|
||||||
value: 0,
|
|
||||||
showValue: false,
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="w-full">
|
|
||||||
<div
|
|
||||||
v-if="showValue"
|
|
||||||
class="flex justify-between mb-1"
|
|
||||||
>
|
|
||||||
<span class="text-sm font-medium text-gray-700">
|
|
||||||
<slot name="value">{{ Math.round(value) }}%</slot>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="w-full bg-gray-200 rounded-full h-2 overflow-hidden">
|
|
||||||
<div
|
|
||||||
class="bg-primary h-2 rounded-full transition-all duration-300"
|
|
||||||
:style="{ width: `${Math.min(100, Math.max(0, value))}%` }"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
interface SkeletonProps {
|
|
||||||
width?: string;
|
|
||||||
height?: string;
|
|
||||||
borderRadius?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<SkeletonProps>(), {
|
|
||||||
width: '100%',
|
|
||||||
height: '1rem',
|
|
||||||
borderRadius: '0.375rem',
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
class="animate-pulse bg-gray-200"
|
|
||||||
:style="{
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
borderRadius,
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
<script setup lang="ts" generic="T">
|
|
||||||
interface TableProps<T> {
|
|
||||||
value: T[];
|
|
||||||
dataKey: string;
|
|
||||||
selection?: T[];
|
|
||||||
tableStyle?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<TableProps<T>>();
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
'update:selection': [value: T[]];
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="overflow-x-auto">
|
|
||||||
<table :class="['w-full', tableStyle]">
|
|
||||||
<thead>
|
|
||||||
<tr class="border-b border-gray-200 bg-gray-50">
|
|
||||||
<slot name="header" />
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr
|
|
||||||
v-for="(item, index) in value"
|
|
||||||
:key="String((item as any)[dataKey])"
|
|
||||||
class="border-b border-gray-100 hover:bg-gray-50 transition-colors"
|
|
||||||
>
|
|
||||||
<slot name="body" :data="item" :index="index" />
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
interface TagProps {
|
|
||||||
value?: string;
|
|
||||||
severity?: 'success' | 'error' | 'warn' | 'info' | 'secondary';
|
|
||||||
rounded?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<TagProps>(), {
|
|
||||||
rounded: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const severityClasses = {
|
|
||||||
success: 'bg-green-100 text-green-800',
|
|
||||||
error: 'bg-red-100 text-red-800',
|
|
||||||
warn: 'bg-yellow-100 text-yellow-800',
|
|
||||||
info: 'bg-blue-100 text-blue-800',
|
|
||||||
secondary: 'bg-gray-100 text-gray-800',
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<span
|
|
||||||
:class="[
|
|
||||||
'inline-flex items-center px-2.5 py-0.5 text-xs font-medium',
|
|
||||||
rounded ? 'rounded-full' : 'rounded',
|
|
||||||
severity ? severityClasses[severity] : 'bg-gray-100 text-gray-800',
|
|
||||||
]"
|
|
||||||
>
|
|
||||||
<slot>{{ value }}</slot>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
<script setup lang="ts" generic="T">
|
|
||||||
import { useForm } from '@tanstack/vue-form';
|
|
||||||
import { provide } from 'vue';
|
|
||||||
|
|
||||||
interface FormProps {
|
|
||||||
initialValues?: T;
|
|
||||||
onSubmit?: (values: T) => void | Promise<void>;
|
|
||||||
validators?: Record<string, (value: any) => string | undefined>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<FormProps>();
|
|
||||||
|
|
||||||
const form = useForm({
|
|
||||||
initialValues: props.initialValues,
|
|
||||||
onSubmit: async (values) => {
|
|
||||||
await props.onSubmit?.(values.value);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Provide form context to child components
|
|
||||||
provide('tanstack-form', form);
|
|
||||||
provide('tanstack-form-validators', props.validators || {});
|
|
||||||
|
|
||||||
const handleSubmit = (e: Event) => {
|
|
||||||
e.preventDefault();
|
|
||||||
form.handleSubmit();
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<form @submit="handleSubmit" class="flex flex-col gap-4 w-full">
|
|
||||||
<slot :form="form" />
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { computed, inject } from 'vue';
|
|
||||||
|
|
||||||
interface TextareaProps {
|
|
||||||
name: string;
|
|
||||||
rows?: number;
|
|
||||||
placeholder?: string;
|
|
||||||
disabled?: boolean;
|
|
||||||
fluid?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<TextareaProps>(), {
|
|
||||||
rows: 3,
|
|
||||||
disabled: false,
|
|
||||||
fluid: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const formContext = inject<{
|
|
||||||
values: Record<string, any>;
|
|
||||||
errors: Record<string, string>;
|
|
||||||
touched: Record<string, boolean>;
|
|
||||||
handleBlur: (name: string) => void;
|
|
||||||
handleChange: (name: string, value: any) => void;
|
|
||||||
} | null>('form-context', null);
|
|
||||||
|
|
||||||
const error = computed(() => formContext?.errors[props.name]);
|
|
||||||
const isInvalid = computed(() => formContext?.touched[props.name] && error.value);
|
|
||||||
const modelValue = computed({
|
|
||||||
get: () => formContext?.values[props.name] ?? '',
|
|
||||||
set: (val) => formContext?.handleChange(props.name, val),
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<textarea
|
|
||||||
:id="name"
|
|
||||||
v-model="modelValue"
|
|
||||||
:rows="rows"
|
|
||||||
:placeholder="placeholder"
|
|
||||||
:disabled="disabled"
|
|
||||||
:class="[
|
|
||||||
'px-3 py-2 text-sm border rounded-lg outline-none transition-colors resize-none',
|
|
||||||
'focus:ring-2 focus:ring-primary/20 focus:border-primary',
|
|
||||||
'disabled:bg-gray-100 disabled:text-gray-500 disabled:cursor-not-allowed',
|
|
||||||
fluid ? 'w-full' : '',
|
|
||||||
isInvalid ? 'border-red-500 focus:border-red-500 focus:ring-red-500/20' : 'border-gray-300',
|
|
||||||
]"
|
|
||||||
@blur="formContext?.handleBlur(name)"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { provide, ref } from 'vue';
|
|
||||||
|
|
||||||
interface ToastItem {
|
|
||||||
id: string;
|
|
||||||
severity: 'success' | 'error' | 'warn' | 'info';
|
|
||||||
summary: string;
|
|
||||||
detail?: string;
|
|
||||||
life?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const toasts = ref<ToastItem[]>([]);
|
|
||||||
|
|
||||||
const addToast = (toast: Omit<ToastItem, 'id'>) => {
|
|
||||||
const id = crypto.randomUUID();
|
|
||||||
const newToast = { ...toast, id };
|
|
||||||
toasts.value.push(newToast);
|
|
||||||
|
|
||||||
const life = toast.life || 5000;
|
|
||||||
setTimeout(() => {
|
|
||||||
removeToast(id);
|
|
||||||
}, life);
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeToast = (id: string) => {
|
|
||||||
const index = toasts.value.findIndex(t => t.id === id);
|
|
||||||
if (index > -1) {
|
|
||||||
toasts.value.splice(index, 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const toast = {
|
|
||||||
add: addToast,
|
|
||||||
remove: removeToast,
|
|
||||||
};
|
|
||||||
|
|
||||||
provide('toast', toast);
|
|
||||||
|
|
||||||
const severityClasses = {
|
|
||||||
success: 'bg-green-50 text-green-800 border-green-200',
|
|
||||||
error: 'bg-red-50 text-red-800 border-red-200',
|
|
||||||
warn: 'bg-yellow-50 text-yellow-800 border-yellow-200',
|
|
||||||
info: 'bg-blue-50 text-blue-800 border-blue-200',
|
|
||||||
};
|
|
||||||
|
|
||||||
const severityIcons = {
|
|
||||||
success: '✓',
|
|
||||||
error: '✕',
|
|
||||||
warn: '⚠',
|
|
||||||
info: 'ℹ',
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Teleport to="body">
|
|
||||||
<div class="fixed top-4 right-4 z-[100] flex flex-col gap-2 max-w-sm">
|
|
||||||
<TransitionGroup name="toast">
|
|
||||||
<div
|
|
||||||
v-for="toast in toasts"
|
|
||||||
:key="toast.id"
|
|
||||||
:class="[
|
|
||||||
'flex items-start gap-3 p-4 rounded-lg border shadow-lg',
|
|
||||||
severityClasses[toast.severity],
|
|
||||||
]"
|
|
||||||
>
|
|
||||||
<span class="text-lg">{{ severityIcons[toast.severity] }}</span>
|
|
||||||
<div class="flex-1 min-w-0">
|
|
||||||
<p class="font-medium">{{ toast.summary }}</p>
|
|
||||||
<p v-if="toast.detail" class="text-sm opacity-90 mt-1">{{ toast.detail }}</p>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
@click="removeToast(toast.id)"
|
|
||||||
class="p-1 hover:bg-black/5 rounded transition-colors"
|
|
||||||
>
|
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</TransitionGroup>
|
|
||||||
</div>
|
|
||||||
</Teleport>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.toast-enter-active,
|
|
||||||
.toast-leave-active {
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast-enter-from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast-move {
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
export { default as Avatar } from './Avatar.vue';
|
|
||||||
export { default as Button } from './Button.vue';
|
|
||||||
export { default as Card } from './Card.vue';
|
|
||||||
export { default as Checkbox } from './Checkbox.vue';
|
|
||||||
export { default as Dialog } from './Dialog.vue';
|
|
||||||
export { default as Field } from './Field.vue';
|
|
||||||
export { default as Form } from './Form.vue';
|
|
||||||
export { default as Input } from './Input.vue';
|
|
||||||
export { default as ProgressBar } from './ProgressBar.vue';
|
|
||||||
export { default as Skeleton } from './Skeleton.vue';
|
|
||||||
export { default as Table } from './Table.vue';
|
|
||||||
export { default as Tag } from './Tag.vue';
|
|
||||||
export { default as Textarea } from './Textarea.vue';
|
|
||||||
export { default as Toast } from './Toast.vue';
|
|
||||||
|
|
||||||
174
src/composables/useDataLoader.ts
Normal file
174
src/composables/useDataLoader.ts
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
|
interface DataLoaderOptions<T> {
|
||||||
|
key: string
|
||||||
|
fetcher: () => Promise<T>
|
||||||
|
revalidateOnFocus?: boolean
|
||||||
|
revalidateOnReconnect?: boolean
|
||||||
|
refreshInterval?: number
|
||||||
|
dedupingInterval?: number
|
||||||
|
fallbackData?: T
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DataLoaderState<T> {
|
||||||
|
data: T | undefined
|
||||||
|
error: Error | null
|
||||||
|
isLoading: boolean
|
||||||
|
isValidating: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global cache
|
||||||
|
const cache = new Map<string, { data: any; timestamp: number }>()
|
||||||
|
const dedupeTimers = new Map<string, number>()
|
||||||
|
const DEDUPING_INTERVAL = 2000
|
||||||
|
|
||||||
|
export function useDataLoader<T>(options: DataLoaderOptions<T>) {
|
||||||
|
const route = useRoute()
|
||||||
|
const {
|
||||||
|
key,
|
||||||
|
fetcher,
|
||||||
|
revalidateOnFocus = false,
|
||||||
|
revalidateOnReconnect = true,
|
||||||
|
refreshInterval,
|
||||||
|
fallbackData
|
||||||
|
} = options
|
||||||
|
|
||||||
|
const data = ref<T | undefined>(fallbackData)
|
||||||
|
const error = ref<Error | null>(null)
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const isValidating = ref(false)
|
||||||
|
|
||||||
|
let refreshTimer: number | null = null
|
||||||
|
let isMounted = false
|
||||||
|
|
||||||
|
const mutate = async (newData?: T): Promise<T | undefined> => {
|
||||||
|
if (newData !== undefined) {
|
||||||
|
data.value = newData
|
||||||
|
cache.set(key, { data: newData, timestamp: Date.now() })
|
||||||
|
return newData
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dedupe requests
|
||||||
|
if (dedupeTimers.has(key)) {
|
||||||
|
return data.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const dedupeKey = key
|
||||||
|
dedupeTimers.set(dedupeKey, window.setTimeout(() => {
|
||||||
|
dedupeTimers.delete(dedupeKey)
|
||||||
|
}, DEDUPING_INTERVAL))
|
||||||
|
|
||||||
|
isValidating.value = true
|
||||||
|
if (!data.value) {
|
||||||
|
isLoading.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await fetcher()
|
||||||
|
data.value = result
|
||||||
|
error.value = null
|
||||||
|
cache.set(key, { data: result, timestamp: Date.now() })
|
||||||
|
return result
|
||||||
|
} catch (err) {
|
||||||
|
error.value = err as Error
|
||||||
|
throw err
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
isValidating.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial load
|
||||||
|
const load = async () => {
|
||||||
|
const cached = cache.get(key)
|
||||||
|
if (cached && Date.now() - cached.timestamp < DEDUPING_INTERVAL) {
|
||||||
|
data.value = cached.data
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await mutate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Revalidate on focus
|
||||||
|
const handleFocus = () => {
|
||||||
|
if (revalidateOnFocus && document.visibilityState === 'visible') {
|
||||||
|
mutate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Revalidate on reconnect
|
||||||
|
const handleOnline = () => {
|
||||||
|
if (revalidateOnReconnect) {
|
||||||
|
mutate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup refresh interval
|
||||||
|
const setupRefreshInterval = () => {
|
||||||
|
if (refreshInterval && refreshInterval > 0) {
|
||||||
|
refreshTimer = window.setInterval(() => {
|
||||||
|
mutate()
|
||||||
|
}, refreshInterval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup refresh interval
|
||||||
|
const cleanupRefreshInterval = () => {
|
||||||
|
if (refreshTimer) {
|
||||||
|
clearInterval(refreshTimer)
|
||||||
|
refreshTimer = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
isMounted = true
|
||||||
|
load()
|
||||||
|
|
||||||
|
if (revalidateOnFocus) {
|
||||||
|
document.addEventListener('visibilitychange', handleFocus)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (revalidateOnReconnect) {
|
||||||
|
window.addEventListener('online', handleOnline)
|
||||||
|
}
|
||||||
|
|
||||||
|
setupRefreshInterval()
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
isMounted = false
|
||||||
|
cleanupRefreshInterval()
|
||||||
|
|
||||||
|
if (revalidateOnFocus) {
|
||||||
|
document.removeEventListener('visibilitychange', handleFocus)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (revalidateOnReconnect) {
|
||||||
|
window.removeEventListener('online', handleOnline)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Revalidate when key changes
|
||||||
|
watch(() => key, () => {
|
||||||
|
if (isMounted) {
|
||||||
|
load()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: computed(() => data.value),
|
||||||
|
error: computed(() => error.value),
|
||||||
|
isLoading: computed(() => isLoading.value),
|
||||||
|
isValidating: computed(() => isValidating.value),
|
||||||
|
mutate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper for SSR compatibility
|
||||||
|
export const useSWRV = <T>(key: string, fetcher: () => Promise<T>) => {
|
||||||
|
return useDataLoader<T>({
|
||||||
|
key,
|
||||||
|
fetcher,
|
||||||
|
revalidateOnFocus: false
|
||||||
|
})
|
||||||
|
}
|
||||||
90
src/composables/useToast.ts
Normal file
90
src/composables/useToast.ts
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import { reactive } from 'vue'
|
||||||
|
|
||||||
|
export type ToastSeverity = 'success' | 'error' | 'info' | 'warn'
|
||||||
|
|
||||||
|
export interface ToastMessage {
|
||||||
|
id: string
|
||||||
|
severity: ToastSeverity
|
||||||
|
summary: string
|
||||||
|
detail?: string
|
||||||
|
life?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ToastState {
|
||||||
|
messages: ToastMessage[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const state = reactive<ToastState>({
|
||||||
|
messages: []
|
||||||
|
})
|
||||||
|
|
||||||
|
let toastIdCounter = 0
|
||||||
|
|
||||||
|
export const useToast = () => {
|
||||||
|
const add = (message: Omit<ToastMessage, 'id'>) => {
|
||||||
|
const id = `toast-${++toastIdCounter}`
|
||||||
|
const newMessage: ToastMessage = {
|
||||||
|
id,
|
||||||
|
life: 3000,
|
||||||
|
...message
|
||||||
|
}
|
||||||
|
|
||||||
|
state.messages.push(newMessage)
|
||||||
|
|
||||||
|
if (newMessage.life && newMessage.life > 0) {
|
||||||
|
setTimeout(() => {
|
||||||
|
remove(id)
|
||||||
|
}, newMessage.life)
|
||||||
|
}
|
||||||
|
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
const remove = (id: string) => {
|
||||||
|
const index = state.messages.findIndex(m => m.id === id)
|
||||||
|
if (index > -1) {
|
||||||
|
state.messages.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const clear = () => {
|
||||||
|
state.messages.length = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const success = (detail: string, summary: string = 'Success') => {
|
||||||
|
return add({ severity: 'success', summary, detail })
|
||||||
|
}
|
||||||
|
|
||||||
|
const error = (detail: string, summary: string = 'Error') => {
|
||||||
|
return add({ severity: 'error', summary, detail })
|
||||||
|
}
|
||||||
|
|
||||||
|
const info = (detail: string, summary: string = 'Info') => {
|
||||||
|
return add({ severity: 'info', summary, detail })
|
||||||
|
}
|
||||||
|
|
||||||
|
const warn = (detail: string, summary: string = 'Warning') => {
|
||||||
|
return add({ severity: 'warn', summary, detail })
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
messages: state.messages,
|
||||||
|
add,
|
||||||
|
remove,
|
||||||
|
clear,
|
||||||
|
success,
|
||||||
|
error,
|
||||||
|
info,
|
||||||
|
warn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global toast instance for use outside of components
|
||||||
|
let globalToastInstance: ReturnType<typeof useToast> | null = null
|
||||||
|
|
||||||
|
export const getGlobalToast = () => {
|
||||||
|
if (!globalToastInstance) {
|
||||||
|
globalToastInstance = useToast()
|
||||||
|
}
|
||||||
|
return globalToastInstance
|
||||||
|
}
|
||||||
@@ -9,8 +9,9 @@ import { buildBootstrapScript } from './lib/manifest';
|
|||||||
import { createTextTransformStreamClass } from './lib/replateStreamText';
|
import { createTextTransformStreamClass } from './lib/replateStreamText';
|
||||||
import { createApp } from './main';
|
import { createApp } from './main';
|
||||||
import { useAuthStore } from './stores/auth';
|
import { useAuthStore } from './stores/auth';
|
||||||
|
|
||||||
const app = new Hono()
|
const app = new Hono()
|
||||||
// app.use(renderer)
|
|
||||||
app.use('*', contextStorage());
|
app.use('*', contextStorage());
|
||||||
app.use(cors(), async (c, next) => {
|
app.use(cors(), async (c, next) => {
|
||||||
c.set("fetch", app.request.bind(app));
|
c.set("fetch", app.request.bind(app));
|
||||||
@@ -31,8 +32,7 @@ app.use(cors(), async (c, next) => {
|
|||||||
url.protocol = 'https:'
|
url.protocol = 'https:'
|
||||||
url.pathname = path.replace(/^\/r/, '') || '/'
|
url.pathname = path.replace(/^\/r/, '') || '/'
|
||||||
url.port = ''
|
url.port = ''
|
||||||
// console.log("url", url.toString())
|
|
||||||
// console.log("c.req.raw", c.req.raw)
|
|
||||||
const headers = new Headers(c.req.header());
|
const headers = new Headers(c.req.header());
|
||||||
headers.delete("host");
|
headers.delete("host");
|
||||||
headers.delete("connection");
|
headers.delete("connection");
|
||||||
@@ -46,9 +46,11 @@ app.use(cors(), async (c, next) => {
|
|||||||
credentials: 'include'
|
credentials: 'include'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get("/.well-known/*", (c) => {
|
app.get("/.well-known/*", (c) => {
|
||||||
return c.json({ ok: true });
|
return c.json({ ok: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get("*", async (c) => {
|
app.get("*", async (c) => {
|
||||||
const nonce = crypto.randomUUID();
|
const nonce = crypto.randomUUID();
|
||||||
const url = new URL(c.req.url);
|
const url = new URL(c.req.url);
|
||||||
@@ -56,25 +58,28 @@ app.get("*", async (c) => {
|
|||||||
app.provide("honoContext", c);
|
app.provide("honoContext", c);
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore();
|
||||||
auth.$reset();
|
auth.$reset();
|
||||||
|
await auth.init();
|
||||||
await router.push(url.pathname);
|
await router.push(url.pathname);
|
||||||
await router.isReady();
|
await router.isReady();
|
||||||
|
|
||||||
return streamText(c, async (stream) => {
|
return streamText(c, async (stream) => {
|
||||||
c.header("Content-Type", "text/html; charset=utf-8");
|
c.header("Content-Type", "text/html; charset=utf-8");
|
||||||
c.header("Content-Encoding", "Identity");
|
c.header("Content-Encoding", "Identity");
|
||||||
const ctx: Record<string, any> = {};
|
const ctx: Record<string, any> = {};
|
||||||
const appStream = renderToWebStream(app, ctx);
|
const appStream = renderToWebStream(app, ctx);
|
||||||
// console.log("ctx: ", );
|
|
||||||
await stream.write("<!DOCTYPE html><html lang='en'><head>");
|
await stream.write("<!DOCTYPE html><html lang='en'><head>");
|
||||||
await stream.write("<base href='" + url.origin + "'/>");
|
await stream.write("<base href='" + url.origin + "'/>");
|
||||||
|
|
||||||
await renderSSRHead(head).then((headString) => stream.write(headString.headTags.replace(/\n/g, "")));
|
await renderSSRHead(head).then((headString) => stream.write(headString.headTags.replace(/\n/g, "")));
|
||||||
// await stream.write(`<link href="https://fonts.googleapis.com/css2?family=Be+Vietnam+Pro:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap"rel="stylesheet"></link>`);
|
|
||||||
await stream.write(`<link rel="preconnect" href="https://fonts.googleapis.com">`);
|
await stream.write(`<link rel="preconnect" href="https://fonts.googleapis.com">`);
|
||||||
await stream.write(`<link href="https://fonts.googleapis.com/css2?family=Google+Sans:ital,opsz,wght@0,17..18,400..700;1,17..18,400..700&display=swap" rel="stylesheet">`);
|
await stream.write(`<link href="https://fonts.googleapis.com/css2?family=Google+Sans:ital,opsz,wght@0,17..18,400..700;1,17..18,400..700&display=swap" rel="stylesheet">`);
|
||||||
await stream.write('<link rel="icon" href="/favicon.ico" />');
|
await stream.write('<link rel="icon" href="/favicon.ico" />');
|
||||||
await stream.write(buildBootstrapScript());
|
await stream.write(buildBootstrapScript());
|
||||||
|
|
||||||
await stream.write(`</head><body class='${bodyClass}'>`);
|
await stream.write(`</head><body class='${bodyClass}'>`);
|
||||||
await stream.pipe(createTextTransformStreamClass(appStream, (text) => text.replace('<div id="anchor-header" class="p-4"></div>', `<div id="anchor-header" class="p-4">${ctx.teleports["#anchor-header"] || ""}</div>`).replace('<div id="anchor-top"></div>', `<div id="anchor-top">${ctx.teleports["#anchor-top"] || ""}</div>`)));
|
await stream.pipe(createTextTransformStreamClass(appStream, (text) => text.replace('<div id="anchor-header" class="p-4"></div>', `<div id="anchor-header" class="p-4">${ctx.teleports["#anchor-header"] || ""}</div>`).replace('<div id="anchor-top"></div>', `<div id="anchor-top">${ctx.teleports["#anchor-top"] || ""}</div>`)));
|
||||||
|
|
||||||
delete ctx.teleports
|
delete ctx.teleports
|
||||||
delete ctx.__teleportBuffers
|
delete ctx.__teleportBuffers
|
||||||
delete ctx.modules;
|
delete ctx.modules;
|
||||||
@@ -83,6 +88,7 @@ app.get("*", async (c) => {
|
|||||||
await stream.write("</body></html>");
|
await stream.write("</body></html>");
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
const ESCAPE_LOOKUP: { [match: string]: string } = {
|
const ESCAPE_LOOKUP: { [match: string]: string } = {
|
||||||
"&": "\\u0026",
|
"&": "\\u0026",
|
||||||
">": "\\u003e",
|
">": "\\u003e",
|
||||||
@@ -96,4 +102,5 @@ const ESCAPE_REGEX = /[&><\u2028\u2029]/g;
|
|||||||
function htmlEscape(str: string): string {
|
function htmlEscape(str: string): string {
|
||||||
return str.replace(ESCAPE_REGEX, (match) => ESCAPE_LOOKUP[match]);
|
return str.replace(ESCAPE_REGEX, (match) => ESCAPE_LOOKUP[match]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default app
|
export default app
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
10
src/main.ts
10
src/main.ts
@@ -5,18 +5,24 @@ import { createSSRApp } from 'vue';
|
|||||||
import { RouterView } from 'vue-router';
|
import { RouterView } from 'vue-router';
|
||||||
import { withErrorBoundary } from './lib/hoc/withErrorBoundary';
|
import { withErrorBoundary } from './lib/hoc/withErrorBoundary';
|
||||||
import createAppRouter from './routes';
|
import createAppRouter from './routes';
|
||||||
|
|
||||||
const bodyClass = ":uno: font-sans text-gray-800 antialiased flex flex-col min-h-screen"
|
const bodyClass = ":uno: font-sans text-gray-800 antialiased flex flex-col min-h-screen"
|
||||||
|
|
||||||
export function createApp() {
|
export function createApp() {
|
||||||
const pinia = createPinia();
|
const pinia = createPinia();
|
||||||
const app = createSSRApp(withErrorBoundary(RouterView));
|
const app = createSSRApp(withErrorBoundary(RouterView));
|
||||||
const head = import.meta.env.SSR ? SSRHead() : CSRHead();
|
const head = import.meta.env.SSR ? SSRHead() : CSRHead();
|
||||||
|
|
||||||
app.use(head);
|
app.use(head);
|
||||||
|
|
||||||
|
// Directive để skip hydration cho các phần tử không cần thiết
|
||||||
app.directive('nh', {
|
app.directive('nh', {
|
||||||
created(el) {
|
created(el) {
|
||||||
el.__v_skip = true;
|
el.__v_skip = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Restore state từ SSR
|
||||||
if (!import.meta.env.SSR) {
|
if (!import.meta.env.SSR) {
|
||||||
Object.entries(JSON.parse(document.getElementById("__APP_DATA__")?.innerText || "{}")).forEach(([key, value]) => {
|
Object.entries(JSON.parse(document.getElementById("__APP_DATA__")?.innerText || "{}")).forEach(([key, value]) => {
|
||||||
(window as any)[key] = value;
|
(window as any)[key] = value;
|
||||||
@@ -25,9 +31,11 @@ export function createApp() {
|
|||||||
pinia.state.value = (window as any).$p;
|
pinia.state.value = (window as any).$p;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
app.use(pinia);
|
app.use(pinia);
|
||||||
|
|
||||||
const router = createAppRouter();
|
const router = createAppRouter();
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
|
||||||
return { app, router, head, pinia, bodyClass };
|
return { app, router, head, pinia, bodyClass };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,397 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { client, type ModelVideo } from '@/api/client';
|
|
||||||
import PageHeader from '@/components/dashboard/PageHeader.vue';
|
|
||||||
import StatsCard from '@/components/dashboard/StatsCard.vue';
|
|
||||||
import { Skeleton } from '@/components/ui/form';
|
|
||||||
import { computed, onMounted, ref } from 'vue';
|
|
||||||
import { useRouter } from 'vue-router';
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const loading = ref(true);
|
|
||||||
const recentVideos = ref<ModelVideo[]>([]);
|
|
||||||
|
|
||||||
// Mock stats data (in real app, fetch from API)
|
|
||||||
const stats = ref({
|
|
||||||
totalVideos: 0,
|
|
||||||
totalViews: 0,
|
|
||||||
storageUsed: 0,
|
|
||||||
storageLimit: 10737418240, // 10GB in bytes
|
|
||||||
uploadsThisMonth: 0
|
|
||||||
});
|
|
||||||
|
|
||||||
const quickActions = [
|
|
||||||
{
|
|
||||||
title: 'Upload Video',
|
|
||||||
description: 'Upload a new video to your library',
|
|
||||||
icon: 'i-heroicons-cloud-arrow-up',
|
|
||||||
color: 'bg-gradient-to-br from-primary/20 to-primary/5',
|
|
||||||
iconColor: 'text-primary',
|
|
||||||
onClick: () => router.push('/upload')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Video Library',
|
|
||||||
description: 'Browse all your videos',
|
|
||||||
icon: 'i-heroicons-film',
|
|
||||||
color: 'bg-gradient-to-br from-blue-100 to-blue-50',
|
|
||||||
iconColor: 'text-blue-600',
|
|
||||||
onClick: () => router.push('/video')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Analytics',
|
|
||||||
description: 'Track performance & insights',
|
|
||||||
icon: 'i-heroicons-chart-bar',
|
|
||||||
color: 'bg-gradient-to-br from-purple-100 to-purple-50',
|
|
||||||
iconColor: 'text-purple-600',
|
|
||||||
onClick: () => {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Manage Plan',
|
|
||||||
description: 'Upgrade or change your plan',
|
|
||||||
icon: 'i-heroicons-credit-card',
|
|
||||||
color: 'bg-gradient-to-br from-orange-100 to-orange-50',
|
|
||||||
iconColor: 'text-orange-600',
|
|
||||||
onClick: () => router.push('/plans')
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const fetchDashboardData = async () => {
|
|
||||||
loading.value = true;
|
|
||||||
try {
|
|
||||||
// Fetch recent videos
|
|
||||||
const response = await client.videos.videosList({ page: 1, limit: 5 });
|
|
||||||
const body = response.data as any;
|
|
||||||
|
|
||||||
if (body.data && Array.isArray(body.data)) {
|
|
||||||
recentVideos.value = body.data;
|
|
||||||
stats.value.totalVideos = body.data.length;
|
|
||||||
} else if (Array.isArray(body)) {
|
|
||||||
recentVideos.value = body;
|
|
||||||
stats.value.totalVideos = body.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate mock stats
|
|
||||||
stats.value.totalViews = recentVideos.value.reduce((sum, v: any) => sum + (v.views || 0), 0);
|
|
||||||
stats.value.storageUsed = recentVideos.value.reduce((sum, v) => sum + (v.size || 0), 0);
|
|
||||||
stats.value.uploadsThisMonth = recentVideos.value.filter(v => {
|
|
||||||
const uploadDate = new Date(v.created_at || '');
|
|
||||||
const now = new Date();
|
|
||||||
return uploadDate.getMonth() === now.getMonth() && uploadDate.getFullYear() === now.getFullYear();
|
|
||||||
}).length;
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to fetch dashboard data:', err);
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatBytes = (bytes: number) => {
|
|
||||||
if (bytes === 0) return '0 B';
|
|
||||||
const k = 1024;
|
|
||||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
||||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
||||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatDuration = (seconds?: number) => {
|
|
||||||
if (!seconds) return '0:00';
|
|
||||||
const m = Math.floor(seconds / 60);
|
|
||||||
const s = Math.floor(seconds % 60);
|
|
||||||
return `${m}:${s.toString().padStart(2, '0')}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatDate = (dateString?: string) => {
|
|
||||||
if (!dateString) return '';
|
|
||||||
return new Date(dateString).toLocaleDateString('en-US', {
|
|
||||||
month: 'short',
|
|
||||||
day: 'numeric',
|
|
||||||
year: 'numeric'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStatusClass = (status?: string) => {
|
|
||||||
switch(status?.toLowerCase()) {
|
|
||||||
case 'ready': return 'bg-green-100 text-green-700';
|
|
||||||
case 'processing': return 'bg-yellow-100 text-yellow-700';
|
|
||||||
case 'failed': return 'bg-red-100 text-red-700';
|
|
||||||
default: return 'bg-gray-100 text-gray-700';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const storagePercentage = computed(() => {
|
|
||||||
return Math.round((stats.value.storageUsed / stats.value.storageLimit) * 100);
|
|
||||||
});
|
|
||||||
|
|
||||||
const storageBreakdown = computed(() => {
|
|
||||||
const videoSize = stats.value.storageUsed;
|
|
||||||
const thumbSize = stats.value.totalVideos * 300 * 1024; // ~300KB per thumbnail
|
|
||||||
const otherSize = stats.value.totalVideos * 100 * 1024; // ~100KB other files
|
|
||||||
const total = videoSize + thumbSize + otherSize;
|
|
||||||
|
|
||||||
return [
|
|
||||||
{ label: 'Videos', size: videoSize, percentage: (videoSize / total) * 100, color: 'bg-primary' },
|
|
||||||
{ label: 'Thumbnails & Assets', size: thumbSize, percentage: (thumbSize / total) * 100, color: 'bg-blue-500' },
|
|
||||||
{ label: 'Other Files', size: otherSize, percentage: (otherSize / total) * 100, color: 'bg-gray-400' },
|
|
||||||
];
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
fetchDashboardData();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="dashboard-overview">
|
|
||||||
<PageHeader
|
|
||||||
title="Dashboard"
|
|
||||||
description="Welcome back! Here's what's happening with your videos."
|
|
||||||
:breadcrumbs="[
|
|
||||||
{ label: 'Dashboard' }
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Loading State -->
|
|
||||||
<div v-if="loading" class="animate-pulse">
|
|
||||||
<!-- Stats Grid Skeleton -->
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
|
||||||
<div v-for="i in 4" :key="i" class="bg-white rounded-xl border border-gray-200 p-6">
|
|
||||||
<div class="flex items-center justify-between mb-4">
|
|
||||||
<div class="space-y-2">
|
|
||||||
<Skeleton width="5rem" height="1rem" class="mb-2 rounded" />
|
|
||||||
<Skeleton width="8rem" height="2rem" class="rounded" />
|
|
||||||
</div>
|
|
||||||
<Skeleton width="3rem" height="3rem" class="rounded-full" />
|
|
||||||
</div>
|
|
||||||
<Skeleton width="4rem" height="1rem" class="rounded" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Quick Actions Skeleton -->
|
|
||||||
<div class="mb-8">
|
|
||||||
<Skeleton width="10rem" height="1.5rem" class="mb-4 rounded" />
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
||||||
<div v-for="i in 4" :key="i" class="p-6 rounded-xl border border-gray-200">
|
|
||||||
<Skeleton width="3rem" height="3rem" class="mb-4 rounded-full" />
|
|
||||||
<Skeleton width="8rem" height="1.25rem" class="mb-2 rounded" />
|
|
||||||
<Skeleton width="100%" height="1rem" class="rounded" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Recent Videos Skeleton -->
|
|
||||||
<div class="mb-8">
|
|
||||||
<div class="flex items-center justify-between mb-4">
|
|
||||||
<Skeleton width="8rem" height="1.5rem" class="rounded" />
|
|
||||||
<Skeleton width="5rem" height="1rem" class="rounded" />
|
|
||||||
</div>
|
|
||||||
<div class="bg-white rounded-xl border border-gray-200 overflow-hidden">
|
|
||||||
<div class="p-4 border-b border-gray-200" v-for="i in 5" :key="i">
|
|
||||||
<div class="flex gap-4">
|
|
||||||
<Skeleton width="4rem" height="2.5rem" class="rounded" />
|
|
||||||
<div class="flex-1 space-y-2">
|
|
||||||
<Skeleton width="30%" height="1rem" class="rounded" />
|
|
||||||
<Skeleton width="20%" height="0.8rem" class="rounded" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else>
|
|
||||||
<!-- Stats Grid -->
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
|
||||||
<StatsCard
|
|
||||||
title="Total Videos"
|
|
||||||
:value="stats.totalVideos"
|
|
||||||
icon="i-heroicons-film"
|
|
||||||
color="primary"
|
|
||||||
:trend="{ value: 12, isPositive: true }"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<StatsCard
|
|
||||||
title="Total Views"
|
|
||||||
:value="stats.totalViews.toLocaleString()"
|
|
||||||
icon="i-heroicons-eye"
|
|
||||||
color="info"
|
|
||||||
:trend="{ value: 8, isPositive: true }"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<StatsCard
|
|
||||||
title="Storage Used"
|
|
||||||
:value="`${formatBytes(stats.storageUsed)} / ${formatBytes(stats.storageLimit)}`"
|
|
||||||
icon="i-heroicons-server"
|
|
||||||
color="warning"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<StatsCard
|
|
||||||
title="Uploads This Month"
|
|
||||||
:value="stats.uploadsThisMonth"
|
|
||||||
icon="i-heroicons-arrow-up-tray"
|
|
||||||
color="success"
|
|
||||||
:trend="{ value: 25, isPositive: true }"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Quick Actions -->
|
|
||||||
<div class="mb-8">
|
|
||||||
<h2 class="text-xl font-semibold mb-4">Quick Actions</h2>
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
||||||
<button
|
|
||||||
v-for="action in quickActions"
|
|
||||||
:key="action.title"
|
|
||||||
@click="action.onClick"
|
|
||||||
:class="[
|
|
||||||
'p-6 rounded-xl text-left transition-all duration-200',
|
|
||||||
'border border-gray-200 hover:border-primary hover:shadow-lg',
|
|
||||||
'group press-animated',
|
|
||||||
action.color
|
|
||||||
]"
|
|
||||||
>
|
|
||||||
<div :class="['w-12 h-12 rounded-lg flex items-center justify-center mb-4 bg-white/80', action.iconColor]">
|
|
||||||
<span :class="[action.icon, 'w-6 h-6']" />
|
|
||||||
</div>
|
|
||||||
<h3 class="font-semibold mb-1 group-hover:text-primary transition-colors">{{ action.title }}</h3>
|
|
||||||
<p class="text-sm text-gray-600">{{ action.description }}</p>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Recent Videos -->
|
|
||||||
<div class="mb-8">
|
|
||||||
<div class="flex items-center justify-between mb-4">
|
|
||||||
<h2 class="text-xl font-semibold">Recent Videos</h2>
|
|
||||||
<router-link
|
|
||||||
to="/video"
|
|
||||||
class="text-sm text-primary hover:underline font-medium flex items-center gap-1"
|
|
||||||
>
|
|
||||||
View all
|
|
||||||
<span class="i-heroicons-arrow-right w-4 h-4" />
|
|
||||||
</router-link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="recentVideos.length === 0" class="bg-white rounded-xl border border-gray-200 p-8 text-center">
|
|
||||||
<div class="w-16 h-16 rounded-full bg-gray-100 flex items-center justify-center mx-auto mb-4">
|
|
||||||
<span class="i-heroicons-film w-8 h-8 text-gray-400" />
|
|
||||||
</div>
|
|
||||||
<p class="text-gray-600 mb-4">No videos yet</p>
|
|
||||||
<router-link
|
|
||||||
to="/upload"
|
|
||||||
class="inline-flex items-center gap-2 px-4 py-2 bg-primary hover:bg-primary-600 text-white rounded-lg font-medium transition-colors"
|
|
||||||
>
|
|
||||||
<span class="i-heroicons-plus w-5 h-5" />
|
|
||||||
Upload your first video
|
|
||||||
</router-link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else class="bg-white rounded-xl border border-gray-200 overflow-hidden">
|
|
||||||
<div class="overflow-x-auto">
|
|
||||||
<table class="w-full">
|
|
||||||
<thead class="bg-gray-50 border-b border-gray-200">
|
|
||||||
<tr>
|
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Video</th>
|
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
|
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Duration</th>
|
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Upload Date</th>
|
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody class="divide-y divide-gray-200">
|
|
||||||
<tr v-for="video in recentVideos" :key="video.id" class="hover:bg-gray-50 transition-colors">
|
|
||||||
<td class="px-6 py-4">
|
|
||||||
<div class="flex items-center gap-3">
|
|
||||||
<div class="w-16 h-10 bg-gray-200 rounded overflow-hidden flex-shrink-0">
|
|
||||||
<img v-if="video.thumbnail" :src="video.thumbnail" :alt="video.title" class="w-full h-full object-cover" />
|
|
||||||
<div v-else class="w-full h-full flex items-center justify-center">
|
|
||||||
<span class="i-heroicons-film text-gray-400 text-xl" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="min-w-0">
|
|
||||||
<p class="font-medium text-gray-900 truncate">{{ video.title }}</p>
|
|
||||||
<p class="text-sm text-gray-500 truncate">{{ video.description || 'No description' }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4">
|
|
||||||
<span :class="['px-2 py-1 text-xs font-medium rounded-full', getStatusClass(video.status)]">
|
|
||||||
{{ video.status || 'Unknown' }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 text-sm text-gray-500">
|
|
||||||
{{ formatDuration(video.duration) }}
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 text-sm text-gray-500">
|
|
||||||
{{ formatDate(video.created_at) }}
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<button class="p-1.5 hover:bg-gray-100 rounded transition-colors" title="Edit">
|
|
||||||
<span class="i-heroicons-pencil w-4 h-4 text-gray-600" />
|
|
||||||
</button>
|
|
||||||
<button class="p-1.5 hover:bg-gray-100 rounded transition-colors" title="Share">
|
|
||||||
<span class="i-heroicons-share w-4 h-4 text-gray-600" />
|
|
||||||
</button>
|
|
||||||
<button class="p-1.5 hover:bg-red-100 rounded transition-colors" title="Delete">
|
|
||||||
<span class="i-heroicons-trash w-4 h-4 text-red-600" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Storage Usage -->
|
|
||||||
<div class="bg-white rounded-xl border border-gray-200 p-6">
|
|
||||||
<h2 class="text-xl font-semibold mb-4">Storage Usage</h2>
|
|
||||||
|
|
||||||
<div class="mb-4">
|
|
||||||
<div class="flex items-center justify-between mb-2">
|
|
||||||
<span class="text-sm font-medium text-gray-700">
|
|
||||||
{{ formatBytes(stats.storageUsed) }} of {{ formatBytes(stats.storageLimit) }} used
|
|
||||||
</span>
|
|
||||||
<span class="text-sm font-medium" :class="storagePercentage > 80 ? 'text-danger' : 'text-gray-700'">
|
|
||||||
{{ storagePercentage }}%
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="h-3 bg-gray-200 rounded-full overflow-hidden">
|
|
||||||
<div
|
|
||||||
class="h-full transition-all duration-500 rounded-full"
|
|
||||||
:class="storagePercentage > 80 ? 'bg-danger' : 'bg-primary'"
|
|
||||||
:style="{ width: `${storagePercentage}%` }"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="space-y-2">
|
|
||||||
<div
|
|
||||||
v-for="item in storageBreakdown"
|
|
||||||
:key="item.label"
|
|
||||||
class="flex items-center justify-between text-sm"
|
|
||||||
>
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<div :class="['w-3 h-3 rounded-sm', item.color]" />
|
|
||||||
<span class="text-gray-700">{{ item.label }}</span>
|
|
||||||
</div>
|
|
||||||
<span class="text-gray-500">{{ formatBytes(item.size) }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="storagePercentage > 80" class="mt-4 p-3 bg-yellow-50 border border-yellow-200 rounded-lg">
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<span class="i-heroicons-exclamation-triangle w-5 h-5 text-yellow-600 flex-shrink-0 mt-0.5" />
|
|
||||||
<div>
|
|
||||||
<p class="text-sm font-medium text-yellow-800">Storage running low</p>
|
|
||||||
<p class="text-sm text-yellow-700 mt-1">
|
|
||||||
Consider upgrading your plan to get more storage.
|
|
||||||
<router-link to="/plans" class="underline font-medium">View plans</router-link>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,64 +1,87 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<Toast />
|
<Toast />
|
||||||
<Form
|
<Form
|
||||||
:initialValues="initialValues"
|
:initial-values="initialValues"
|
||||||
:validators="validators"
|
:resolver="forgotSchema"
|
||||||
@submit="onFormSubmit"
|
class="flex flex-col gap-4 w-full"
|
||||||
class="flex flex-col gap-4 w-full"
|
@submit="onFormSubmit"
|
||||||
>
|
>
|
||||||
<div class="text-sm text-gray-600 mb-2">
|
<template #default="{ form }">
|
||||||
Enter your email address and we'll send you a link to reset your password.
|
<div class="text-sm text-gray-600 mb-2">
|
||||||
</div>
|
Enter your email address and we'll send you a link to reset your password.
|
||||||
|
</div>
|
||||||
|
|
||||||
<Field name="email" label="Email address">
|
<div class="flex flex-col gap-1">
|
||||||
<template #default="{ value, error, isInvalid }">
|
<label for="email" class="text-sm font-medium text-gray-700">Email address</label>
|
||||||
<Input name="email" type="email" placeholder="you@example.com" :modelValue="value" />
|
<Input
|
||||||
<div v-if="isInvalid" class="text-xs text-red-600 mt-1">{{ error }}</div>
|
name="email"
|
||||||
</template>
|
type="email"
|
||||||
</Field>
|
placeholder="you@example.com"
|
||||||
|
fluid
|
||||||
|
/>
|
||||||
|
<Message
|
||||||
|
v-if="form.getFieldMeta('email')?.errorMap?.onChange"
|
||||||
|
severity="error"
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
{{ form.getFieldMeta('email')?.errorMap?.onChange }}
|
||||||
|
</Message>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Button type="submit" label="Send Reset Link" />
|
<Button type="submit" size="sm" fluid>
|
||||||
|
Send Reset Link
|
||||||
|
</Button>
|
||||||
|
|
||||||
<div class="text-center mt-2">
|
<div class="text-center mt-2">
|
||||||
<router-link to="/login" replace
|
<router-link
|
||||||
class="inline-flex items-center text-sm font-medium text-gray-600 hover:text-gray-900 transition-colors">
|
to="/login"
|
||||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
replace
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
class="inline-flex items-center text-sm font-medium text-gray-600 hover:text-gray-900 transition-colors"
|
||||||
d="M10 19l-7-7m0 0l7-7m-7 7h18"></path>
|
>
|
||||||
</svg>
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
Back to Sign in
|
<path
|
||||||
</router-link>
|
stroke-linecap="round"
|
||||||
</div>
|
stroke-linejoin="round"
|
||||||
</Form>
|
stroke-width="2"
|
||||||
</div>
|
d="M10 19l-7-7m0 0l7-7m-7 7h18"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
Back to Sign in
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { client } from '@/api/client';
|
import { client } from '@/api/client'
|
||||||
import { Button, Field, Form, Input, Toast } from '@/components/ui/form';
|
import Form from '@/components/form/Form.vue'
|
||||||
import { inject, reactive } from 'vue';
|
import Message from '@/components/form/Message.vue'
|
||||||
|
import Button from '@/components/ui/Button.vue'
|
||||||
|
import Input from '@/components/ui/Input.vue'
|
||||||
|
import Toast from '@/components/ui/Toast.vue'
|
||||||
|
import { useToast } from '@/composables/useToast'
|
||||||
|
import { reactive } from 'vue'
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
const toast = inject<{ add: (t: any) => void }>('toast');
|
const toast = useToast()
|
||||||
|
|
||||||
|
const forgotSchema = z.object({
|
||||||
|
email: z.string().min(1, { message: 'Email is required.' }).email({ message: 'Invalid email address.' })
|
||||||
|
})
|
||||||
|
|
||||||
const initialValues = reactive({
|
const initialValues = reactive({
|
||||||
email: ''
|
email: ''
|
||||||
});
|
})
|
||||||
|
|
||||||
const validators = {
|
const onFormSubmit = async (values: any) => {
|
||||||
email: [
|
try {
|
||||||
(value: string) => !value ? 'Email is required.' : undefined,
|
await client.auth.forgotPasswordCreate({ email: values.email })
|
||||||
(value: string) => !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) ? 'Invalid email address.' : undefined,
|
toast.success('Reset link sent', 'Success')
|
||||||
],
|
} catch (error: any) {
|
||||||
};
|
toast.error(error.message || 'An error occurred', 'Error')
|
||||||
|
}
|
||||||
const onFormSubmit = (values: Record<string, any>) => {
|
}
|
||||||
client.auth.forgotPasswordCreate({ email: values.email })
|
|
||||||
.then(() => {
|
|
||||||
toast?.add({ severity: 'success', summary: 'Success', detail: 'Reset link sent', life: 3000 });
|
|
||||||
})
|
|
||||||
.catch((error: any) => {
|
|
||||||
toast?.add({ severity: 'error', summary: 'Error', detail: error.message || 'An error occurred', life: 3000 });
|
|
||||||
});
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import vueHead from "@/components/VueHead";
|
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|||||||
@@ -1,118 +1,157 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<Toast />
|
<Toast />
|
||||||
<Form
|
<Form
|
||||||
:initialValues="initialValues"
|
:initial-values="initialValues"
|
||||||
:validators="validators"
|
:resolver="loginSchema"
|
||||||
@submit="onFormSubmit"
|
class="flex flex-col gap-4 w-full"
|
||||||
class="flex flex-col gap-4 w-full"
|
@submit="onFormSubmit"
|
||||||
|
>
|
||||||
|
<template #default="{ form }">
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<label for="email" class="text-sm font-medium text-gray-700">Email</label>
|
||||||
|
<Input
|
||||||
|
name="email"
|
||||||
|
type="text"
|
||||||
|
placeholder="Enter your email"
|
||||||
|
fluid
|
||||||
|
:disabled="auth.loading"
|
||||||
|
/>
|
||||||
|
<Message
|
||||||
|
v-if="form.getFieldMeta('email')?.errorMap?.onChange"
|
||||||
|
severity="error"
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
{{ form.getFieldMeta('email')?.errorMap?.onChange }}
|
||||||
|
</Message>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<label for="password" class="text-sm font-medium text-gray-700">Password</label>
|
||||||
|
<InputPassword
|
||||||
|
name="password"
|
||||||
|
placeholder="Enter your password"
|
||||||
|
:feedback="false"
|
||||||
|
fluid
|
||||||
|
:disabled="auth.loading"
|
||||||
|
/>
|
||||||
|
<Message
|
||||||
|
v-if="form.getFieldMeta('password')?.errorMap?.onChange"
|
||||||
|
severity="error"
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
{{ form.getFieldMeta('password')?.errorMap?.onChange }}
|
||||||
|
</Message>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<Checkbox
|
||||||
|
:model-value="form.getFieldValue('rememberMe')"
|
||||||
|
binary
|
||||||
|
:disabled="auth.loading"
|
||||||
|
@update:model-value="form.setFieldValue('rememberMe', $event)"
|
||||||
|
/>
|
||||||
|
<label for="remember-me" class="text-sm text-gray-900">Remember me</label>
|
||||||
|
</div>
|
||||||
|
<div class="text-sm">
|
||||||
|
<router-link
|
||||||
|
to="/forgot"
|
||||||
|
class="text-blue-600 hover:text-blue-500 hover:underline"
|
||||||
|
>
|
||||||
|
Forgot password?
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
size="sm"
|
||||||
|
:loading="auth.loading"
|
||||||
|
fluid
|
||||||
>
|
>
|
||||||
<Field name="email" label="Email">
|
{{ auth.loading ? 'Signing in...' : 'Sign in' }}
|
||||||
<template #default="{ value, error, isInvalid }">
|
</Button>
|
||||||
<Input
|
|
||||||
name="email"
|
|
||||||
type="text"
|
|
||||||
placeholder="Enter your email"
|
|
||||||
:modelValue="value"
|
|
||||||
:disabled="auth.loading"
|
|
||||||
/>
|
|
||||||
<div v-if="isInvalid" class="text-xs text-red-600 mt-1">{{ error }}</div>
|
|
||||||
</template>
|
|
||||||
</Field>
|
|
||||||
|
|
||||||
<Field name="password" label="Password">
|
<div class="relative">
|
||||||
<template #default="{ value, error, isInvalid }">
|
<div class="absolute inset-0 flex items-center">
|
||||||
<Input
|
<div class="w-full border-t border-gray-300"></div>
|
||||||
name="password"
|
</div>
|
||||||
type="password"
|
<div class="relative flex justify-center text-sm">
|
||||||
placeholder="Enter your password"
|
<span class="px-2 bg-white text-gray-500">Or continue with</span>
|
||||||
:modelValue="value"
|
</div>
|
||||||
:disabled="auth.loading"
|
</div>
|
||||||
/>
|
|
||||||
<div v-if="isInvalid" class="text-xs text-red-600 mt-1">{{ error }}</div>
|
|
||||||
</template>
|
|
||||||
</Field>
|
|
||||||
|
|
||||||
<div class="flex items-center justify-between">
|
<Button
|
||||||
<div class="flex items-center gap-2">
|
size="sm"
|
||||||
<input
|
type="button"
|
||||||
id="remember-me"
|
variant="outline"
|
||||||
type="checkbox"
|
class="w-full flex items-center justify-center gap-2"
|
||||||
v-model="initialValues.rememberMe"
|
:disabled="auth.loading"
|
||||||
class="w-4 h-4 rounded border-gray-300 text-primary focus:ring-primary/20"
|
@click="loginWithGoogle"
|
||||||
/>
|
>
|
||||||
<label for="remember-me" class="text-sm text-gray-900">Remember me</label>
|
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor">
|
||||||
</div>
|
<path
|
||||||
<div class="text-sm">
|
d="M12.545,10.239v3.821h5.445c-0.712,2.315-2.647,3.972-5.445,3.972c-3.332,0-6.033-2.701-6.033-6.032s2.701-6.032,6.033-6.032c1.498,0,2.866,0.549,3.921,1.453l2.814-2.814C17.503,2.988,15.139,2,12.545,2C7.021,2,2.543,6.477,2.543,12s4.478,10,10.002,10c8.396,0,10.249-7.85,9.426-11.748L12.545,10.239z"
|
||||||
<router-link to="/forgot"
|
/>
|
||||||
class="text-blue-600 hover:text-blue-500 hover:underline">Forgot
|
</svg>
|
||||||
password?</router-link>
|
Google
|
||||||
</div>
|
</Button>
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button type="submit" :label="auth.loading ? 'Signing in...' : 'Sign in'" :loading="auth.loading" />
|
<div class="mt-2 flex flex-col items-center justify-center gap-1 text-sm text-gray-600">
|
||||||
|
<p class="text-center text-sm text-gray-600">
|
||||||
<div class="relative">
|
Don't have an account?
|
||||||
<div class="absolute inset-0 flex items-center">
|
<router-link
|
||||||
<div class="w-full border-t border-gray-300"></div>
|
to="/sign-up"
|
||||||
</div>
|
class="font-medium text-blue-600 hover:text-blue-500 hover:underline"
|
||||||
<div class="relative flex justify-center text-sm">
|
>
|
||||||
<span class="px-2 bg-white text-gray-500">Or continue with</span>
|
Sign up
|
||||||
</div>
|
</router-link>
|
||||||
</div>
|
</p>
|
||||||
|
</div>
|
||||||
<Button type="button" variant="outlined" label="Google" :loading="auth.loading" @click="loginWithGoogle">
|
</template>
|
||||||
<template #default>
|
</Form>
|
||||||
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor">
|
</div>
|
||||||
<path
|
|
||||||
d="M12.545,10.239v3.821h5.445c-0.712,2.315-2.647,3.972-5.445,3.972c-3.332,0-6.033-2.701-6.033-6.032s2.701-6.032,6.033-6.032c1.498,0,2.866,0.549,3.921,1.453l2.814-2.814C17.503,2.988,15.139,2,12.545,2C7.021,2,2.543,6.477,2.543,12s4.478,10,10.002,10c8.396,0,10.249-7.85,9.426-11.748L12.545,10.239z" />
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
<div class="mt-2 flex flex-col items-center justify-center gap-1 text-sm text-gray-600">
|
|
||||||
<p class="text-center text-sm text-gray-600">
|
|
||||||
Don't have an account?
|
|
||||||
<router-link to="/sign-up" class="font-medium text-blue-600 hover:text-blue-500 hover:underline">Sign up</router-link>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Button, Field, Form, Input, Toast } from '@/components/ui/form';
|
import Form from '@/components/form/Form.vue'
|
||||||
import { useAuthStore } from '@/stores/auth';
|
import Message from '@/components/form/Message.vue'
|
||||||
import { inject, reactive, watch } from 'vue';
|
import Button from '@/components/ui/Button.vue'
|
||||||
|
import Checkbox from '@/components/ui/Checkbox.vue'
|
||||||
|
import Input from '@/components/ui/Input.vue'
|
||||||
|
import InputPassword from '@/components/ui/InputPassword.vue'
|
||||||
|
import Toast from '@/components/ui/Toast.vue'
|
||||||
|
import { useToast } from '@/composables/useToast'
|
||||||
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
import { reactive, watch } from 'vue'
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore()
|
||||||
const toast = inject<{ add: (t: any) => void }>('toast');
|
const toast = useToast()
|
||||||
|
|
||||||
watch(() => auth.error, (newError) => {
|
const loginSchema = z.object({
|
||||||
if (newError && toast) {
|
email: z.string().min(1, { message: 'Email or username is required.' }),
|
||||||
toast.add({ severity: 'error', summary: String(auth.error), detail: newError, life: 5000 });
|
password: z.string().min(1, { message: 'Password is required.' })
|
||||||
}
|
})
|
||||||
});
|
|
||||||
|
|
||||||
const initialValues = reactive({
|
const initialValues = reactive({
|
||||||
email: '',
|
email: '',
|
||||||
password: '',
|
password: '',
|
||||||
rememberMe: false
|
rememberMe: false
|
||||||
});
|
})
|
||||||
|
|
||||||
const validators = {
|
watch(() => auth.error, (newError) => {
|
||||||
email: [
|
if (newError) {
|
||||||
(value: string) => !value ? 'Email or username is required.' : undefined,
|
toast.error(String(auth.error), 'Error')
|
||||||
],
|
}
|
||||||
password: [
|
})
|
||||||
(value: string) => !value ? 'Password is required.' : undefined,
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const onFormSubmit = async (values: Record<string, any>) => {
|
const onFormSubmit = async (values: any) => {
|
||||||
auth.login(values.email, values.password);
|
await auth.login(values.email, values.password)
|
||||||
};
|
}
|
||||||
|
|
||||||
const loginWithGoogle = () => {
|
const loginWithGoogle = () => {
|
||||||
auth.loginWithGoogle();
|
auth.loginWithGoogle()
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,71 +1,106 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<Form
|
<Form
|
||||||
:initialValues="initialValues"
|
:initial-values="initialValues"
|
||||||
:validators="validators"
|
:resolver="signupSchema"
|
||||||
@submit="onFormSubmit"
|
class="flex flex-col gap-4 w-full"
|
||||||
class="flex flex-col gap-4 w-full"
|
@submit="onFormSubmit"
|
||||||
>
|
>
|
||||||
<Field name="name" label="Full Name">
|
<template #default="{ form }">
|
||||||
<template #default="{ value, error, isInvalid }">
|
<div class="flex flex-col gap-1">
|
||||||
<Input name="name" type="text" placeholder="John Doe" :modelValue="value" />
|
<label for="name" class="text-sm font-medium text-gray-700">Full Name</label>
|
||||||
<div v-if="isInvalid" class="text-xs text-red-600 mt-1">{{ error }}</div>
|
<Input
|
||||||
</template>
|
name="name"
|
||||||
</Field>
|
placeholder="John Doe"
|
||||||
|
fluid
|
||||||
|
/>
|
||||||
|
<Message
|
||||||
|
v-if="form.getFieldMeta('name')?.errorMap?.onChange"
|
||||||
|
severity="error"
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
{{ form.getFieldMeta('name')?.errorMap?.onChange }}
|
||||||
|
</Message>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Field name="email" label="Email address">
|
<div class="flex flex-col gap-1">
|
||||||
<template #default="{ value, error, isInvalid }">
|
<label for="email" class="text-sm font-medium text-gray-700">Email address</label>
|
||||||
<Input name="email" type="email" placeholder="you@example.com" :modelValue="value" />
|
<Input
|
||||||
<div v-if="isInvalid" class="text-xs text-red-600 mt-1">{{ error }}</div>
|
name="email"
|
||||||
</template>
|
type="email"
|
||||||
</Field>
|
placeholder="you@example.com"
|
||||||
|
fluid
|
||||||
|
/>
|
||||||
|
<Message
|
||||||
|
v-if="form.getFieldMeta('email')?.errorMap?.onChange"
|
||||||
|
severity="error"
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
{{ form.getFieldMeta('email')?.errorMap?.onChange }}
|
||||||
|
</Message>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Field name="password" label="Password">
|
<div class="flex flex-col gap-1">
|
||||||
<template #default="{ value, error, isInvalid }">
|
<label for="password" class="text-sm font-medium text-gray-700">Password</label>
|
||||||
<Input name="password" type="password" placeholder="Create a password" :modelValue="value" />
|
<InputPassword
|
||||||
<small class="text-gray-500">Must be at least 8 characters.</small>
|
name="password"
|
||||||
<div v-if="isInvalid" class="text-xs text-red-600 mt-1">{{ error }}</div>
|
placeholder="Create a password"
|
||||||
</template>
|
:feedback="true"
|
||||||
</Field>
|
fluid
|
||||||
|
/>
|
||||||
|
<small class="text-gray-500">Must be at least 8 characters.</small>
|
||||||
|
<Message
|
||||||
|
v-if="form.getFieldMeta('password')?.errorMap?.onChange"
|
||||||
|
severity="error"
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
{{ form.getFieldMeta('password')?.errorMap?.onChange }}
|
||||||
|
</Message>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Button type="submit" label="Create Account" />
|
<Button type="submit" size="sm" fluid>
|
||||||
|
Create Account
|
||||||
|
</Button>
|
||||||
|
|
||||||
<p class="mt-4 text-center text-sm text-gray-600">
|
<p class="mt-4 text-center text-sm text-gray-600">
|
||||||
Already have an account?
|
Already have an account?
|
||||||
<router-link to="/login" class="font-medium text-blue-600 hover:text-blue-500 hover:underline">Sign in</router-link>
|
<router-link
|
||||||
</p>
|
to="/login"
|
||||||
</Form>
|
class="font-medium text-blue-600 hover:text-blue-500 hover:underline"
|
||||||
</div>
|
>
|
||||||
|
Sign in
|
||||||
|
</router-link>
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Button, Field, Form, Input } from '@/components/ui/form';
|
import Form from '@/components/form/Form.vue'
|
||||||
import { useAuthStore } from '@/stores/auth';
|
import Message from '@/components/form/Message.vue'
|
||||||
import { reactive } from 'vue';
|
import Button from '@/components/ui/Button.vue'
|
||||||
|
import Input from '@/components/ui/Input.vue'
|
||||||
|
import InputPassword from '@/components/ui/InputPassword.vue'
|
||||||
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
import { reactive } from 'vue'
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore()
|
||||||
|
|
||||||
|
const signupSchema = z.object({
|
||||||
|
name: z.string().min(1, { message: 'Name is required.' }),
|
||||||
|
email: z.string().min(1, { message: 'Email is required.' }).email({ message: 'Invalid email address.' }),
|
||||||
|
password: z.string().min(8, { message: 'Password must be at least 8 characters.' })
|
||||||
|
})
|
||||||
|
|
||||||
const initialValues = reactive({
|
const initialValues = reactive({
|
||||||
name: '',
|
name: '',
|
||||||
email: '',
|
email: '',
|
||||||
password: ''
|
password: ''
|
||||||
});
|
})
|
||||||
|
|
||||||
const validators = {
|
const onFormSubmit = (values: any) => {
|
||||||
name: [
|
auth.register(values.name, values.email, values.password)
|
||||||
(value: string) => !value ? 'Name is required.' : undefined,
|
}
|
||||||
],
|
|
||||||
email: [
|
|
||||||
(value: string) => !value ? 'Email is required.' : undefined,
|
|
||||||
(value: string) => !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) ? 'Invalid email address.' : undefined,
|
|
||||||
],
|
|
||||||
password: [
|
|
||||||
(value: string) => !value ? 'Password is required.' : undefined,
|
|
||||||
(value: string) => value.length < 8 ? 'Password must be at least 8 characters.' : undefined,
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const onFormSubmit = (values: Record<string, any>) => {
|
|
||||||
auth.register(values.name, values.email, values.password);
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import Chart from '@/components/icons/Chart.vue';
|
|||||||
import Credit from '@/components/icons/Credit.vue';
|
import Credit from '@/components/icons/Credit.vue';
|
||||||
import Upload from '@/components/icons/Upload.vue';
|
import Upload from '@/components/icons/Upload.vue';
|
||||||
import Video from '@/components/icons/Video.vue';
|
import Video from '@/components/icons/Video.vue';
|
||||||
import { Skeleton } from '@/components/ui/form';
|
import Skeleton from '@/components/ui/Skeleton.vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import Referral from './Referral.vue';
|
import Referral from './Referral.vue';
|
||||||
|
|
||||||
@@ -45,19 +45,19 @@ const quickActions = [
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="loading" class="mb-8">
|
<div v-if="loading" class="mb-8">
|
||||||
<Skeleton width="10rem" height="1.5rem" class="mb-4" />
|
<Skeleton width="10rem" height="1.5rem" class="mb-4"></Skeleton>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
<div v-for="i in 4" :key="i" class="p-6 rounded-xl border border-gray-200">
|
<div v-for="i in 4" :key="i" class="p-6 rounded-xl border border-gray-200">
|
||||||
<Skeleton width="3rem" height="3rem" borderRadius="9999px" class="mb-4" />
|
<Skeleton circle width="3rem" height="3rem" class="mb-4"></Skeleton>
|
||||||
<Skeleton width="8rem" height="1.25rem" class="mb-2" />
|
<Skeleton width="8rem" height="1.25rem" class="mb-2"></Skeleton>
|
||||||
<Skeleton width="100%" height="1rem" />
|
<Skeleton width="100%" height="1rem"></Skeleton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col justify-between p-6 rounded-xl border border-gray-200">
|
<div class="flex flex-col justify-between p-6 rounded-xl border border-gray-200">
|
||||||
<Skeleton width="10rem" height="2rem" />
|
<Skeleton width="10rem" height="2rem"></Skeleton>
|
||||||
<Skeleton width="100%" height="1.25rem" class="my-4" />
|
<Skeleton width="100%" height="1.25rem" class="my-4"></Skeleton>
|
||||||
<Skeleton width="100%" height="1rem" />
|
<Skeleton width="100%" height="1rem"></Skeleton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ModelVideo } from '@/api/client';
|
import { ModelVideo } from '@/api/client';
|
||||||
import EmptyState from '@/components/dashboard/EmptyState.vue';
|
import EmptyState from '@/components/dashboard/EmptyState.vue';
|
||||||
import { Skeleton } from '@/components/ui/form';
|
import Skeleton from '@/components/ui/Skeleton.vue';
|
||||||
import { formatDate, formatDuration } from '@/lib/utils';
|
import { formatDate, formatDuration } from '@/lib/utils';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ const getStatusClass = (status?: string) => {
|
|||||||
<div class="bg-white rounded-xl border border-gray-200 overflow-hidden">
|
<div class="bg-white rounded-xl border border-gray-200 overflow-hidden">
|
||||||
<div class="p-4 border-b border-gray-200" v-for="i in 5" :key="i">
|
<div class="p-4 border-b border-gray-200" v-for="i in 5" :key="i">
|
||||||
<div class="flex gap-4">
|
<div class="flex gap-4">
|
||||||
<Skeleton width="4rem" height="2.5rem" class="rounded"></Skeleton>
|
<Skeleton width="4rem" height="2.5rem" border-radius="0.25rem"></Skeleton>
|
||||||
<div class="flex-1 space-y-2">
|
<div class="flex-1 space-y-2">
|
||||||
<Skeleton width="30%" height="1rem"></Skeleton>
|
<Skeleton width="30%" height="1rem"></Skeleton>
|
||||||
<Skeleton width="20%" height="0.8rem"></Skeleton>
|
<Skeleton width="20%" height="0.8rem"></Skeleton>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<p class="text-sm text-gray-600 font-medium">Share your referral link and earn commissions from
|
<p class="text-sm text-gray-600 font-medium">Share your referral link and earn commissions from
|
||||||
referred users!</p>
|
referred users!</p>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<InputText class="w-full" readonly type="text" :value="url" @click="copyToClipboard" />
|
<Input class="w-full" readonly type="text" :value="url" @click="copyToClipboard" />
|
||||||
<button class="btn btn-primary" @click="copyToClipboard" :disabled="isCopied">
|
<button class="btn btn-primary" @click="copyToClipboard" :disabled="isCopied">
|
||||||
<svg v-if="!isCopied" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
|
<svg v-if="!isCopied" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
|
||||||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import Input from '@/components/ui/Input.vue';
|
||||||
import { useAuthStore } from '@/stores/auth';
|
import { useAuthStore } from '@/stores/auth';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import StatsCard from '@/components/dashboard/StatsCard.vue';
|
import StatsCard from '@/components/dashboard/StatsCard.vue';
|
||||||
import { Skeleton } from '@/components/ui/form';
|
import Skeleton from '@/components/ui/Skeleton.vue';
|
||||||
import { formatBytes } from '@/lib/utils';
|
import { formatBytes } from '@/lib/utils';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -22,11 +22,12 @@ defineProps<Props>();
|
|||||||
<div v-for="i in 4" :key="i" class="bg-surface rounded-xl border border-gray-200 p-6">
|
<div v-for="i in 4" :key="i" class="bg-surface rounded-xl border border-gray-200 p-6">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<Skeleton width="5rem" height="1rem" class="mb-2" />
|
<Skeleton width="5rem" height="1rem" class="mb-2"></Skeleton>
|
||||||
<Skeleton width="8rem" height="2rem" />
|
<Skeleton width="8rem" height="2rem"></Skeleton>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- <Skeleton circle width="3rem" height="3rem"></Skeleton> -->
|
||||||
</div>
|
</div>
|
||||||
<Skeleton width="4rem" height="1rem" />
|
<Skeleton width="4rem" height="1rem"></Skeleton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,186 +1,177 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { client, type ModelPlan } from '@/api/client';
|
import { client, type ModelPlan } from '@/api/client'
|
||||||
import PageHeader from '@/components/dashboard/PageHeader.vue';
|
import PageHeader from '@/components/dashboard/PageHeader.vue'
|
||||||
import { useAuthStore } from '@/stores/auth';
|
import { useSWRV } from '@/composables/useDataLoader'
|
||||||
import { computed, onMounted, ref } from 'vue';
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import CurrentPlanCard from './components/CurrentPlanCard.vue';
|
import { computed, ref } from 'vue'
|
||||||
import EditPlanDialog from './components/EditPlanDialog.vue';
|
import CurrentPlanCard from './components/CurrentPlanCard.vue'
|
||||||
import ManageSubscriptionDialog from './components/ManageSubscriptionDialog.vue';
|
import EditPlanDialog from './components/EditPlanDialog.vue'
|
||||||
import PlanList from './components/PlanList.vue';
|
import ManageSubscriptionDialog from './components/ManageSubscriptionDialog.vue'
|
||||||
import PlanPaymentHistory from './components/PlanPaymentHistory.vue';
|
import PlanList from './components/PlanList.vue'
|
||||||
import UsageStatsCard from './components/UsageStatsCard.vue';
|
import PlanPaymentHistory from './components/PlanPaymentHistory.vue'
|
||||||
|
import UsageStatsCard from './components/UsageStatsCard.vue'
|
||||||
|
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore()
|
||||||
const subscribing = ref<string | null>(null);
|
const subscribing = ref<string | null>(null)
|
||||||
const showManageDialog = ref(false);
|
const showManageDialog = ref(false)
|
||||||
const cancelling = ref(false);
|
const cancelling = ref(false)
|
||||||
const isLoading = ref(true);
|
|
||||||
const plansData = ref<any>(null);
|
|
||||||
|
|
||||||
// Mock Payment History Data
|
// Mock Payment History Data
|
||||||
const paymentHistory = ref([
|
const paymentHistory = ref([
|
||||||
{ id: 'inv_001', date: 'Oct 24, 2025', amount: 9.99, plan: 'Basic Plan', status: 'success', invoiceId: 'INV-2025-001' },
|
{ id: 'inv_001', date: 'Oct 24, 2025', amount: 9.99, plan: 'Basic Plan', status: 'success', invoiceId: 'INV-2025-001' },
|
||||||
{ id: 'inv_002', date: 'Nov 24, 2025', amount: 9.99, plan: 'Basic Plan', status: 'success', invoiceId: 'INV-2025-002' },
|
{ id: 'inv_002', date: 'Nov 24, 2025', amount: 9.99, plan: 'Basic Plan', status: 'success', invoiceId: 'INV-2025-002' },
|
||||||
{ id: 'inv_003', date: 'Dec 24, 2025', amount: 19.99, plan: 'Pro Plan', status: 'failed', invoiceId: 'INV-2025-003' },
|
{ id: 'inv_003', date: 'Dec 24, 2025', amount: 19.99, plan: 'Pro Plan', status: 'failed', invoiceId: 'INV-2025-003' },
|
||||||
{ id: 'inv_004', date: 'Jan 24, 2026', amount: 19.99, plan: 'Pro Plan', status: 'pending', invoiceId: 'INV-2026-001' },
|
{ id: 'inv_004', date: 'Jan 24, 2026', amount: 19.99, plan: 'Pro Plan', status: 'pending', invoiceId: 'INV-2026-001' },
|
||||||
]);
|
])
|
||||||
|
|
||||||
const fetchPlans = async () => {
|
const { data, isLoading, mutate: mutatePlans } = useSWRV('r/plans', () => client.plans.plansList())
|
||||||
isLoading.value = true;
|
|
||||||
try {
|
|
||||||
plansData.value = await client.plans.plansList();
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Failed to fetch plans', e);
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
fetchPlans();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Computed Usage (Mock if not in store)
|
// Computed Usage (Mock if not in store)
|
||||||
const storageUsed = computed(() => auth.user?.storage_used || 0); // bytes
|
const storageUsed = computed(() => auth.user?.storage_used || 0) // bytes
|
||||||
const storageLimit = computed(() => 10737418240);
|
const storageLimit = computed(() => 10737418240)
|
||||||
const uploadsUsed = ref(12);
|
const uploadsUsed = ref(12)
|
||||||
const uploadsLimit = ref(50);
|
const uploadsLimit = ref(50)
|
||||||
|
|
||||||
const currentPlanId = computed(() => {
|
const currentPlanId = computed(() => {
|
||||||
if (auth.user?.plan_id) return auth.user.plan_id;
|
if (auth.user?.plan_id) return auth.user.plan_id
|
||||||
if (Array.isArray(plansData.value?.data?.data?.plans) && plansData.value?.data?.data?.plans.length > 0) return plansData.value.data.data.plans[0].id;
|
if (Array.isArray(data.value?.data?.data?.plans) && data.value?.data?.data?.plans.length > 0) return data.value.data.data.plans[0].id
|
||||||
return undefined;
|
return undefined
|
||||||
});
|
})
|
||||||
|
|
||||||
const currentPlan = computed(() => {
|
const currentPlan = computed(() => {
|
||||||
if (!Array.isArray(plansData.value?.data?.data?.plans)) return undefined;
|
if (!Array.isArray(data.value?.data?.data?.plans)) return undefined
|
||||||
return plansData.value.data.data.plans.find((p: ModelPlan) => p.id === currentPlanId.value);
|
return data.value.data.data.plans.find((p: ModelPlan) => p.id === currentPlanId.value)
|
||||||
});
|
})
|
||||||
|
|
||||||
const showEditDialog = ref(false);
|
const showEditDialog = ref(false)
|
||||||
const editingPlan = ref<ModelPlan>({});
|
const editingPlan = ref<ModelPlan>({} as ModelPlan)
|
||||||
const isSaving = ref(false);
|
const isSaving = ref(false)
|
||||||
|
|
||||||
const openEditPlan = (plan: ModelPlan) => {
|
const openEditPlan = (plan: ModelPlan) => {
|
||||||
editingPlan.value = { ...plan };
|
editingPlan.value = { ...plan }
|
||||||
showEditDialog.value = true;
|
showEditDialog.value = true
|
||||||
};
|
}
|
||||||
|
|
||||||
const savePlan = async (updatedPlan: ModelPlan) => {
|
const savePlan = async (updatedPlan: ModelPlan) => {
|
||||||
isSaving.value = true;
|
isSaving.value = true
|
||||||
try {
|
try {
|
||||||
if (!updatedPlan.id) return;
|
if (!updatedPlan.id) return
|
||||||
|
|
||||||
await client.request({
|
await client.request({
|
||||||
path: `/plans/${updatedPlan.id}`,
|
path: `/plans/${updatedPlan.id}`,
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: updatedPlan
|
body: updatedPlan
|
||||||
});
|
})
|
||||||
|
|
||||||
await fetchPlans();
|
await mutatePlans()
|
||||||
showEditDialog.value = false;
|
|
||||||
alert('Plan updated successfully');
|
showEditDialog.value = false
|
||||||
} catch (e: any) {
|
alert('Plan updated successfully')
|
||||||
console.error('Failed to update plan', e);
|
} catch (e: any) {
|
||||||
showEditDialog.value = false;
|
console.error('Failed to update plan', e)
|
||||||
} finally {
|
const idx = data.value!.data.data.plans.findIndex((p: ModelPlan) => p.id === updatedPlan.id)
|
||||||
isSaving.value = false;
|
if (idx !== -1) {
|
||||||
|
data.value!.data.data.plans[idx] = { ...updatedPlan }
|
||||||
}
|
}
|
||||||
};
|
showEditDialog.value = false
|
||||||
|
} finally {
|
||||||
|
isSaving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const subscribe = async (plan: ModelPlan) => {
|
const subscribe = async (plan: ModelPlan) => {
|
||||||
if (!plan.id) return;
|
if (!plan.id) return
|
||||||
subscribing.value = plan.id;
|
subscribing.value = plan.id
|
||||||
try {
|
try {
|
||||||
await client.payments.paymentsCreate({
|
await client.payments.paymentsCreate({
|
||||||
amount: plan.price || 0,
|
amount: plan.price || 0,
|
||||||
plan_id: plan.id
|
plan_id: plan.id
|
||||||
});
|
})
|
||||||
alert(`Successfully subscribed to ${plan.name}`);
|
alert(`Successfully subscribed to ${plan.name}`)
|
||||||
|
|
||||||
paymentHistory.value.unshift({
|
paymentHistory.value.unshift({
|
||||||
id: `inv_${Date.now()}`,
|
id: `inv_${Date.now()}`,
|
||||||
date: new Date().toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }),
|
date: new Date().toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }),
|
||||||
amount: plan.price || 0,
|
amount: plan.price || 0,
|
||||||
plan: plan.name || 'Unknown',
|
plan: plan.name || 'Unknown',
|
||||||
status: 'success',
|
status: 'success',
|
||||||
invoiceId: `INV-${new Date().getFullYear()}-${Math.floor(Math.random() * 1000)}`
|
invoiceId: `INV-${new Date().getFullYear()}-${Math.floor(Math.random() * 1000)}`
|
||||||
});
|
})
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error(err);
|
console.error(err)
|
||||||
alert('Failed to subscribe: ' + (err.message || 'Unknown error'));
|
alert('Failed to subscribe: ' + (err.message || 'Unknown error'))
|
||||||
} finally {
|
} finally {
|
||||||
subscribing.value = null;
|
subscribing.value = null
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const cancelSubscription = async () => {
|
const cancelSubscription = async () => {
|
||||||
cancelling.value = true;
|
cancelling.value = true
|
||||||
try {
|
try {
|
||||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
await new Promise(resolve => setTimeout(resolve, 1500))
|
||||||
alert('Subscription has been canceled.');
|
alert('Subscription has been canceled.')
|
||||||
showManageDialog.value = false;
|
showManageDialog.value = false
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert('Failed to cancel subscription.');
|
alert('Failed to cancel subscription.')
|
||||||
} finally {
|
} finally {
|
||||||
cancelling.value = false;
|
cancelling.value = false
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="plans-page">
|
<div class="plans-page">
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="Subscription"
|
title="Subscription"
|
||||||
description="Manage your workspace plan and usage"
|
description="Manage your workspace plan and usage"
|
||||||
:breadcrumbs="[
|
:breadcrumbs="[
|
||||||
{ label: 'Dashboard', to: '/' },
|
{ label: 'Dashboard', to: '/' },
|
||||||
{ label: 'Subscription' }
|
{ label: 'Subscription' }
|
||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="content max-w-7xl mx-auto space-y-12 pb-12">
|
<div class="content max-w-7xl mx-auto space-y-12 pb-12">
|
||||||
|
|
||||||
<!-- Hero Section: Current Plan & Usage -->
|
<!-- Hero Section: Current Plan & Usage -->
|
||||||
<div v-if="!isLoading" class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
<div v-if="!isLoading" class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
<CurrentPlanCard
|
<CurrentPlanCard
|
||||||
:current-plan="currentPlan"
|
:current-plan="currentPlan"
|
||||||
@manage="showManageDialog = true"
|
@manage="showManageDialog = true"
|
||||||
/>
|
|
||||||
|
|
||||||
<UsageStatsCard
|
|
||||||
:storage-used="storageUsed"
|
|
||||||
:storage-limit="storageLimit"
|
|
||||||
:uploads-used="uploadsUsed"
|
|
||||||
:uploads-limit="uploadsLimit"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<PlanList
|
|
||||||
:plans="plansData?.data?.data?.plans || []"
|
|
||||||
:is-loading="!!isLoading"
|
|
||||||
:current-plan-id="currentPlanId"
|
|
||||||
:subscribing-plan-id="subscribing"
|
|
||||||
:is-admin="auth.user?.role === 'admin'"
|
|
||||||
@subscribe="subscribe"
|
|
||||||
@edit="openEditPlan"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PlanPaymentHistory :history="paymentHistory" />
|
<UsageStatsCard
|
||||||
|
:storage-used="storageUsed"
|
||||||
<ManageSubscriptionDialog
|
:storage-limit="storageLimit"
|
||||||
v-model:visible="showManageDialog"
|
:uploads-used="uploadsUsed"
|
||||||
:current-plan="currentPlan"
|
:uploads-limit="uploadsLimit"
|
||||||
:cancelling="cancelling"
|
|
||||||
@cancel-subscription="cancelSubscription"
|
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<PlanList
|
||||||
|
:plans="data?.data?.data?.plans || []"
|
||||||
|
:is-loading="!!isLoading"
|
||||||
|
:current-plan-id="currentPlanId"
|
||||||
|
:subscribing-plan-id="subscribing"
|
||||||
|
:is-admin="auth.user?.role === 'admin'"
|
||||||
|
@subscribe="subscribe"
|
||||||
|
@edit="openEditPlan"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PlanPaymentHistory :history="paymentHistory" />
|
||||||
|
|
||||||
|
<ManageSubscriptionDialog
|
||||||
|
v-model:visible="showManageDialog"
|
||||||
|
:current-plan="currentPlan"
|
||||||
|
:cancelling="cancelling"
|
||||||
|
@cancel-subscription="cancelSubscription"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<EditPlanDialog
|
<EditPlanDialog
|
||||||
v-model:visible="showEditDialog"
|
v-model:visible="showEditDialog"
|
||||||
:plan="editingPlan"
|
:plan="editingPlan"
|
||||||
:loading="isSaving"
|
:loading="isSaving"
|
||||||
@save="savePlan"
|
@save="savePlan"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,38 +1,44 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type ModelPlan } from '@/api/client';
|
import type { ModelPlan } from '@/api/client';
|
||||||
import { Button, Tag } from '@/components/ui/form';
|
import Button from '@/components/ui/Button.vue';
|
||||||
|
import Tag from '@/components/ui/Tag.vue';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
defineProps<{
|
const props = defineProps<{
|
||||||
currentPlan?: ModelPlan;
|
currentPlan?: ModelPlan
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'manage'): void;
|
(e: 'manage'): void
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
|
const planName = computed(() => props.currentPlan?.name || 'Free Plan')
|
||||||
|
const planPrice = computed(() => props.currentPlan?.price || 0)
|
||||||
|
const planCycle = computed(() => props.currentPlan?.cycle || 'month')
|
||||||
|
const isActive = computed(() => props.currentPlan?.is_active !== false)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="lg:col-span-2 relative overflow-hidden rounded-2xl bg-gradient-to-br from-gray-900 to-gray-800 text-white p-8">
|
<div class="bg-gradient-to-br from-blue-600 to-purple-700 rounded-2xl p-8 text-white">
|
||||||
<!-- Background decorations -->
|
<div class="flex items-start justify-between mb-4">
|
||||||
<div class="absolute top-0 right-0 -mt-16 -mr-16 w-64 h-64 bg-primary-500 rounded-full blur-3xl opacity-20"></div>
|
<div>
|
||||||
<div class="absolute bottom-0 left-0 -mb-16 -ml-16 w-64 h-64 bg-purple-500 rounded-full blur-3xl opacity-20"></div>
|
<p class="text-blue-100 text-sm font-medium mb-1">Current Plan</p>
|
||||||
|
<h3 class="text-3xl font-bold">{{ planName }}</h3>
|
||||||
<div class="relative z-10 flex flex-col h-full justify-between">
|
</div>
|
||||||
<div class="flex justify-between items-start">
|
<Tag
|
||||||
<div>
|
:value="isActive ? 'Active' : 'Inactive'"
|
||||||
<h2 class="text-sm font-medium text-gray-400 uppercase tracking-wider mb-1">Current Plan</h2>
|
:severity="isActive ? 'success' : 'danger'"
|
||||||
<h3 class="text-4xl font-bold text-white mb-2">{{ currentPlan?.name || 'Standard Plan' }}</h3>
|
class="!bg-white/20 !text-white !border-white/30"
|
||||||
<Tag value="Active" severity="success" />
|
/>
|
||||||
</div>
|
|
||||||
<div class="text-right">
|
|
||||||
<div class="text-3xl font-bold text-white">${{ currentPlan?.price || 0 }}<span class="text-lg text-gray-400 font-normal">/mo</span></div>
|
|
||||||
<p class="text-gray-400 text-sm mt-1">Next billing on Feb 24, 2026</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-8 pt-8 border-t border-gray-700/50 flex gap-4">
|
|
||||||
<Button label="Manage Subscription" variant="secondary" @click="$emit('manage')" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-baseline gap-1 mb-6">
|
||||||
|
<span class="text-4xl font-bold">${{ planPrice }}</span>
|
||||||
|
<span class="text-blue-100">/{{ planCycle }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button variant="outline" class="!text-white !border-white/50 hover:!bg-white/20" @click="emit('manage')">
|
||||||
|
Manage Subscription
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,132 +1,107 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type ModelPlan } from '@/api/client';
|
import { type ModelPlan } from '@/api/client'
|
||||||
import { Button, Dialog } from '@/components/ui/form';
|
import Button from '@/components/ui/Button.vue'
|
||||||
import { computed, ref, watch } from 'vue';
|
import Checkbox from '@/components/ui/Checkbox.vue'
|
||||||
|
import Dialog from '@/components/ui/Dialog.vue'
|
||||||
|
import Input from '@/components/ui/Input.vue'
|
||||||
|
import { computed, ref, watch } from 'vue'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
visible: boolean;
|
visible: boolean
|
||||||
plan: ModelPlan;
|
plan: ModelPlan
|
||||||
loading?: boolean;
|
loading?: boolean
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'update:visible', value: boolean): void;
|
(e: 'update:visible', value: boolean): void
|
||||||
(e: 'save', plan: ModelPlan): void;
|
(e: 'save', plan: ModelPlan): void
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
// Create a local copy to edit
|
// Create a local copy to edit
|
||||||
const localPlan = ref<ModelPlan>({});
|
const localPlan = ref<ModelPlan>({} as ModelPlan)
|
||||||
|
|
||||||
// Sync when dialog opens or plan changes
|
// Sync when dialog opens or plan changes
|
||||||
watch(() => props.plan, (newPlan) => {
|
watch(() => props.plan, (newPlan) => {
|
||||||
localPlan.value = { ...newPlan };
|
localPlan.value = { ...newPlan }
|
||||||
}, { immediate: true });
|
}, { immediate: true })
|
||||||
|
|
||||||
const onSave = () => {
|
const onSave = () => {
|
||||||
emit('save', localPlan.value);
|
emit('save', localPlan.value)
|
||||||
};
|
}
|
||||||
|
|
||||||
const visibleModel = computed({
|
const handleClose = () => {
|
||||||
get: () => props.visible,
|
emit('update:visible', false)
|
||||||
set: (val) => emit('update:visible', val)
|
}
|
||||||
});
|
|
||||||
|
const isActive = computed({
|
||||||
|
get: () => localPlan.value.is_active ?? false,
|
||||||
|
set: (val: boolean) => {
|
||||||
|
localPlan.value.is_active = val
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Dialog v-model:visible="visibleModel" header="Edit Plan" :style="{ width: '40rem' }">
|
<Dialog
|
||||||
<div class="space-y-4">
|
:visible="visible"
|
||||||
<div class="flex flex-col gap-2">
|
header="Edit Plan"
|
||||||
<label for="plan-name" class="text-sm font-medium text-gray-700">Name</label>
|
width="40rem"
|
||||||
<input
|
:closable="true"
|
||||||
id="plan-name"
|
@update:visible="handleClose"
|
||||||
v-model="localPlan.name"
|
>
|
||||||
type="text"
|
<div class="space-y-4">
|
||||||
placeholder="Plan Name"
|
<div class="flex flex-col gap-2">
|
||||||
class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary"
|
<label for="plan-name" class="text-sm font-medium text-gray-700">Name</label>
|
||||||
/>
|
<Input id="plan-name" v-model="localPlan.name" placeholder="Plan Name" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<label for="plan-price" class="text-sm font-medium text-gray-700">Price ($)</label>
|
<label for="plan-price" class="text-sm font-medium text-gray-700">Price ($)</label>
|
||||||
<input
|
<Input id="plan-price" :model-value="localPlan.price ?? ''" type="number" placeholder="0.00" @update:model-value="localPlan.price = Number($event)" />
|
||||||
id="plan-price"
|
|
||||||
v-model="localPlan.price"
|
|
||||||
type="number"
|
|
||||||
placeholder="Price"
|
|
||||||
class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
<label for="plan-cycle" class="text-sm font-medium text-gray-700">Billing Cycle</label>
|
|
||||||
<input
|
|
||||||
id="plan-cycle"
|
|
||||||
v-model="localPlan.cycle"
|
|
||||||
type="text"
|
|
||||||
placeholder="e.g. month, year"
|
|
||||||
class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
<label for="plan-desc" class="text-sm font-medium text-gray-700">Description</label>
|
|
||||||
<textarea
|
|
||||||
id="plan-desc"
|
|
||||||
v-model="localPlan.description"
|
|
||||||
rows="2"
|
|
||||||
placeholder="Description"
|
|
||||||
class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary resize-none"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-4">
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
<label for="plan-storage" class="text-sm font-medium text-gray-700">Storage Limit (bytes)</label>
|
|
||||||
<input
|
|
||||||
id="plan-storage"
|
|
||||||
v-model="localPlan.storage_limit"
|
|
||||||
type="number"
|
|
||||||
placeholder="Storage limit"
|
|
||||||
class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
<label for="plan-uploads" class="text-sm font-medium text-gray-700">Upload Limit (per day)</label>
|
|
||||||
<input
|
|
||||||
id="plan-uploads"
|
|
||||||
v-model="localPlan.upload_limit"
|
|
||||||
type="number"
|
|
||||||
placeholder="Upload limit"
|
|
||||||
class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
<label for="plan-duration" class="text-sm font-medium text-gray-700">Duration Limit (sec)</label>
|
|
||||||
<input
|
|
||||||
id="plan-duration"
|
|
||||||
v-model="localPlan.duration_limit"
|
|
||||||
type="number"
|
|
||||||
placeholder="Duration limit"
|
|
||||||
class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-center gap-2 pt-2">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="plan-active"
|
|
||||||
v-model="localPlan.is_active"
|
|
||||||
class="w-4 h-4 rounded border-gray-300"
|
|
||||||
/>
|
|
||||||
<label for="plan-active" class="text-sm font-medium text-gray-700">Active</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<label for="plan-cycle" class="text-sm font-medium text-gray-700">Billing Cycle</label>
|
||||||
|
<Input id="plan-cycle" v-model="localPlan.cycle" placeholder="e.g. month, year" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<template #footer>
|
<div class="flex flex-col gap-2">
|
||||||
<Button variant="secondary" label="Cancel" @click="visibleModel = false" />
|
<label for="plan-desc" class="text-sm font-medium text-gray-700">Description</label>
|
||||||
<Button label="Save Changes" @click="onSave" :loading="loading" />
|
<textarea
|
||||||
</template>
|
id="plan-desc"
|
||||||
</Dialog>
|
v-model="localPlan.description"
|
||||||
|
rows="2"
|
||||||
|
class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<label for="plan-storage" class="text-sm font-medium text-gray-700">Storage Limit (bytes)</label>
|
||||||
|
<Input id="plan-storage" :model-value="localPlan.storage_limit ?? ''" type="number" @update:model-value="localPlan.storage_limit = Number($event)" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<label for="plan-uploads" class="text-sm font-medium text-gray-700">Upload Limit (per day)</label>
|
||||||
|
<Input id="plan-uploads" :model-value="localPlan.upload_limit ?? ''" type="number" @update:model-value="localPlan.upload_limit = Number($event)" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<label for="plan-duration" class="text-sm font-medium text-gray-700">Duration Limit (sec)</label>
|
||||||
|
<Input id="plan-duration" :model-value="localPlan.duration_limit ?? ''" type="number" @update:model-value="localPlan.duration_limit = Number($event)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-2 pt-2">
|
||||||
|
<Checkbox v-model="isActive" :binary="true" />
|
||||||
|
<label class="text-sm font-medium text-gray-700">Active</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex justify-end gap-2">
|
||||||
|
<Button variant="outline" @click="handleClose">Cancel</Button>
|
||||||
|
<Button :loading="loading" @click="onSave">Save Changes</Button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,54 +1,55 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type ModelPlan } from '@/api/client';
|
import type { ModelPlan } from '@/api/client';
|
||||||
import { Button, Dialog } from '@/components/ui/form';
|
import Button from '@/components/ui/Button.vue';
|
||||||
import { computed } from 'vue';
|
import Dialog from '@/components/ui/Dialog.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
visible: boolean;
|
visible: boolean
|
||||||
currentPlan?: ModelPlan;
|
currentPlan?: ModelPlan
|
||||||
cancelling?: boolean;
|
cancelling?: boolean
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'update:visible', value: boolean): void;
|
(e: 'update:visible', value: boolean): void
|
||||||
(e: 'cancel-subscription'): void;
|
(e: 'cancel-subscription'): void
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
const visibleModel = computed({
|
const handleClose = () => {
|
||||||
get: () => props.visible,
|
emit('update:visible', false)
|
||||||
set: (val) => emit('update:visible', val)
|
}
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Dialog v-model:visible="visibleModel" header="Manage Subscription" :style="{ width: '30rem' }">
|
<Dialog
|
||||||
<div class="mb-4">
|
:visible="visible"
|
||||||
<p class="text-gray-600 mb-4">You are currently subscribed to <span class="font-bold text-gray-900">{{ currentPlan?.name }}</span>.</p>
|
header="Manage Subscription"
|
||||||
<div class="bg-gray-50 p-4 rounded-lg space-y-2 border border-gray-200">
|
width="28rem"
|
||||||
<div class="flex justify-between">
|
:closable="true"
|
||||||
<span class="text-sm text-gray-500">Status</span>
|
@update:visible="handleClose"
|
||||||
<span class="text-sm font-medium text-green-600">Active</span>
|
>
|
||||||
</div>
|
<div class="space-y-4">
|
||||||
<div class="flex justify-between">
|
<div v-if="currentPlan" class="bg-gray-50 rounded-lg p-4">
|
||||||
<span class="text-sm text-gray-500">Renewal Date</span>
|
<h4 class="font-medium text-gray-900">{{ currentPlan.name }}</h4>
|
||||||
<span class="text-sm font-medium text-gray-900">Feb 24, 2026</span>
|
<p class="text-sm text-gray-500">${{ currentPlan.price }}/{{ currentPlan.cycle }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between">
|
|
||||||
<span class="text-sm text-gray-500">Amount</span>
|
<div class="border-t border-gray-200 pt-4">
|
||||||
<span class="text-sm font-medium text-gray-900">${{ currentPlan?.price || 0 }}/mo</span>
|
<h4 class="font-medium text-gray-900 mb-2">Cancel Subscription</h4>
|
||||||
</div>
|
<p class="text-sm text-gray-600 mb-4">
|
||||||
</div>
|
If you cancel, you'll lose access to premium features at the end of your billing period.
|
||||||
</div>
|
|
||||||
<p class="text-sm text-gray-600 mb-6">
|
|
||||||
Canceling your subscription will downgrade you to the Free plan at the end of your current billing period.
|
|
||||||
</p>
|
</p>
|
||||||
<div class="flex justify-end gap-2">
|
<Button
|
||||||
<Button variant="secondary" label="Close" @click="visibleModel = false" />
|
variant="danger"
|
||||||
<Button
|
:loading="cancelling"
|
||||||
label="Cancel Subscription"
|
@click="emit('cancel-subscription')"
|
||||||
@click="emit('cancel-subscription')"
|
>
|
||||||
:disabled="cancelling"
|
Cancel Subscription
|
||||||
/>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<Button variant="outline" @click="handleClose">Close</Button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,101 +1,99 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type ModelPlan } from '@/api/client';
|
import type { ModelPlan } from '@/api/client';
|
||||||
import { Button, Skeleton } from '@/components/ui/form';
|
import Button from '@/components/ui/Button.vue';
|
||||||
|
import Skeleton from '@/components/ui/Skeleton.vue';
|
||||||
import { formatBytes } from '@/lib/utils';
|
import { formatBytes } from '@/lib/utils';
|
||||||
|
|
||||||
defineProps<{
|
const props = defineProps<{
|
||||||
plans: ModelPlan[];
|
plans: ModelPlan[]
|
||||||
isLoading: boolean;
|
isLoading: boolean
|
||||||
currentPlanId?: string;
|
currentPlanId?: string
|
||||||
subscribingPlanId?: string | null;
|
subscribingPlanId?: string | null
|
||||||
isAdmin?: boolean;
|
isAdmin?: boolean
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'subscribe', plan: ModelPlan): void;
|
(e: 'subscribe', plan: ModelPlan): void
|
||||||
(e: 'edit', plan: ModelPlan): void;
|
(e: 'edit', plan: ModelPlan): void
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
const formatDuration = (seconds?: number) => {
|
|
||||||
if (!seconds) return '0 mins';
|
|
||||||
return `${Math.floor(seconds / 60)} mins`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const isPopular = (plan: ModelPlan) => {
|
|
||||||
return plan.name?.toLowerCase().includes('pro') || plan.name?.toLowerCase().includes('premium');
|
|
||||||
};
|
|
||||||
|
|
||||||
const isCurrentComp = (plan: ModelPlan, currentId?: string) => {
|
|
||||||
return plan.id === currentId;
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section>
|
<section>
|
||||||
<div class="flex items-center justify-between mb-8">
|
<h2 class="text-2xl font-bold mb-6 text-gray-900">Available Plans</h2>
|
||||||
<h2 class="text-2xl font-bold text-gray-900">Upgrade your workspace</h2>
|
|
||||||
</div>
|
<!-- Loading State -->
|
||||||
|
<div v-if="isLoading" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
<!-- Loading State -->
|
<div v-for="i in 3" :key="i" class="bg-white border border-gray-200 rounded-xl p-6">
|
||||||
<div v-if="isLoading" class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
<Skeleton height="1.5rem" width="60%" class="mb-4" />
|
||||||
<div v-for="i in 3" :key="i" class="h-full">
|
<Skeleton height="2rem" width="40%" class="mb-6" />
|
||||||
<Skeleton height="300px" borderRadius="16px" />
|
<Skeleton height="1rem" class="mb-2" />
|
||||||
</div>
|
<Skeleton height="1rem" class="mb-2" />
|
||||||
|
<Skeleton height="1rem" width="80%" class="mb-6" />
|
||||||
|
<Skeleton height="2.5rem" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Plans Grid -->
|
||||||
|
<div v-else class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
<div
|
||||||
|
v-for="plan in plans"
|
||||||
|
:key="plan.id"
|
||||||
|
class="bg-white border rounded-xl p-6 transition-all hover:shadow-lg"
|
||||||
|
:class="plan.id === currentPlanId ? 'border-blue-500 ring-2 ring-blue-500/20' : 'border-gray-200'"
|
||||||
|
>
|
||||||
|
<div class="flex items-start justify-between mb-4">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg font-bold text-gray-900">{{ plan.name }}</h3>
|
||||||
|
<p class="text-sm text-gray-500">{{ plan.cycle }}</p>
|
||||||
|
</div>
|
||||||
|
<div v-if="plan.id === currentPlanId" class="text-xs font-medium text-blue-600 bg-blue-50 px-2 py-1 rounded-full">
|
||||||
|
Current
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="grid grid-cols-1 md:grid-cols-3 gap-8 items-start">
|
<div class="mb-6">
|
||||||
<div v-for="plan in plans" :key="plan.id" class="relative group h-full">
|
<span class="text-3xl font-bold text-gray-900">${{ plan.price }}</span>
|
||||||
<div v-if="isPopular(plan) && !isCurrentComp(plan, currentPlanId)" class="absolute -top-3 left-1/2 -translate-x-1/2 bg-primary text-white text-xs font-bold px-3 py-1 rounded-full z-10 shadow-md uppercase tracking-wide">
|
<span class="text-gray-500">/{{ plan.cycle }}</span>
|
||||||
Recommended
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Admin Edit Button -->
|
|
||||||
<Button
|
|
||||||
v-if="isAdmin"
|
|
||||||
class="absolute top-2 right-2 z-20"
|
|
||||||
variant="secondary"
|
|
||||||
@click.stop="emit('edit', plan)"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div :class="[
|
|
||||||
'relative bg-white rounded-2xl p-6 h-full border transition-all duration-200 flex flex-col',
|
|
||||||
isCurrentComp(plan, currentPlanId) ? 'border-primary ring-1 ring-primary/50 bg-primary-50/10' : 'border-gray-200 hover:border-gray-300 hover:shadow-lg',
|
|
||||||
isPopular(plan) && !isCurrentComp(plan, currentPlanId) ? 'shadow-md border-primary/20' : ''
|
|
||||||
]">
|
|
||||||
<div class="mb-4">
|
|
||||||
<h3 class="text-xl font-bold text-gray-900">{{ plan.name }}</h3>
|
|
||||||
<p class="text-gray-500 text-sm min-h-[2.5rem] mt-2">{{ plan.description }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-6">
|
|
||||||
<span class="text-4xl font-bold text-gray-900">${{ plan.price }}</span>
|
|
||||||
<span class="text-gray-500 text-sm">/{{ plan.cycle }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul class="space-y-3 mb-8 flex-grow">
|
|
||||||
<li class="flex items-center gap-3 text-sm text-gray-700">
|
|
||||||
<span class="i-heroicons-check-circle text-green-500 text-lg flex-shrink-0"></span>
|
|
||||||
{{ formatBytes(plan.storage_limit || 0) }} Storage
|
|
||||||
</li>
|
|
||||||
<li class="flex items-center gap-3 text-sm text-gray-700">
|
|
||||||
<span class="i-heroicons-check-circle text-green-500 text-lg flex-shrink-0"></span>
|
|
||||||
{{ formatDuration(plan.duration_limit) }} Max Duration
|
|
||||||
</li>
|
|
||||||
<li class="flex items-center gap-3 text-sm text-gray-700">
|
|
||||||
<span class="i-heroicons-check-circle text-green-500 text-lg flex-shrink-0"></span>
|
|
||||||
{{ plan.upload_limit }} Uploads / day
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
:label="isCurrentComp(plan, currentPlanId) ? 'Current Plan' : (subscribingPlanId === plan.id ? 'Processing...' : 'Upgrade')"
|
|
||||||
class="w-full"
|
|
||||||
:variant="isCurrentComp(plan, currentPlanId) ? 'outlined' : 'primary'"
|
|
||||||
:disabled="!!subscribingPlanId || isCurrentComp(plan, currentPlanId)"
|
|
||||||
@click="emit('subscribe', plan)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
|
||||||
|
<p class="text-sm text-gray-600 mb-6">{{ plan.description }}</p>
|
||||||
|
|
||||||
|
<div class="space-y-2 mb-6 text-sm">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="i-heroicons-check-circle text-green-500 w-4 h-4" />
|
||||||
|
<span>{{ formatBytes(plan.storage_limit || 0) }} storage</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="i-heroicons-check-circle text-green-500 w-4 h-4" />
|
||||||
|
<span>{{ plan.upload_limit }} uploads/day</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="i-heroicons-check-circle text-green-500 w-4 h-4" />
|
||||||
|
<span>{{ plan.duration_limit }}s video duration</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<Button
|
||||||
|
v-if="isAdmin"
|
||||||
|
variant="outline"
|
||||||
|
class="flex-1"
|
||||||
|
@click="emit('edit', plan)"
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
class="flex-1"
|
||||||
|
:variant="plan.id === currentPlanId ? 'outline' : 'primary'"
|
||||||
|
:loading="subscribingPlanId === plan.id"
|
||||||
|
:disabled="plan.id === currentPlanId"
|
||||||
|
@click="emit('subscribe', plan)"
|
||||||
|
>
|
||||||
|
{{ plan.id === currentPlanId ? 'Current Plan' : 'Subscribe' }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,83 +1,78 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Tag } from '@/components/ui/form';
|
import { createColumnHelper } from '@/components/table/Column'
|
||||||
import { inject } from 'vue';
|
import DataTable from '@/components/table/DataTable.vue'
|
||||||
|
import Tag from '@/components/ui/Tag.vue'
|
||||||
|
import Toast from '@/components/ui/Toast.vue'
|
||||||
|
import { useToast } from '@/composables/useToast'
|
||||||
|
import { h } from 'vue'
|
||||||
|
|
||||||
interface PaymentHistoryItem {
|
interface PaymentHistoryItem {
|
||||||
id: string;
|
id: string
|
||||||
date: string;
|
date: string
|
||||||
amount: number;
|
amount: number
|
||||||
plan: string;
|
plan: string
|
||||||
status: string;
|
status: string
|
||||||
invoiceId: string;
|
invoiceId: string
|
||||||
}
|
}
|
||||||
|
|
||||||
defineProps<{
|
const props = defineProps<{
|
||||||
history: PaymentHistoryItem[];
|
history: PaymentHistoryItem[]
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
const getStatusSeverity = (status: string): 'success' | 'error' | 'warn' | 'info' | 'secondary' => {
|
const toast = useToast()
|
||||||
switch (status) {
|
|
||||||
case 'success':
|
|
||||||
return 'success';
|
|
||||||
case 'failed':
|
|
||||||
return 'error';
|
|
||||||
case 'pending':
|
|
||||||
return 'warn';
|
|
||||||
default:
|
|
||||||
return 'info';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const toast = inject<{ add: (t: any) => void }>('toast');
|
const getStatusSeverity = (status: string) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'success':
|
||||||
|
return 'success' as const
|
||||||
|
case 'failed':
|
||||||
|
return 'danger' as const
|
||||||
|
case 'pending':
|
||||||
|
return 'warning' as const
|
||||||
|
default:
|
||||||
|
return 'info' as const
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const columnHelper = createColumnHelper<PaymentHistoryItem>()
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
columnHelper.accessor('date', {
|
||||||
|
header: 'Date',
|
||||||
|
cell: ({ getValue }) => h('span', { class: 'font-medium' }, getValue()),
|
||||||
|
enableSorting: true
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('amount', {
|
||||||
|
header: 'Amount',
|
||||||
|
cell: ({ getValue }) => h('span', {}, `$${getValue()}`)
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('plan', {
|
||||||
|
header: 'Plan'
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('status', {
|
||||||
|
header: 'Status',
|
||||||
|
cell: ({ getValue }) => h(Tag, {
|
||||||
|
value: getValue(),
|
||||||
|
severity: getStatusSeverity(getValue())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
]
|
||||||
|
|
||||||
const downloadInvoice = (item: PaymentHistoryItem) => {
|
const downloadInvoice = (item: PaymentHistoryItem) => {
|
||||||
toast?.add({
|
toast.info(`Downloading invoice #${item.invoiceId}...`, 'Downloading')
|
||||||
severity: 'info',
|
|
||||||
summary: 'Downloading',
|
|
||||||
detail: `Downloading invoice #${item.invoiceId}...`,
|
|
||||||
life: 2000
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
toast?.add({
|
toast.success(`Invoice #${item.invoiceId} downloaded successfully`, 'Downloaded')
|
||||||
severity: 'success',
|
}, 1500)
|
||||||
summary: 'Downloaded',
|
}
|
||||||
detail: `Invoice #${item.invoiceId} downloaded successfully`,
|
|
||||||
life: 3000
|
|
||||||
});
|
|
||||||
}, 1500);
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section>
|
<section>
|
||||||
<h2 class="text-2xl font-bold mb-6 text-gray-900">Billing History</h2>
|
<Toast />
|
||||||
<div class="bg-white border border-gray-200 rounded-xl overflow-hidden">
|
<h2 class="text-2xl font-bold mb-6 text-gray-900">Billing History</h2>
|
||||||
<div class="overflow-x-auto">
|
<div class="bg-white border border-gray-200 rounded-xl overflow-hidden">
|
||||||
<table class="w-full">
|
<DataTable :data="history" :columns="columns" />
|
||||||
<thead>
|
</div>
|
||||||
<tr class="border-b border-gray-200 bg-gray-50">
|
</section>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date</th>
|
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Amount</th>
|
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Plan</th>
|
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody class="divide-y divide-gray-100">
|
|
||||||
<tr v-for="item in history" :key="item.id">
|
|
||||||
<td class="px-4 py-3 text-sm font-medium text-gray-900">{{ item.date }}</td>
|
|
||||||
<td class="px-4 py-3 text-sm text-gray-900">${{ item.amount }}</td>
|
|
||||||
<td class="px-4 py-3 text-sm text-gray-500">{{ item.plan }}</td>
|
|
||||||
<td class="px-4 py-3">
|
|
||||||
<Tag :value="item.status" :severity="getStatusSeverity(item.status)" />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="history.length === 0">
|
|
||||||
<td colspan="4" class="px-4 py-8 text-center text-gray-500">No payment history found.</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,39 +1,43 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ProgressBar } from '@/components/ui/form';
|
import ProgressBar from '@/components/ui/ProgressBar.vue';
|
||||||
import { formatBytes } from '@/lib/utils';
|
import { formatBytes } from '@/lib/utils';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
storageUsed: number;
|
storageUsed: number
|
||||||
storageLimit: number;
|
storageLimit: number
|
||||||
uploadsUsed: number;
|
uploadsUsed: number
|
||||||
uploadsLimit: number;
|
uploadsLimit: number
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
const storagePercentage = computed(() => Math.min(Math.round((props.storageUsed / props.storageLimit) * 100), 100));
|
const storagePercentage = computed(() => Math.min(Math.round((props.storageUsed / props.storageLimit) * 100), 100))
|
||||||
const uploadsPercentage = computed(() => Math.min(Math.round((props.uploadsUsed / props.uploadsLimit) * 100), 100));
|
const uploadsPercentage = computed(() => Math.min(Math.round((props.uploadsUsed / props.uploadsLimit) * 100), 100))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="bg-white border border-gray-200 rounded-2xl p-8 flex flex-col justify-center">
|
<div class="bg-white border border-gray-200 rounded-2xl p-8 flex flex-col justify-center">
|
||||||
<h3 class="text-lg font-bold text-gray-900 mb-6">Usage Statistics</h3>
|
<h3 class="text-lg font-bold text-gray-900 mb-6">Usage Statistics</h3>
|
||||||
|
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<div class="flex justify-between text-sm mb-2">
|
<div class="flex justify-between text-sm mb-2">
|
||||||
<span class="text-gray-600 font-medium">Storage</span>
|
<span class="text-gray-600 font-medium">Storage</span>
|
||||||
<span class="text-gray-900 font-bold">{{ storagePercentage }}%</span>
|
<span class="text-gray-900 font-bold">{{ storagePercentage }}%</span>
|
||||||
</div>
|
</div>
|
||||||
<ProgressBar :value="storagePercentage" />
|
<ProgressBar
|
||||||
<p class="text-xs text-gray-500 mt-2">{{ formatBytes(storageUsed) }} of {{ formatBytes(storageLimit) }} used</p>
|
:value="storagePercentage"
|
||||||
</div>
|
:show-value="false"
|
||||||
|
:color="storagePercentage > 90 ? 'danger' : 'primary'"
|
||||||
<div>
|
/>
|
||||||
<div class="flex justify-between text-sm mb-2">
|
<p class="text-xs text-gray-500 mt-2">{{ formatBytes(storageUsed) }} of {{ formatBytes(storageLimit) }} used</p>
|
||||||
<span class="text-gray-600 font-medium">Monthly Uploads</span>
|
|
||||||
<span class="text-gray-900 font-bold">{{ uploadsPercentage }}%</span>
|
|
||||||
</div>
|
|
||||||
<ProgressBar :value="uploadsPercentage" />
|
|
||||||
<p class="text-xs text-gray-500 mt-2">{{ uploadsUsed }} of {{ uploadsLimit }} uploads</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="flex justify-between text-sm mb-2">
|
||||||
|
<span class="text-gray-600 font-medium">Monthly Uploads</span>
|
||||||
|
<span class="text-gray-900 font-bold">{{ uploadsPercentage }}%</span>
|
||||||
|
</div>
|
||||||
|
<ProgressBar :value="uploadsPercentage" :show-value="false" />
|
||||||
|
<p class="text-xs text-gray-500 mt-2">{{ uploadsUsed }} of {{ uploadsLimit }} uploads</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,107 +1,93 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import PageHeader from '@/components/dashboard/PageHeader.vue';
|
import PageHeader from '@/components/dashboard/PageHeader.vue'
|
||||||
import { useAuthStore } from '@/stores/auth';
|
import Toast from '@/components/ui/Toast.vue'
|
||||||
import { computed, inject, ref } from 'vue';
|
import { useToast } from '@/composables/useToast'
|
||||||
import AccountStatusCard from './components/AccountStatusCard.vue';
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import ChangePasswordDialog from './components/ChangePasswordDialog.vue';
|
import { computed, ref } from 'vue'
|
||||||
import LinkedAccountsCard from './components/LinkedAccountsCard.vue';
|
import AccountStatusCard from './components/AccountStatusCard.vue'
|
||||||
import ProfileHero from './components/ProfileHero.vue';
|
import ChangePasswordDialog from './components/ChangePasswordDialog.vue'
|
||||||
import ProfileInfoCard from './components/ProfileInfoCard.vue';
|
import LinkedAccountsCard from './components/LinkedAccountsCard.vue'
|
||||||
|
import ProfileHero from './components/ProfileHero.vue'
|
||||||
|
import ProfileInfoCard from './components/ProfileInfoCard.vue'
|
||||||
|
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore()
|
||||||
const toast = inject<{ add: (t: any) => void }>('toast');
|
const toast = useToast()
|
||||||
|
|
||||||
// Dialog visibility
|
// Dialog visibility
|
||||||
const showPasswordDialog = ref(false);
|
const showPasswordDialog = ref(false)
|
||||||
|
|
||||||
// Refs for dialog components
|
// Refs for dialog components
|
||||||
const passwordDialogRef = ref<any>();
|
const passwordDialogRef = ref<InstanceType<typeof ChangePasswordDialog>>()
|
||||||
|
|
||||||
// Computed storage values
|
// Computed storage values
|
||||||
const storageUsed = computed(() => auth.user?.storage_used || 0);
|
const storageUsed = computed(() => auth.user?.storage_used || 0)
|
||||||
const storageLimit = computed(() => 10737418240); // 10GB default
|
const storageLimit = computed(() => 10737418240) // 10GB default
|
||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
const handleEditSave = async (data: { username: string; email: string }) => {
|
const handleEditSave = async (data: { username: string; email: string }) => {
|
||||||
try {
|
try {
|
||||||
await auth.updateProfile(data);
|
await auth.updateProfile(data)
|
||||||
toast?.add({
|
toast.success('Your profile has been updated successfully.', 'Profile Updated')
|
||||||
severity: 'success',
|
} catch (e) {
|
||||||
summary: 'Profile Updated',
|
toast.error(auth.error || 'Failed to update profile.', 'Update Failed')
|
||||||
detail: 'Your profile has been updated successfully.',
|
}
|
||||||
life: 3000
|
}
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
toast?.add({
|
|
||||||
severity: 'error',
|
|
||||||
summary: 'Update Failed',
|
|
||||||
detail: auth.error || 'Failed to update profile.',
|
|
||||||
life: 5000
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePasswordSave = async (data: { currentPassword: string; newPassword: string }) => {
|
const handlePasswordSave = async (data: { currentPassword: string; newPassword: string }) => {
|
||||||
try {
|
try {
|
||||||
await auth.changePassword(data.currentPassword, data.newPassword);
|
await auth.changePassword(data.currentPassword, data.newPassword)
|
||||||
showPasswordDialog.value = false;
|
showPasswordDialog.value = false
|
||||||
toast?.add({
|
toast.success('Your password has been changed successfully.', 'Password Changed')
|
||||||
severity: 'success',
|
} catch (e: any) {
|
||||||
summary: 'Password Changed',
|
passwordDialogRef.value?.setError(e.message || 'Failed to change password')
|
||||||
detail: 'Your password has been changed successfully.',
|
}
|
||||||
life: 3000
|
}
|
||||||
});
|
|
||||||
} catch (e: any) {
|
|
||||||
if (passwordDialogRef.value?.setError) {
|
|
||||||
passwordDialogRef.value.setError(e.message || 'Failed to change password');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="profile-page">
|
<div class="profile-page">
|
||||||
<PageHeader
|
<Toast />
|
||||||
title="Profile Settings"
|
<PageHeader
|
||||||
description="Manage your account information and preferences."
|
title="Profile Settings"
|
||||||
:breadcrumbs="[
|
description="Manage your account information and preferences."
|
||||||
{ label: 'Dashboard', to: '/' },
|
:breadcrumbs="[
|
||||||
{ label: 'Profile' }
|
{ label: 'Dashboard', to: '/' },
|
||||||
]"
|
{ label: 'Profile' }
|
||||||
/>
|
]"
|
||||||
|
/>
|
||||||
<div class="max-w-5xl mx-auto space-y-8 pb-12">
|
|
||||||
<!-- Hero Identity Card -->
|
<div class="max-w-5xl mx-auto space-y-8 pb-12">
|
||||||
<ProfileHero
|
<!-- Hero Identity Card -->
|
||||||
:user="auth.user"
|
<ProfileHero
|
||||||
@logout="auth.logout()"
|
:user="auth.user"
|
||||||
/>
|
@logout="auth.logout()"
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||||
<!-- Personal Info -->
|
<!-- Personal Info -->
|
||||||
<div class="md:col-span-2">
|
<div class="md:col-span-2">
|
||||||
<ProfileInfoCard
|
<ProfileInfoCard
|
||||||
:user="auth.user"
|
:user="auth.user"
|
||||||
@change-password="showPasswordDialog = true"
|
@change-password="showPasswordDialog = true"
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Stats Side -->
|
|
||||||
<div class="md:col-span-1 space-y-6">
|
|
||||||
<AccountStatusCard
|
|
||||||
:storage-used="storageUsed"
|
|
||||||
:storage-limit="storageLimit"
|
|
||||||
/>
|
|
||||||
<LinkedAccountsCard />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Dialogs -->
|
<!-- Stats Side -->
|
||||||
<ChangePasswordDialog
|
<div class="md:col-span-1 space-y-6">
|
||||||
ref="passwordDialogRef"
|
<AccountStatusCard
|
||||||
v-model:visible="showPasswordDialog"
|
:storage-used="storageUsed"
|
||||||
@save="handlePasswordSave"
|
:storage-limit="storageLimit"
|
||||||
/>
|
/>
|
||||||
|
<LinkedAccountsCard />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Dialogs -->
|
||||||
|
<ChangePasswordDialog
|
||||||
|
ref="passwordDialogRef"
|
||||||
|
v-model:visible="showPasswordDialog"
|
||||||
|
@save="handlePasswordSave"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,47 +1,37 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ProgressBar } from '@/components/ui/form';
|
import ProgressBar from '@/components/ui/ProgressBar.vue';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
interface Props {
|
||||||
storageUsed: number;
|
storageUsed?: number
|
||||||
storageLimit: number;
|
storageTotal?: number
|
||||||
}>();
|
}
|
||||||
|
|
||||||
const storagePercentage = computed(() =>
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
Math.min(Math.round((props.storageUsed / props.storageLimit) * 100), 100)
|
storageUsed: 0,
|
||||||
);
|
storageTotal: 100
|
||||||
|
})
|
||||||
|
|
||||||
const formatBytes = (bytes: number) => {
|
const usagePercentage = computed(() => {
|
||||||
if (bytes === 0) return '0 B';
|
if (props.storageTotal === 0) return 0
|
||||||
const k = 1024;
|
return Math.round((props.storageUsed / props.storageTotal) * 100)
|
||||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
})
|
||||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
||||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="bg-white border border-gray-200 rounded-2xl p-6">
|
<div class="bg-white rounded-xl p-6 border border-gray-200">
|
||||||
<h3 class="text-lg font-bold text-gray-900 mb-4">Account Status</h3>
|
<h3 class="text-lg font-semibold text-gray-900 mb-4">Account Status</h3>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<div class="flex justify-between text-sm mb-2">
|
<div class="flex justify-between text-sm mb-2">
|
||||||
<span class="text-gray-600">Storage Used</span>
|
<span class="text-gray-600">Storage Usage</span>
|
||||||
<span class="font-bold text-gray-900">{{ storagePercentage }}%</span>
|
<span class="font-medium text-gray-900">{{ usagePercentage }}%</span>
|
||||||
</div>
|
|
||||||
<ProgressBar :value="storagePercentage" />
|
|
||||||
<p class="text-xs text-gray-500 mt-2">{{ formatBytes(storageUsed) }} of {{ formatBytes(storageLimit) }} used</p>
|
|
||||||
</div>
|
|
||||||
<div class="bg-green-50 rounded-lg p-4 border border-green-100 flex items-start gap-3">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-green-600 mt-0.5 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
|
|
||||||
<polyline points="22 4 12 14.01 9 11.01"/>
|
|
||||||
</svg>
|
|
||||||
<div>
|
|
||||||
<h4 class="font-bold text-green-800 text-sm">Account Active</h4>
|
|
||||||
<p class="text-green-600 text-xs mt-0.5">Your subscription is in good standing.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<ProgressBar :value="usagePercentage" :show-value="false" />
|
||||||
|
<p class="text-xs text-gray-500 mt-2">
|
||||||
|
{{ storageUsed }} GB of {{ storageTotal }} GB used
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,116 +1,132 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Button, Dialog, Input } from '@/components/ui/form';
|
import Button from '@/components/ui/Button.vue';
|
||||||
|
import Dialog from '@/components/ui/Dialog.vue';
|
||||||
|
import Input from '@/components/ui/Input.vue';
|
||||||
import { computed, ref, watch } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
visible: boolean;
|
visible: boolean
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
'update:visible': [value: boolean];
|
'update:visible': [value: boolean]
|
||||||
save: [data: { currentPassword: string; newPassword: string }];
|
save: [data: { currentPassword: string; newPassword: string }]
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
const currentPassword = ref('');
|
const currentPassword = ref('')
|
||||||
const newPassword = ref('');
|
const newPassword = ref('')
|
||||||
const confirmPassword = ref('');
|
const confirmPassword = ref('')
|
||||||
const loading = ref(false);
|
const loading = ref(false)
|
||||||
const error = ref('');
|
const error = ref('')
|
||||||
|
|
||||||
watch(() => props.visible, (val) => {
|
watch(() => props.visible, (val) => {
|
||||||
if (val) {
|
if (val) {
|
||||||
currentPassword.value = '';
|
currentPassword.value = ''
|
||||||
newPassword.value = '';
|
newPassword.value = ''
|
||||||
confirmPassword.value = '';
|
confirmPassword.value = ''
|
||||||
error.value = '';
|
error.value = ''
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
const isValid = computed(() => {
|
const isValid = computed(() => {
|
||||||
return currentPassword.value.length >= 1
|
return currentPassword.value.length >= 1
|
||||||
&& newPassword.value.length >= 6
|
&& newPassword.value.length >= 6
|
||||||
&& newPassword.value === confirmPassword.value;
|
&& newPassword.value === confirmPassword.value
|
||||||
});
|
})
|
||||||
|
|
||||||
const passwordMismatch = computed(() => {
|
const passwordMismatch = computed(() => {
|
||||||
return confirmPassword.value.length > 0 && newPassword.value !== confirmPassword.value;
|
return confirmPassword.value.length > 0 && newPassword.value !== confirmPassword.value
|
||||||
});
|
})
|
||||||
|
|
||||||
const passwordTooShort = computed(() => {
|
const passwordTooShort = computed(() => {
|
||||||
return newPassword.value.length > 0 && newPassword.value.length < 6;
|
return newPassword.value.length > 0 && newPassword.value.length < 6
|
||||||
});
|
})
|
||||||
|
|
||||||
|
const newPasswordInvalidClass = computed(() => passwordTooShort.value ? 'border-red-500 focus:border-red-500 focus:ring-red-500' : '')
|
||||||
|
const confirmPasswordInvalidClass = computed(() => passwordMismatch.value ? 'border-red-500 focus:border-red-500 focus:ring-red-500' : '')
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
if (!isValid.value) return;
|
if (!isValid.value) return
|
||||||
loading.value = true;
|
loading.value = true
|
||||||
error.value = '';
|
error.value = ''
|
||||||
emit('save', {
|
emit('save', {
|
||||||
currentPassword: currentPassword.value,
|
currentPassword: currentPassword.value,
|
||||||
newPassword: newPassword.value
|
newPassword: newPassword.value
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
emit('update:visible', false);
|
emit('update:visible', false)
|
||||||
};
|
}
|
||||||
|
|
||||||
// Expose methods for parent to control loading state
|
// Expose methods for parent to control loading state
|
||||||
defineExpose({
|
defineExpose({
|
||||||
setLoading: (val: boolean) => { loading.value = val; },
|
setLoading: (val: boolean) => { loading.value = val },
|
||||||
setError: (msg: string) => { error.value = msg; loading.value = false; }
|
setError: (msg: string) => { error.value = msg; loading.value = false }
|
||||||
});
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Dialog
|
<Dialog
|
||||||
:visible="visible"
|
:visible="visible"
|
||||||
@update:visible="emit('update:visible', $event)"
|
header="Change Password"
|
||||||
header="Change Password"
|
width="28rem"
|
||||||
:style="{ width: '28rem' }"
|
:closable="true"
|
||||||
:closable="true"
|
:draggable="false"
|
||||||
>
|
@update:visible="handleClose"
|
||||||
<div class="space-y-6 pt-2">
|
>
|
||||||
<div v-if="error" class="p-3 bg-red-50 border border-red-200 rounded-lg text-red-800 text-sm">
|
<div class="space-y-6 pt-2">
|
||||||
{{ error }}
|
<div
|
||||||
</div>
|
v-if="error"
|
||||||
|
class="p-3 bg-red-50 border border-red-200 rounded-lg text-red-700 text-sm"
|
||||||
<div class="flex flex-col gap-2">
|
>
|
||||||
<label for="current-password" class="text-sm font-medium text-gray-700">Current Password</label>
|
{{ error }}
|
||||||
<Input
|
</div>
|
||||||
id="current-password"
|
|
||||||
v-model="currentPassword"
|
<div class="flex flex-col gap-2">
|
||||||
type="password"
|
<label for="current-password" class="text-sm font-medium text-gray-700">Current Password</label>
|
||||||
placeholder="Enter current password"
|
<Input
|
||||||
/>
|
id="current-password"
|
||||||
</div>
|
v-model="currentPassword"
|
||||||
|
type="password"
|
||||||
<div class="flex flex-col gap-2">
|
placeholder="Enter current password"
|
||||||
<label for="new-password" class="text-sm font-medium text-gray-700">New Password</label>
|
/>
|
||||||
<Input
|
</div>
|
||||||
id="new-password"
|
|
||||||
v-model="newPassword"
|
<div class="flex flex-col gap-2">
|
||||||
type="password"
|
<label for="new-password" class="text-sm font-medium text-gray-700">New Password</label>
|
||||||
placeholder="Enter new password (min 6 characters)"
|
<Input
|
||||||
/>
|
id="new-password"
|
||||||
<small v-if="passwordTooShort" class="text-red-500">Password must be at least 6 characters</small>
|
v-model="newPassword"
|
||||||
</div>
|
type="password"
|
||||||
|
placeholder="Enter new password (min 6 characters)"
|
||||||
<div class="flex flex-col gap-2">
|
:class="newPasswordInvalidClass"
|
||||||
<label for="confirm-password" class="text-sm font-medium text-gray-700">Confirm New Password</label>
|
/>
|
||||||
<Input
|
<small v-if="passwordTooShort" class="text-red-500">Password must be at least 6 characters</small>
|
||||||
id="confirm-password"
|
</div>
|
||||||
v-model="confirmPassword"
|
|
||||||
type="password"
|
<div class="flex flex-col gap-2">
|
||||||
placeholder="Confirm new password"
|
<label for="confirm-password" class="text-sm font-medium text-gray-700">Confirm New Password</label>
|
||||||
/>
|
<Input
|
||||||
<small v-if="passwordMismatch" class="text-red-500">Passwords do not match</small>
|
id="confirm-password"
|
||||||
</div>
|
v-model="confirmPassword"
|
||||||
</div>
|
type="password"
|
||||||
<template #footer>
|
placeholder="Confirm new password"
|
||||||
<div class="flex justify-end gap-3 pt-4">
|
:class="confirmPasswordInvalidClass"
|
||||||
<Button variant="secondary" label="Cancel" @click="handleClose" :disabled="loading" />
|
/>
|
||||||
<Button label="Change Password" @click="handleSave" :loading="loading" :disabled="!isValid" />
|
<small v-if="passwordMismatch" class="text-red-500">Passwords do not match</small>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
</Dialog>
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex justify-end gap-3 pt-4">
|
||||||
|
<Button variant="outline" :disabled="loading" @click="handleClose">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button :loading="loading" :disabled="!isValid" @click="handleSave">
|
||||||
|
Change Password
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,25 +1,24 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Tag } from '@/components/ui/form';
|
import Tag from '@/components/ui/Tag.vue';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="bg-white border border-gray-200 rounded-2xl p-6">
|
<div class="bg-white rounded-xl p-6 border border-gray-200">
|
||||||
<h3 class="text-lg font-bold text-gray-900 mb-4">Linked Accounts</h3>
|
<h3 class="text-lg font-semibold text-gray-900 mb-4">Linked Accounts</h3>
|
||||||
<div class="space-y-3">
|
<div class="space-y-4">
|
||||||
<div class="flex items-center justify-between p-3 rounded-lg border border-gray-100 hover:border-gray-200 transition-colors">
|
<!-- Placeholder for linked accounts -->
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center justify-between py-2">
|
||||||
<div class="w-8 h-8 rounded-full bg-red-100 flex items-center justify-center">
|
<div class="flex items-center gap-3">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 text-red-600" viewBox="0 0 24 24">
|
<div class="w-10 h-10 rounded-full bg-gray-100 flex items-center justify-center">
|
||||||
<path fill="currentColor" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
|
<span class="i-heroicons-envelope text-gray-600" />
|
||||||
<path fill="currentColor" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
|
</div>
|
||||||
<path fill="currentColor" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
|
<div>
|
||||||
<path fill="currentColor" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
|
<p class="font-medium text-gray-900">Email</p>
|
||||||
</svg>
|
<p class="text-sm text-gray-500">Connected</p>
|
||||||
</div>
|
</div>
|
||||||
<span class="font-medium text-gray-700">Google</span>
|
|
||||||
</div>
|
|
||||||
<Tag value="Connected" severity="success" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<Tag value="Active" severity="success" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,70 +1,44 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ModelUser } from '@/api/client';
|
import type { ModelUser } from '@/api/client'
|
||||||
import { Avatar, Button, Tag } from '@/components/ui/form';
|
import Avatar from '@/components/ui/Avatar.vue'
|
||||||
import { computed } from 'vue';
|
import Button from '@/components/ui/Button.vue'
|
||||||
|
import Tag from '@/components/ui/Tag.vue'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
const props = defineProps<{
|
interface Props {
|
||||||
user: ModelUser | null;
|
user: ModelUser | null
|
||||||
}>();
|
}
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const props = defineProps<Props>()
|
||||||
logout: [];
|
|
||||||
changePassword: [];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const joinDate = computed(() => {
|
const displayName = computed(() => {
|
||||||
return new Date(props.user?.created_at || Date.now()).toLocaleDateString('en-US', {
|
return props.user?.username || 'User'
|
||||||
year: 'numeric',
|
})
|
||||||
month: 'long',
|
|
||||||
day: 'numeric'
|
const displayEmail = computed(() => {
|
||||||
});
|
return props.user?.email || 'No email'
|
||||||
});
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="relative overflow-hidden rounded-2xl bg-gradient-to-r from-gray-900 via-gray-800 to-gray-900 text-white p-8 md:p-10">
|
<div class="bg-gradient-to-r from-blue-600 to-purple-600 rounded-xl p-6 text-white">
|
||||||
<!-- Background decorations -->
|
<div class="flex items-center gap-4">
|
||||||
<div class="absolute top-0 right-0 -mt-20 -mr-20 w-80 h-80 bg-primary-500 rounded-full mix-blend-overlay filter blur-3xl"></div>
|
<Avatar
|
||||||
<div class="absolute bottom-0 left-0 -mb-20 -ml-20 w-80 h-80 bg-purple-500 rounded-full mix-blend-overlay filter blur-3xl"></div>
|
:label="displayName"
|
||||||
|
size="xl"
|
||||||
<div class="relative z-10 flex flex-col md:flex-row items-center gap-8">
|
shape="circle"
|
||||||
<div class="relative">
|
class="border-4 border-white/30"
|
||||||
<div class="absolute inset-0 bg-primary-500 rounded-full blur-lg opacity-40"></div>
|
/>
|
||||||
<Avatar
|
<div class="flex-1">
|
||||||
size="large"
|
<div class="flex items-center gap-2 mb-1">
|
||||||
shape="circle"
|
<h2 class="text-2xl font-bold">{{ displayName }}</h2>
|
||||||
label=""
|
<Tag value="Active" severity="success" class="!bg-white/20 !text-white !border-white/30" />
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-center md:text-left space-y-2 flex-grow">
|
|
||||||
<div class="flex flex-col md:flex-row items-center gap-3 justify-center md:justify-start">
|
|
||||||
<h2 class="text-3xl font-bold text-white">{{ user?.username || 'User' }}</h2>
|
|
||||||
<Tag :value="user?.role || 'User'" severity="info" />
|
|
||||||
</div>
|
|
||||||
<p class="text-gray-400 text-lg">{{ user?.email }}</p>
|
|
||||||
<p class="text-gray-500 text-sm flex items-center justify-center md:justify-start gap-2">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<rect width="18" height="18" x="3" y="4" rx="2" ry="2"/>
|
|
||||||
<line x1="16" x2="16" y1="2" y2="6"/>
|
|
||||||
<line x1="8" x2="8" y1="2" y2="6"/>
|
|
||||||
<line x1="3" x2="21" y1="10" y2="10"/>
|
|
||||||
</svg>
|
|
||||||
Member since {{ joinDate }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex gap-3">
|
|
||||||
<Button label="Logout" @click="emit('logout')">
|
|
||||||
<template #default>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 mr-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/>
|
|
||||||
<polyline points="16 17 21 12 16 7"/>
|
|
||||||
<line x1="21" x2="9" y1="12" y2="12"/>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<p class="text-white/80">{{ displayEmail }}</p>
|
||||||
|
</div>
|
||||||
|
<Button variant="ghost" class="!text-white hover:!bg-white/20">
|
||||||
|
Edit Profile
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,69 +1,81 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ModelUser } from '@/api/client';
|
import type { ModelUser } from '@/api/client'
|
||||||
import { Button } from '@/components/ui/form';
|
import Button from '@/components/ui/Button.vue'
|
||||||
|
import Input from '@/components/ui/Input.vue'
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
|
||||||
defineProps<{
|
interface Props {
|
||||||
user: ModelUser | null;
|
user: ModelUser | null
|
||||||
}>();
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
edit: [];
|
save: [data: { username: string; email: string }]
|
||||||
changePassword: [];
|
}>()
|
||||||
}>();
|
|
||||||
|
const isEditing = ref(false)
|
||||||
|
const username = ref('')
|
||||||
|
const email = ref('')
|
||||||
|
|
||||||
|
watch(() => props.user, (newUser) => {
|
||||||
|
if (newUser) {
|
||||||
|
username.value = newUser.username || ''
|
||||||
|
email.value = newUser.email || ''
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
emit('save', { username: username.value, email: email.value })
|
||||||
|
isEditing.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
username.value = props.user?.username || ''
|
||||||
|
email.value = props.user?.email || ''
|
||||||
|
isEditing.value = false
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="bg-white border border-gray-200 rounded-2xl p-8">
|
<div class="bg-white rounded-xl p-6 border border-gray-200">
|
||||||
<div class="flex items-center justify-between mb-6">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<h3 class="text-xl font-bold text-gray-900">Personal Information</h3>
|
<h3 class="text-lg font-semibold text-gray-900">Profile Information</h3>
|
||||||
<div class="flex gap-2">
|
<Button
|
||||||
<Button variant="text" @click="emit('changePassword')">
|
v-if="!isEditing"
|
||||||
<template #default>
|
size="sm"
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 mr-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
variant="outline"
|
||||||
<rect width="18" height="11" x="3" y="11" rx="2" ry="2"/>
|
@click="isEditing = true"
|
||||||
<path d="M7 11V7a5 5 0 0 1 10 0v4"/>
|
>
|
||||||
</svg>
|
Edit
|
||||||
Change Password
|
</Button>
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-1 gap-6">
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
<label class="text-sm font-medium text-gray-700">Username</label>
|
|
||||||
<div class="relative">
|
|
||||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-gray-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/>
|
|
||||||
<circle cx="12" cy="7" r="4"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
:value="user?.username"
|
|
||||||
readonly
|
|
||||||
class="w-full pl-10 px-3 py-2 text-sm border border-gray-300 rounded-lg bg-gray-50 outline-none"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
<label class="text-sm font-medium text-gray-700">Email Address</label>
|
|
||||||
<div class="relative">
|
|
||||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-gray-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<rect width="20" height="16" x="2" y="4" rx="2"/>
|
|
||||||
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
:value="user?.email"
|
|
||||||
readonly
|
|
||||||
class="w-full pl-10 px-3 py-2 text-sm border border-gray-300 rounded-lg bg-gray-50 outline-none"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">Username</label>
|
||||||
|
<Input
|
||||||
|
v-if="isEditing"
|
||||||
|
v-model="username"
|
||||||
|
placeholder="Enter username"
|
||||||
|
/>
|
||||||
|
<p v-else class="text-gray-900">{{ user?.username || 'Not set' }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">Email</label>
|
||||||
|
<Input
|
||||||
|
v-if="isEditing"
|
||||||
|
v-model="email"
|
||||||
|
type="email"
|
||||||
|
placeholder="Enter email"
|
||||||
|
/>
|
||||||
|
<p v-else class="text-gray-900">{{ user?.email || 'Not set' }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="isEditing" class="flex gap-2 pt-2">
|
||||||
|
<Button size="sm" variant="outline" @click="handleCancel">Cancel</Button>
|
||||||
|
<Button size="sm" @click="handleSave">Save</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,77 +1,113 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ModelVideo } from '@/api/client';
|
import type { ModelVideo } from '@/api/client'
|
||||||
import { formatDate, formatDuration, getStatusClass } from '@/lib/utils';
|
import Checkbox from '@/components/ui/Checkbox.vue'
|
||||||
|
import { formatDate, formatDuration, getStatusClass } from '@/lib/utils'
|
||||||
|
|
||||||
defineProps<{
|
interface Props {
|
||||||
videos: ModelVideo[];
|
videos: ModelVideo[]
|
||||||
selectedVideos: ModelVideo[];
|
selectedVideos: ModelVideo[]
|
||||||
}>();
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'update:selectedVideos', value: ModelVideo[]): void;
|
(e: 'update:selectedVideos', value: ModelVideo[]): void
|
||||||
(e: 'delete', videoId: string): void;
|
(e: 'delete', videoId: string): void
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
|
const isSelected = (video: ModelVideo) => {
|
||||||
|
return props.selectedVideos.some(v => v.id === video.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleSelection = (video: ModelVideo) => {
|
||||||
|
const newSelection = isSelected(video)
|
||||||
|
? props.selectedVideos.filter(v => v.id !== video.id)
|
||||||
|
: [...props.selectedVideos, video]
|
||||||
|
emit('update:selectedVideos', newSelection)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6 gap-4">
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6 gap-4">
|
||||||
<div v-for="video in videos" :key="video.id"
|
<div
|
||||||
class="bg-white rounded-xl shadow-sm overflow-hidden hover:shadow-md transition-shadow group relative border border-gray-200"
|
v-for="video in videos"
|
||||||
:class="{ 'border-primary ring-2 ring-primary': selectedVideos.some(v => v.id === video.id) }">
|
:key="video.id"
|
||||||
|
class="bg-white rounded-xl border overflow-hidden shadow-sm hover:shadow-md transition-shadow group relative"
|
||||||
<div class="aspect-video bg-gray-200 relative overflow-hidden group-hover:opacity-95 transition-opacity">
|
:class="isSelected(video) ? 'border-primary ring-2 ring-primary' : 'border-gray-200'"
|
||||||
<!-- Grid Selection Checkbox -->
|
>
|
||||||
<div class="absolute top-2 left-2 z-10 opacity-0 group-hover:opacity-100 transition-opacity"
|
<!-- Header/Thumbnail -->
|
||||||
:class="{ 'opacity-100': selectedVideos.some(v => v.id === video.id) }">
|
<div
|
||||||
<input
|
class="aspect-video bg-gray-200 relative overflow-hidden group-hover:opacity-95 transition-opacity"
|
||||||
type="checkbox"
|
>
|
||||||
:checked="selectedVideos.some(v => v.id === video.id)"
|
<!-- Grid Selection Checkbox -->
|
||||||
@change="emit('update:selectedVideos', selectedVideos.some(v => v.id === video.id) ? selectedVideos.filter(v => v.id !== video.id) : [...selectedVideos, video])"
|
<div
|
||||||
class="rounded border-gray-300"
|
class="absolute top-2 left-2 z-10 opacity-0 group-hover:opacity-100 transition-opacity"
|
||||||
/>
|
:class="{ 'opacity-100': isSelected(video) }"
|
||||||
</div>
|
>
|
||||||
|
<Checkbox
|
||||||
<img v-if="video.thumbnail" :src="video.thumbnail" :alt="video.title"
|
:model-value="isSelected(video)"
|
||||||
class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105" />
|
:binary="true"
|
||||||
<div v-else class="w-full h-full flex items-center justify-center text-gray-400">
|
@click="toggleSelection(video)"
|
||||||
<span class="i-heroicons-film text-3xl" />
|
/>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center pointer-events-none">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span
|
|
||||||
class="absolute bottom-1.5 right-1.5 bg-black/70 text-white text-[10px] font-medium px-1.5 py-0.5 rounded">
|
|
||||||
{{ formatDuration(video.duration) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="p-4 flex flex-col h-full">
|
|
||||||
<div class="flex items-start justify-between gap-2 mb-1">
|
|
||||||
<h3 class="font-medium text-sm text-gray-900 line-clamp-2 leading-snug flex-1"
|
|
||||||
:title="video.title">
|
|
||||||
{{ video.title }}
|
|
||||||
</h3>
|
|
||||||
<button class="text-gray-400 hover:text-gray-700">
|
|
||||||
<span class="i-heroicons-ellipsis-vertical w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="text-xs text-gray-500 mb-3 line-clamp-1 h-4">{{ video.description || 'No description' }}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="mt-auto flex items-center justify-between">
|
|
||||||
<span
|
|
||||||
:class="['px-1.5 py-0.5 text-[10px] font-medium rounded-full uppercase tracking-wider', getStatusClass(video.status)]">
|
|
||||||
{{ video.status }}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<div class="text-[10px] text-gray-400">
|
|
||||||
{{ formatDate(video.created_at) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<img
|
||||||
|
v-if="video.thumbnail"
|
||||||
|
:src="video.thumbnail"
|
||||||
|
:alt="video.title"
|
||||||
|
class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="w-full h-full flex items-center justify-center text-gray-400"
|
||||||
|
>
|
||||||
|
<span class="i-heroicons-film text-3xl" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center pointer-events-none"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span
|
||||||
|
class="absolute bottom-1.5 right-1.5 bg-black/70 text-white text-[10px] font-medium px-1.5 py-0.5 rounded"
|
||||||
|
>
|
||||||
|
{{ formatDuration(video.duration) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
|
<div class="flex flex-col h-full p-4">
|
||||||
|
<div class="flex items-start justify-between gap-2 mb-1">
|
||||||
|
<h3
|
||||||
|
class="font-medium text-sm text-gray-900 line-clamp-2 leading-snug flex-1"
|
||||||
|
:title="video.title"
|
||||||
|
>
|
||||||
|
{{ video.title }}
|
||||||
|
</h3>
|
||||||
|
<button class="text-gray-400 hover:text-gray-700">
|
||||||
|
<span class="i-heroicons-ellipsis-vertical w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="text-xs text-gray-500 mb-3 line-clamp-1 h-4">
|
||||||
|
{{ video.description || 'No description' }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="mt-auto flex items-center justify-between">
|
||||||
|
<span
|
||||||
|
:class="[
|
||||||
|
'px-1.5 py-0.5 text-[10px] font-medium rounded-full uppercase tracking-wider',
|
||||||
|
getStatusClass(video.status)
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
{{ video.status }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="text-[10px] text-gray-400">
|
||||||
|
{{ formatDate(video.created_at) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,97 +1,142 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ModelVideo } from '@/api/client';
|
import type { ModelVideo } from '@/api/client'
|
||||||
import { formatBytes, formatDate, formatDuration, getStatusClass } from '@/lib/utils';
|
import { createColumnHelper } from '@/components/table/Column'
|
||||||
|
import DataTable from '@/components/table/DataTable.vue'
|
||||||
|
import Checkbox from '@/components/ui/Checkbox.vue'
|
||||||
|
import { formatBytes, formatDate, formatDuration, getStatusClass } from '@/lib/utils'
|
||||||
|
import { h } from 'vue'
|
||||||
|
|
||||||
defineProps<{
|
interface Props {
|
||||||
videos: ModelVideo[];
|
videos: ModelVideo[]
|
||||||
selectedVideos: ModelVideo[];
|
selectedVideos: ModelVideo[]
|
||||||
}>();
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'update:selectedVideos', value: ModelVideo[]): void;
|
(e: 'update:selectedVideos', value: ModelVideo[]): void
|
||||||
(e: 'delete', videoId: string): void;
|
(e: 'delete', videoId: string): void
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
|
const columnHelper = createColumnHelper<ModelVideo>()
|
||||||
|
|
||||||
|
const isSelected = (video: ModelVideo) => {
|
||||||
|
return props.selectedVideos.some(v => v.id === video.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleAll = () => {
|
||||||
|
const allSelected = props.videos.length > 0 && props.videos.every(v => isSelected(v))
|
||||||
|
const newSelection = allSelected ? [] : [...props.videos]
|
||||||
|
emit('update:selectedVideos', newSelection)
|
||||||
|
}
|
||||||
|
|
||||||
|
const allSelected = () => props.videos.length > 0 && props.videos.every(v => isSelected(v))
|
||||||
|
const someSelected = () => props.videos.some(v => isSelected(v)) && !allSelected()
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
columnHelper.display({
|
||||||
|
id: 'select',
|
||||||
|
header: () => h('div', {
|
||||||
|
class: 'flex justify-center'
|
||||||
|
}, h(Checkbox, {
|
||||||
|
modelValue: allSelected(),
|
||||||
|
binary: true,
|
||||||
|
onClick: toggleAll
|
||||||
|
})),
|
||||||
|
cell: ({ row }) => h('div', {
|
||||||
|
class: 'flex justify-center'
|
||||||
|
}, h(Checkbox, {
|
||||||
|
modelValue: isSelected(row.original),
|
||||||
|
binary: true,
|
||||||
|
onClick: () => {
|
||||||
|
const newSelection = isSelected(row.original)
|
||||||
|
? props.selectedVideos.filter(v => v.id !== row.original.id)
|
||||||
|
: [...props.selectedVideos, row.original]
|
||||||
|
emit('update:selectedVideos', newSelection)
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
size: 50
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('title', {
|
||||||
|
header: 'Video',
|
||||||
|
cell: ({ row }) => h('div', { class: 'flex items-center gap-3' }, [
|
||||||
|
h('div', { class: 'w-20 h-12 bg-gray-200 rounded overflow-hidden flex-shrink-0' }, [
|
||||||
|
row.original.thumbnail
|
||||||
|
? h('img', {
|
||||||
|
src: row.original.thumbnail,
|
||||||
|
alt: row.original.title,
|
||||||
|
class: 'w-full h-full object-cover'
|
||||||
|
})
|
||||||
|
: h('div', { class: 'w-full h-full flex items-center justify-center' }, [
|
||||||
|
h('span', { class: 'i-heroicons-film text-gray-400 text-xl' })
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
h('div', { class: 'min-w-0 flex-1' }, [
|
||||||
|
h('p', { class: 'font-medium text-gray-900 truncate' }, row.original.title),
|
||||||
|
h('p', { class: 'text-sm text-gray-500 truncate' }, row.original.description || 'No description')
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
enableSorting: true
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('status', {
|
||||||
|
header: 'Status',
|
||||||
|
cell: ({ getValue }) => {
|
||||||
|
const status = getValue() || 'Unknown'
|
||||||
|
return h('span', {
|
||||||
|
class: `px-2 py-1 text-xs font-medium rounded-full whitespace-nowrap ${getStatusClass(status)}`
|
||||||
|
}, status)
|
||||||
|
},
|
||||||
|
enableSorting: true
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('duration', {
|
||||||
|
header: 'Duration',
|
||||||
|
cell: ({ getValue }) => h('span', { class: 'text-sm text-gray-500' }, formatDuration(getValue())),
|
||||||
|
enableSorting: true
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('size', {
|
||||||
|
header: 'Size',
|
||||||
|
cell: ({ getValue }) => h('span', { class: 'text-sm text-gray-500' }, formatBytes(getValue())),
|
||||||
|
enableSorting: true
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('created_at', {
|
||||||
|
header: 'Upload Date',
|
||||||
|
cell: ({ getValue }) => h('span', { class: 'text-sm text-gray-500' }, formatDate(getValue())),
|
||||||
|
enableSorting: true
|
||||||
|
}),
|
||||||
|
columnHelper.display({
|
||||||
|
id: 'actions',
|
||||||
|
header: 'Actions',
|
||||||
|
cell: ({ row }) => h('div', { class: 'flex items-center gap-1' }, [
|
||||||
|
h('button', {
|
||||||
|
class: 'p-1.5 text-gray-400 hover:text-primary hover:bg-primary/5 rounded transition-colors',
|
||||||
|
title: 'Download'
|
||||||
|
}, h('span', { class: 'i-heroicons-arrow-down-tray w-4 h-4' })),
|
||||||
|
h('button', {
|
||||||
|
class: 'p-1.5 text-gray-400 hover:text-primary hover:bg-primary/5 rounded transition-colors',
|
||||||
|
title: 'Copy Link'
|
||||||
|
}, h('span', { class: 'i-heroicons-link w-4 h-4' })),
|
||||||
|
h('div', { class: 'w-px h-3 bg-gray-200 mx-1' }),
|
||||||
|
h('button', {
|
||||||
|
class: 'p-1.5 text-gray-400 hover:text-blue-600 hover:bg-blue-50 rounded transition-colors',
|
||||||
|
title: 'Edit'
|
||||||
|
}, h('span', { class: 'i-heroicons-pencil w-4 h-4' })),
|
||||||
|
h('button', {
|
||||||
|
class: 'p-1.5 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded transition-colors',
|
||||||
|
title: 'Delete',
|
||||||
|
onClick: () => row.original.id && emit('delete', row.original.id)
|
||||||
|
}, h('span', { class: 'i-heroicons-trash w-4 h-4' }))
|
||||||
|
]),
|
||||||
|
size: 150
|
||||||
|
})
|
||||||
|
]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="bg-white rounded-xl border border-gray-200 overflow-hidden">
|
<div class="bg-white rounded-xl border border-gray-200 overflow-hidden">
|
||||||
<div class="overflow-x-auto">
|
<DataTable
|
||||||
<table class="w-full min-w-[50rem]">
|
:data="videos"
|
||||||
<thead>
|
:columns="columns"
|
||||||
<tr class="border-b border-gray-200 bg-gray-50">
|
:enable-sorting="true"
|
||||||
<th class="w-12 px-4 py-3 text-left">
|
/>
|
||||||
<input type="checkbox" class="rounded border-gray-300" />
|
</div>
|
||||||
</th>
|
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Video</th>
|
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
|
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Duration</th>
|
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Size</th>
|
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Upload Date</th>
|
|
||||||
<th class="w-32 px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody class="divide-y divide-gray-100">
|
|
||||||
<tr v-for="video in videos" :key="video.id" class="hover:bg-gray-50">
|
|
||||||
<td class="px-4 py-3">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
:checked="selectedVideos.some(v => v.id === video.id)"
|
|
||||||
@change="emit('update:selectedVideos', selectedVideos.some(v => v.id === video.id) ? selectedVideos.filter(v => v.id !== video.id) : [...selectedVideos, video])"
|
|
||||||
class="rounded border-gray-300"
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td class="px-4 py-3">
|
|
||||||
<div class="flex items-center gap-3">
|
|
||||||
<div class="w-20 h-12 bg-gray-200 rounded overflow-hidden flex-shrink-0">
|
|
||||||
<img v-if="video.thumbnail" :src="video.thumbnail" :alt="video.title"
|
|
||||||
class="w-full h-full object-cover" />
|
|
||||||
<div v-else class="w-full h-full flex items-center justify-center">
|
|
||||||
<span class="i-heroicons-film text-gray-400 text-xl" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="min-w-0 flex-1">
|
|
||||||
<p class="font-medium text-gray-900 truncate">{{ video.title }}</p>
|
|
||||||
<p class="text-sm text-gray-500 truncate">{{ video.description || 'No description' }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td class="px-4 py-3">
|
|
||||||
<span
|
|
||||||
:class="['px-2 py-1 text-xs font-medium rounded-full whitespace-nowrap', getStatusClass(video.status)]">
|
|
||||||
{{ video.status || 'Unknown' }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td class="px-4 py-3 text-sm text-gray-500">{{ formatDuration(video.duration) }}</td>
|
|
||||||
<td class="px-4 py-3 text-sm text-gray-500">{{ formatBytes(video.size) }}</td>
|
|
||||||
<td class="px-4 py-3 text-sm text-gray-500">{{ formatDate(video.created_at) }}</td>
|
|
||||||
<td class="px-4 py-3">
|
|
||||||
<div class="flex items-center gap-1">
|
|
||||||
<button
|
|
||||||
class="p-1.5 text-gray-400 hover:text-primary hover:bg-primary/5 rounded transition-colors"
|
|
||||||
title="Download">
|
|
||||||
<span class="i-heroicons-arrow-down-tray w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="p-1.5 text-gray-400 hover:text-primary hover:bg-primary/5 rounded transition-colors"
|
|
||||||
title="Copy Link">
|
|
||||||
<span class="i-heroicons-link w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
<div class="w-px h-3 bg-gray-200 mx-1"></div>
|
|
||||||
<button
|
|
||||||
class="p-1.5 text-gray-400 hover:text-blue-600 hover:bg-blue-50 rounded transition-colors"
|
|
||||||
title="Edit">
|
|
||||||
<span class="i-heroicons-pencil w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
<button @click="emit('delete', video.id!)"
|
|
||||||
class="p-1.5 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded transition-colors"
|
|
||||||
title="Delete">
|
|
||||||
<span class="i-heroicons-trash w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -178,6 +178,10 @@ export default defineConfig({
|
|||||||
DEFAULT: "#fafafa",
|
DEFAULT: "#fafafa",
|
||||||
light: "#f8f9fa",
|
light: "#f8f9fa",
|
||||||
},
|
},
|
||||||
|
muted: {
|
||||||
|
DEFAULT: "#f5f4f2",
|
||||||
|
light: "#f8f9fa",
|
||||||
|
},
|
||||||
border: {
|
border: {
|
||||||
DEFAULT: "#e6e7e2",
|
DEFAULT: "#e6e7e2",
|
||||||
light: "#f8f9fa",
|
light: "#f8f9fa",
|
||||||
|
|||||||
@@ -3,28 +3,39 @@ import vue from "@vitejs/plugin-vue";
|
|||||||
import vueJsx from "@vitejs/plugin-vue-jsx";
|
import vueJsx from "@vitejs/plugin-vue-jsx";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import unocss from "unocss/vite";
|
import unocss from "unocss/vite";
|
||||||
|
import AutoImport from "unplugin-auto-import/vite";
|
||||||
|
import Components from "unplugin-vue-components/vite";
|
||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
import ssrPlugin from "./ssrPlugin";
|
import ssrPlugin from "./ssrPlugin";
|
||||||
|
|
||||||
export default defineConfig((env) => {
|
export default defineConfig((env) => {
|
||||||
// console.log("env:", env, import.meta.env);
|
|
||||||
return {
|
return {
|
||||||
plugins: [
|
plugins: [
|
||||||
unocss(),
|
unocss(),
|
||||||
vue(),
|
vue(),
|
||||||
vueJsx(),
|
vueJsx(),
|
||||||
|
AutoImport({
|
||||||
|
imports: ["vue", "vue-router", "pinia"],
|
||||||
|
dts: true,
|
||||||
|
}),
|
||||||
|
Components({
|
||||||
|
dirs: ["src/components"],
|
||||||
|
extensions: ["vue", "tsx"],
|
||||||
|
dts: true,
|
||||||
|
dtsTsx: true,
|
||||||
|
directives: false,
|
||||||
|
}),
|
||||||
ssrPlugin(),
|
ssrPlugin(),
|
||||||
cloudflare(),
|
cloudflare(),
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
"@": path.resolve(__dirname, "./src"),
|
"@": path.resolve(__dirname, "./src"),
|
||||||
// "httpClientAdapter": path.resolve(__dirname, "./src/api/httpClientAdapter.server.ts")
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
exclude: ["vue"],
|
exclude: ["vue"],
|
||||||
},
|
},
|
||||||
|
|
||||||
ssr: {
|
ssr: {
|
||||||
noExternal: ["vue"],
|
noExternal: ["vue"],
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user